fix(tui): keep progress and shell work reachable
This commit is contained in:
+11
-5
@@ -94,11 +94,12 @@ runs against Pi 0.8.41 and by dogfooding repeated manual compaction.
|
||||
- `todo_write` is an optional progress surface rather than required model
|
||||
ceremony.
|
||||
- New turns use one small, stable toolbox: `read`, `write`, `edit`, `bash`,
|
||||
`agent`, and `tool_search`. Specialized native, Web, MCP, plugin, memory,
|
||||
task, and verification tools are policy-filtered and searchable; activated
|
||||
schemas stay in a bounded per-conversation cache. Every sub-agent keeps its
|
||||
own search and cache, including policy-allowed Web research, while forked
|
||||
context and parent activations remain warm starts rather than allowlists.
|
||||
`agent`, `todo_write`, and `tool_search`. The optional progress tool stays
|
||||
visible as familiar working memory; specialized native, Web, MCP, plugin,
|
||||
memory, task, and verification tools are policy-filtered and searchable;
|
||||
activated schemas stay in a bounded per-conversation cache. Every sub-agent
|
||||
keeps its own search and cache, including policy-allowed Web research, while
|
||||
forked context and parent activations remain warm starts rather than allowlists.
|
||||
- The direct file and shell schemas follow Pi's deliberately small contract:
|
||||
bounded complete-line reads, hash-free writes, unambiguous multi-edit with
|
||||
BOM/CRLF preservation and conservative fuzzy matching, and one foreground
|
||||
@@ -122,6 +123,11 @@ runs against Pi 0.8.41 and by dogfooding repeated manual compaction.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Sending more context while a lowercase `bash` command is running now moves
|
||||
the command to `/jobs` and returns a successful running receipt instead of
|
||||
falsely reporting `Command exited with code -1`; the process keeps running
|
||||
and its completion still arrives through the normal runtime event.
|
||||
|
||||
- First-run usage disclosure now opens as a native Codewhale modal instead of a
|
||||
shell questionnaire before application startup. Telemetry remains unarmed
|
||||
until the native choice is made, and an in-memory Disable choice governs the
|
||||
|
||||
+13
-14
@@ -4961,24 +4961,23 @@ mod tests {
|
||||
async fn assert_kimi_code_captures_exact_general_child_catalog() {
|
||||
let tools = crate::tools::subagent::kimi_general_child_request_tools_fixture();
|
||||
let source_len = tools.len();
|
||||
// The deliberate lowercase contract: the child wire catalog is the
|
||||
// fixed six-tool surface, with specialized tools discoverable through
|
||||
// tool_search rather than eager.
|
||||
// Specialized tools remain discoverable beyond the fixed eager head.
|
||||
assert_eq!(
|
||||
source_len, 6,
|
||||
"expected the six-tool General child catalog: {tools:?}"
|
||||
source_len,
|
||||
crate::core::engine::default_active_native_tool_names().len() + 1,
|
||||
"expected the seven-tool General child catalog: {tools:?}"
|
||||
);
|
||||
let source_names = tools
|
||||
.iter()
|
||||
.map(|tool| tool.name.as_str())
|
||||
.map(|tool| tool.name.clone())
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
assert_eq!(
|
||||
source_names,
|
||||
["agent", "bash", "edit", "read", "tool_search", "write"]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
"the child wire catalog must be exactly the six-tool surface"
|
||||
);
|
||||
let expected_names = crate::core::engine::default_active_native_tool_names()
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([crate::core::engine::tool_catalog::TOOL_SEARCH_NAME])
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
assert_eq!(source_names, expected_names);
|
||||
|
||||
// Name the offending first-party tool in test-only diagnostics while
|
||||
// production errors remain fixed and non-secret.
|
||||
@@ -4999,7 +4998,7 @@ mod tests {
|
||||
|
||||
let captured = body["tools"].as_array().expect("captured tool catalog");
|
||||
assert_eq!(captured.len(), source_len);
|
||||
for required in ["read", "write", "edit", "bash", "agent", "tool_search"] {
|
||||
for required in &source_names {
|
||||
assert!(
|
||||
captured_function(&body, required).is_object(),
|
||||
"{required} must reach the Kimi Code wire"
|
||||
|
||||
@@ -6669,9 +6669,9 @@ fn approval_stamp_preserves_existing_metadata() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_pi_core_native_tools_default_to_eager() {
|
||||
fn core_primitives_and_todo_write_default_to_eager() {
|
||||
let always_load = HashSet::new();
|
||||
for core in ["read", "write", "edit", "bash", "agent"] {
|
||||
for core in ["read", "write", "edit", "bash", "agent", "todo_write"] {
|
||||
assert!(!should_default_defer_tool(core, &always_load));
|
||||
}
|
||||
for searchable in ["File", "Bash", "Git", "Run", "tasks", "git_blame"] {
|
||||
@@ -6681,7 +6681,7 @@ fn only_pi_core_native_tools_default_to_eager() {
|
||||
|
||||
#[test]
|
||||
fn default_active_contract_keeps_discovery_and_core_tools_eager() {
|
||||
const EXPECTED_NATIVE: [&str; 5] = ["read", "write", "edit", "bash", "agent"];
|
||||
const EXPECTED_NATIVE: [&str; 6] = ["read", "write", "edit", "bash", "agent", "todo_write"];
|
||||
assert_eq!(
|
||||
default_active_native_tool_names(),
|
||||
EXPECTED_NATIVE.as_slice()
|
||||
@@ -6715,7 +6715,7 @@ fn default_active_contract_keeps_discovery_and_core_tools_eager() {
|
||||
#[test]
|
||||
fn non_yolo_mode_retains_default_defer_policy() {
|
||||
let always_load = HashSet::new();
|
||||
for core in ["read", "write", "edit", "bash", "agent"] {
|
||||
for core in ["read", "write", "edit", "bash", "agent", "todo_write"] {
|
||||
assert!(!should_default_defer_tool(core, &always_load));
|
||||
}
|
||||
for searchable in [
|
||||
@@ -7191,7 +7191,15 @@ fn metric_tool_names<'a>(
|
||||
#[allow(clippy::await_holding_lock)]
|
||||
async fn runtime_contract_tool_metric_uses_canonical_mode_surfaces() {
|
||||
let payload = measure_production_mode_tool_catalogs().await;
|
||||
let expected_active = HashSet::from(["agent", "bash", "edit", "read", "tool_search", "write"]);
|
||||
let expected_active = HashSet::from([
|
||||
"agent",
|
||||
"bash",
|
||||
"edit",
|
||||
"read",
|
||||
"todo_write",
|
||||
"tool_search",
|
||||
"write",
|
||||
]);
|
||||
|
||||
for mode in ["plan", "act", "operate"] {
|
||||
let full = metric_tool_names(&payload, mode, "full");
|
||||
@@ -8020,8 +8028,8 @@ fn model_catalog_exposes_work_update_as_sole_progress_surface() {
|
||||
"todo_write must be model-visible"
|
||||
);
|
||||
assert!(
|
||||
!active.contains("todo_write"),
|
||||
"todo_write is searchable/lazy"
|
||||
active.contains("todo_write"),
|
||||
"todo_write must be available without a discovery turn"
|
||||
);
|
||||
assert!(
|
||||
!catalog_names.contains("update_plan"),
|
||||
|
||||
@@ -43,14 +43,11 @@ pub(crate) fn is_tool_search_tool(name: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
// Crate-visible rather than `pub(super)` so the hook gate's classifier test can
|
||||
// assert it still recognises every default-active name. Without that anchor the
|
||||
// test pins hardcoded strings and stays green through a tool rename, while the
|
||||
// gate silently reclassifies the renamed tool as unknown.
|
||||
// Crate-visible so the hook gate tests the real eager names instead of a copy.
|
||||
#[rustfmt::skip]
|
||||
pub(crate) const DEFAULT_ACTIVE_NATIVE_TOOLS: &[&str] = &[
|
||||
// A deliberately small, stable router surface. Specialized native, MCP,
|
||||
// plugin, and durable-work tools remain discoverable through tool_search.
|
||||
"read", "write", "edit", "bash", "agent",
|
||||
// Specialized native, MCP, plugin, and durable-work tools stay searchable.
|
||||
"read", "write", "edit", "bash", "agent", "todo_write",
|
||||
];
|
||||
|
||||
const CORE_ACTION_TOOL_FALLBACKS: &[CoreActionToolFallback] = &[
|
||||
|
||||
@@ -72,12 +72,20 @@ fn published_synthetic_names_agree_with_the_synthetic_predicate() {
|
||||
fn first_turn_surface_is_stable_across_plan_work_and_full_access() {
|
||||
assert_eq!(
|
||||
DEFAULT_ACTIVE_NATIVE_TOOLS,
|
||||
&["read", "write", "edit", "bash", "agent"]
|
||||
&["read", "write", "edit", "bash", "agent", "todo_write"]
|
||||
);
|
||||
let expected = ["agent", "bash", "edit", "read", "tool_search", "write"]
|
||||
.into_iter()
|
||||
.map(str::to_string)
|
||||
.collect::<BTreeSet<_>>();
|
||||
let expected = [
|
||||
"agent",
|
||||
"bash",
|
||||
"edit",
|
||||
"read",
|
||||
"todo_write",
|
||||
"tool_search",
|
||||
"write",
|
||||
]
|
||||
.into_iter()
|
||||
.map(str::to_string)
|
||||
.collect::<BTreeSet<_>>();
|
||||
for mode in [AppMode::Plan, AppMode::Agent, AppMode::Yolo] {
|
||||
let mut catalog = [
|
||||
"read",
|
||||
@@ -85,6 +93,7 @@ fn first_turn_surface_is_stable_across_plan_work_and_full_access() {
|
||||
"edit",
|
||||
"bash",
|
||||
"agent",
|
||||
"todo_write",
|
||||
"Git",
|
||||
"Run",
|
||||
"tasks",
|
||||
@@ -244,7 +253,7 @@ fn unknown_and_wildcard_allowlists_keep_mcp_startup() {
|
||||
#[test]
|
||||
fn compact_surface_keeps_the_exact_eager_agent_head() {
|
||||
let catalog = build_model_tool_catalog_with_surface(
|
||||
["read", "write", "edit", "bash", "agent"]
|
||||
["read", "write", "edit", "bash", "agent", "todo_write"]
|
||||
.into_iter()
|
||||
.map(tool)
|
||||
.collect(),
|
||||
|
||||
@@ -2450,7 +2450,7 @@ fn tool_category_for(tool_name: &str, tool_args: Option<&str>) -> &'static str {
|
||||
// `shell.rs` still stamps it for the `shell_env` hook event.
|
||||
"bash" | "Bash" | "exec_shell" => "shell",
|
||||
// The lowercase primitives ship without an action envelope.
|
||||
"read" => "safe",
|
||||
"read" | "todo_write" => "safe",
|
||||
"write" | "edit" => "file_write",
|
||||
"File" | "file" => match action.as_deref() {
|
||||
Some("read" | "list" | "search_name" | "search_content") => "safe",
|
||||
@@ -5608,6 +5608,7 @@ command = "echo project"
|
||||
("bash", "shell"),
|
||||
// The router itself touches nothing a hook needs to gate.
|
||||
("agent", "other"),
|
||||
("todo_write", "safe"),
|
||||
];
|
||||
for name in crate::core::engine::tool_catalog::DEFAULT_ACTIVE_NATIVE_TOOLS {
|
||||
let expected = EXPECTED.iter().find(|(n, _)| n == name).map(|(_, c)| *c);
|
||||
|
||||
@@ -145,7 +145,7 @@ fn validate_shell_working_dir(path: &Path, inherited_session_workspace: bool) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Status of a shell process
|
||||
/// Status of a shell process.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum ShellStatus {
|
||||
Running,
|
||||
@@ -155,9 +155,8 @@ pub enum ShellStatus {
|
||||
TimedOut,
|
||||
}
|
||||
|
||||
/// Result from a shell command execution
|
||||
/// Result from a shell command execution.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
pub struct ShellResult {
|
||||
pub task_id: Option<String>,
|
||||
pub status: ShellStatus,
|
||||
@@ -3517,6 +3516,20 @@ fn finish_pi_bash_result(
|
||||
) -> Result<ToolResult, ToolError> {
|
||||
let mut output = result.stdout.clone();
|
||||
output.push_str(&result.stderr);
|
||||
let metadata = json!({
|
||||
"evidence_routing": "inline", "exit_code": result.exit_code,
|
||||
"status": format!("{:?}", result.status), "duration_ms": result.duration_ms,
|
||||
"sandboxed": result.sandboxed, "sandbox_type": result.sandbox_type,
|
||||
"task_id": result.task_id, "backgrounded": result.status == ShellStatus::Running,
|
||||
});
|
||||
if result.status == ShellStatus::Running {
|
||||
let task_id = result.task_id.as_deref().unwrap_or("unknown");
|
||||
let partial = (!output.is_empty()).then(|| format!("\n\nOutput so far:\n{output}"));
|
||||
return Ok(ToolResult::success(format!(
|
||||
"Foreground shell wait moved to /jobs: {task_id}{}\n\nThe command is still running; completion will appear as a runtime event.",
|
||||
partial.as_deref().unwrap_or_default()
|
||||
)).with_metadata(metadata));
|
||||
}
|
||||
if result.status != ShellStatus::Completed {
|
||||
let status = pi_bash_error_status(&result, timeout_ms);
|
||||
return Err(ToolError::execution_failed(if output.is_empty() {
|
||||
@@ -3531,14 +3544,7 @@ fn finish_pi_bash_result(
|
||||
} else {
|
||||
output
|
||||
})
|
||||
.with_metadata(json!({
|
||||
"evidence_routing": "inline",
|
||||
"exit_code": result.exit_code,
|
||||
"status": "Completed",
|
||||
"duration_ms": result.duration_ms,
|
||||
"sandboxed": result.sandboxed,
|
||||
"sandbox_type": result.sandbox_type,
|
||||
})))
|
||||
.with_metadata(metadata))
|
||||
}
|
||||
|
||||
/// Small foreground-only shell surface shown to new model turns.
|
||||
|
||||
@@ -2269,6 +2269,50 @@ async fn test_exec_shell_foreground_can_move_to_background() {
|
||||
assert_eq!(killed.status, ShellStatus::Killed);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lowercase_bash_foreground_detach_is_a_successful_running_receipt() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let ctx = ToolContext::new(tmp.path());
|
||||
let shell_manager = ctx.shell_manager.clone();
|
||||
let command = sleep_command(30);
|
||||
let task_ctx = ctx.clone();
|
||||
|
||||
let task = tokio::spawn(async move {
|
||||
PiBashTool
|
||||
.execute(json!({"command": command}), &task_ctx)
|
||||
.await
|
||||
.expect("execute")
|
||||
});
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(150)).await;
|
||||
shell_manager
|
||||
.lock()
|
||||
.expect("shell manager lock")
|
||||
.request_foreground_background();
|
||||
|
||||
let result = tokio::time::timeout(Duration::from_secs(5), task)
|
||||
.await
|
||||
.expect("foreground shell should detach")
|
||||
.expect("task should not panic");
|
||||
|
||||
assert!(result.success, "{}", result.content);
|
||||
assert!(
|
||||
result.content.contains("moved to /jobs"),
|
||||
"{}",
|
||||
result.content
|
||||
);
|
||||
assert!(!result.content.contains("code -1"), "{}", result.content);
|
||||
let metadata = result.metadata.expect("metadata");
|
||||
assert_eq!(metadata["status"], "Running");
|
||||
assert_eq!(metadata["backgrounded"], true);
|
||||
let task_id = metadata["task_id"].as_str().expect("task id");
|
||||
|
||||
let mut manager = shell_manager.lock().expect("shell manager lock");
|
||||
let job = manager.inspect_job(task_id).expect("inspect job");
|
||||
assert_eq!(job.snapshot.status, ShellStatus::Running);
|
||||
manager.kill(task_id).expect("kill test job");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_exec_shell_wait_cancel_leaves_background_process_running() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
|
||||
@@ -6225,10 +6225,18 @@ fn small_surface_starts_with_only_pi_head_and_search() {
|
||||
|
||||
assert_eq!(
|
||||
model_tool_names(model_request_tools(&mut surface)),
|
||||
["agent", "bash", "edit", "read", "tool_search", "write"]
|
||||
.into_iter()
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
[
|
||||
"agent",
|
||||
"bash",
|
||||
"edit",
|
||||
"read",
|
||||
"todo_write",
|
||||
"tool_search",
|
||||
"write",
|
||||
]
|
||||
.into_iter()
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
);
|
||||
let strict = surface.request_tools(surface.catalog.clone(), true);
|
||||
assert_eq!(
|
||||
@@ -6474,7 +6482,7 @@ fn small_surface_depth_cap_removes_only_agent() {
|
||||
);
|
||||
assert_eq!(
|
||||
model_tool_names(model_request_tools(&mut surface)),
|
||||
["bash", "edit", "read", "tool_search", "write"]
|
||||
["bash", "edit", "read", "todo_write", "tool_search", "write"]
|
||||
.into_iter()
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
|
||||
@@ -292,10 +292,10 @@ second catalog snapshot.
|
||||
|
||||
The default diet removes `exec_wait` and `exec_interact` from the active head
|
||||
(they become hidden-compat; their canonical twins `exec_shell_wait` /
|
||||
`exec_shell_interact` stay). `tts` and `todo_*` are *already not* in the active
|
||||
set, so they do not change the active budget in this diet. The net effect of
|
||||
this specific diet is to remove two duplicate active aliases from whatever
|
||||
default active head is current after the surrounding v0.8.53 PR batch.
|
||||
`exec_shell_interact` stay). `tts` and the legacy `todo_*` aliases remain out
|
||||
of the active set. The canonical `todo_write` tool became eager in v0.9.6 as an
|
||||
explicit budget decision so ordinary progress tracking never requires a
|
||||
discovery turn.
|
||||
|
||||
### Per mode (Plan / Agent / YOLO)
|
||||
|
||||
|
||||
@@ -17,16 +17,17 @@ Implementation sources:
|
||||
|
||||
## Default-active contract
|
||||
|
||||
New turns start with exactly six model-facing names:
|
||||
New turns start with exactly seven model-facing names:
|
||||
|
||||
1. `read`
|
||||
2. `write`
|
||||
3. `edit`
|
||||
4. `bash`
|
||||
5. `agent`
|
||||
6. `tool_search`
|
||||
6. `todo_write`
|
||||
7. `tool_search`
|
||||
|
||||
The first five are `DEFAULT_ACTIVE_NATIVE_TOOLS` in
|
||||
The first six are `DEFAULT_ACTIVE_NATIVE_TOOLS` in
|
||||
`crates/tui/src/core/engine/tool_catalog.rs`. `tool_search` is synthetic and is
|
||||
always active. An authority boundary may remove `agent` at the maximum child
|
||||
depth, but route size alone must not change this core vocabulary.
|
||||
@@ -40,6 +41,7 @@ The direct schemas deliberately stay small:
|
||||
| `edit` | `path`, `edits` | Apply one or more unambiguous text replacements against one original snapshot. |
|
||||
| `bash` | `command`, optional `timeout` | Run one cancellable foreground shell command and return a bounded tail. |
|
||||
| `agent` | delegated task and optional scope/context controls | Start or inspect focused child work. |
|
||||
| `todo_write` | complete replacement list of `{content, status}` items | Keep optional, agent-owned progress notes for genuinely multi-step work. |
|
||||
| `tool_search` | `query`, optional matching controls | Discover policy-allowed deferred tools and add selected schemas to this conversation's toolbox. |
|
||||
|
||||
Mode is an authority decision, not a synonym system. Plan, Work, and Operate
|
||||
@@ -49,8 +51,8 @@ trusted-path, repository-law, and managed-policy gates. Full Access changes
|
||||
ordinary approval behavior but does not bypass hard safety or repository law.
|
||||
|
||||
`update_plan` remains registered only for saved-artifact compatibility and is
|
||||
not model-visible. `todo_write`, `tasks`, `Git`, `Run`, `Web`, `remember`, and
|
||||
other specialized capabilities are searchable rather than first-turn ceremony.
|
||||
not model-visible. `tasks`, `Git`, `Run`, `Web`, `remember`, and other
|
||||
specialized capabilities are searchable rather than first-turn ceremony.
|
||||
|
||||
## Deferred and dynamic tools
|
||||
|
||||
@@ -253,7 +255,7 @@ exits 0 with "0 passed; N filtered out" when a filter matches nothing, so a
|
||||
misspelled filter is indistinguishable from a pass. (Three filters printed here
|
||||
before v0.9.4 named tests that did not exist.)
|
||||
|
||||
The provider-free receipt must report the six default-active names listed
|
||||
The provider-free receipt must report the seven default-active names listed
|
||||
above. A separate repository-wide tool count may include deferred, dynamic,
|
||||
feature-gated, and compatibility-only registrations; it is not the number of
|
||||
tools placed in the first-turn model catalog.
|
||||
|
||||
@@ -85,23 +85,24 @@
|
||||
"modes": {
|
||||
"act": {
|
||||
"active": {
|
||||
"bytes": 12786,
|
||||
"identity_sha256": "34a00b1aa3688c88b5b3b57b292b0fbc01ced97e7d5ae00ecbeefee4cfb62671",
|
||||
"tokens_est": 3197,
|
||||
"bytes": 13435,
|
||||
"identity_sha256": "8411bddfc0d7fce72ec53dfeef341f28e6a3cfe37b98a721dc05b17d53b0a13e",
|
||||
"tokens_est": 3359,
|
||||
"tool_names": [
|
||||
"agent",
|
||||
"bash",
|
||||
"edit",
|
||||
"read",
|
||||
"todo_write",
|
||||
"tool_search",
|
||||
"write"
|
||||
],
|
||||
"tools": 6
|
||||
"tools": 7
|
||||
},
|
||||
"full": {
|
||||
"bytes": 68409,
|
||||
"bytes": 68377,
|
||||
"identity_sha256": "8f3b51d6221804baac42de911d89f772b89c38a1320da7ebb6d3bdc96c4efb79",
|
||||
"tokens_est": 17103,
|
||||
"tokens_est": 17095,
|
||||
"tool_names": [
|
||||
"Git",
|
||||
"Run",
|
||||
@@ -164,23 +165,24 @@
|
||||
},
|
||||
"operate": {
|
||||
"active": {
|
||||
"bytes": 12786,
|
||||
"identity_sha256": "34a00b1aa3688c88b5b3b57b292b0fbc01ced97e7d5ae00ecbeefee4cfb62671",
|
||||
"tokens_est": 3197,
|
||||
"bytes": 13435,
|
||||
"identity_sha256": "8411bddfc0d7fce72ec53dfeef341f28e6a3cfe37b98a721dc05b17d53b0a13e",
|
||||
"tokens_est": 3359,
|
||||
"tool_names": [
|
||||
"agent",
|
||||
"bash",
|
||||
"edit",
|
||||
"read",
|
||||
"todo_write",
|
||||
"tool_search",
|
||||
"write"
|
||||
],
|
||||
"tools": 6
|
||||
"tools": 7
|
||||
},
|
||||
"full": {
|
||||
"bytes": 68409,
|
||||
"bytes": 68377,
|
||||
"identity_sha256": "8f3b51d6221804baac42de911d89f772b89c38a1320da7ebb6d3bdc96c4efb79",
|
||||
"tokens_est": 17103,
|
||||
"tokens_est": 17095,
|
||||
"tool_names": [
|
||||
"Git",
|
||||
"Run",
|
||||
@@ -243,23 +245,24 @@
|
||||
},
|
||||
"plan": {
|
||||
"active": {
|
||||
"bytes": 12786,
|
||||
"identity_sha256": "34a00b1aa3688c88b5b3b57b292b0fbc01ced97e7d5ae00ecbeefee4cfb62671",
|
||||
"tokens_est": 3197,
|
||||
"bytes": 13435,
|
||||
"identity_sha256": "8411bddfc0d7fce72ec53dfeef341f28e6a3cfe37b98a721dc05b17d53b0a13e",
|
||||
"tokens_est": 3359,
|
||||
"tool_names": [
|
||||
"agent",
|
||||
"bash",
|
||||
"edit",
|
||||
"read",
|
||||
"todo_write",
|
||||
"tool_search",
|
||||
"write"
|
||||
],
|
||||
"tools": 6
|
||||
"tools": 7
|
||||
},
|
||||
"full": {
|
||||
"bytes": 41403,
|
||||
"bytes": 41371,
|
||||
"identity_sha256": "2cf3e5b6810e7840ee2d2df2b6dd81a24058e3e916acdb1199aff3abae916a41",
|
||||
"tokens_est": 10351,
|
||||
"tokens_est": 10343,
|
||||
"tool_names": [
|
||||
"Git",
|
||||
"Web",
|
||||
|
||||
Reference in New Issue
Block a user