Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
5d9c3ed
fix(kimi): repair empty items instead of dropping
asto18089 Oct 12, 2026
1d6b8ad
fix(tool-search): report evicted activations
asto18089 Oct 12, 2026
f25fab2
fix(kimi): repair nested empty items shapes
asto18089 Oct 12, 2026
6cbf250
fix(tool-search): report every toolbox eviction
asto18089 Oct 12, 2026
4628c9a
fix(tool-search): report toolbox drops truthfully
asto18089 Oct 12, 2026
5ba133c
fix(tool-search): net re-admissions from evictions
asto18089 Oct 12, 2026
56ab249
fix(tool-search): report turn-start toolbox evictions
asto18089 Oct 12, 2026
ed636f2
fix(subagent): report child toolbox evictions
asto18089 Oct 12, 2026
9b2b7ba
docs(tool-search): truthful toolbox contract
asto18089 Oct 12, 2026
2816d95
test(engine): pin the stranded-note sweep and re-park backstop
asto18089 Oct 12, 2026
0cf4243
fix(tool-search): net parked evictions at ride time
asto18089 Oct 12, 2026
87ceae0
fix(subagent): report child evictions on error results
asto18089 Oct 12, 2026
c868077
fix(tool-search): stay silent when hydration is refused
asto18089 Oct 12, 2026
6c65022
test(kimi): pin the remaining empty-items repair visits
asto18089 Oct 12, 2026
a4cae80
test(engine): pin sweep-before-park at turn entry
asto18089 Oct 12, 2026
133bdba
docs(tool-search): scope the reporting quantifier to the code
asto18089 Oct 12, 2026
fb23a95
fix(tool-search): net notes against active tools
asto18089 Oct 13, 2026
48f0475
fix(tool-search): mark refusal receipts unexecuted
asto18089 Oct 13, 2026
22f1887
fix(tool-search): truthful oversize prose and dedup
asto18089 Oct 13, 2026
fbb6cea
fix(tool-search): re-park notes by extending
asto18089 Oct 13, 2026
ef7e793
test(engine): pin the batch eviction park
asto18089 Oct 13, 2026
022d8e0
test(subagent): pin the note ride through the glue
asto18089 Oct 13, 2026
fab7e20
test(client): pin the surviving wire tool name
asto18089 Oct 13, 2026
e5ebd61
docs(tool-search): tighten the toolbox contract
asto18089 Oct 13, 2026
25118a5
fix(tool-search): net step evictions at ride time
asto18089 Oct 13, 2026
44af058
fix(engine): carry executed into tool.result audit
asto18089 Oct 13, 2026
fec5d4b
docs(tool-search): name the overflowing budget
asto18089 Oct 13, 2026
abc2b9e
fix(tool-search): refresh respects evictions
asto18089 Oct 13, 2026
ce52b7b
fix(tool-search): dedupe and drain the eviction-note render
asto18089 Oct 13, 2026
ecb814a
test(engine): pin refusal audit executed marker
asto18089 Oct 13, 2026
a501c6b
test(subagent): pin the child dispatch glue
asto18089 Oct 13, 2026
8922312
test(kimi): pin minItems positions and fail-closed shapes
asto18089 Oct 13, 2026
22f8e55
fix(cli): count unexecuted receipts as neither
asto18089 Oct 13, 2026
ae27ae9
fix(tool-search): truthful prose, comments, and one bounds pop
asto18089 Oct 13, 2026
0e1c5e7
fix(tool-search): name the unrecoverable evictions
asto18089 Oct 13, 2026
0e89a8a
fix(tool-search): splice refreshes and honor turn policy
asto18089 Oct 13, 2026
56bd33c
fix(subagent): rehydrate mid-request evicted retries
asto18089 Oct 13, 2026
de2851e
test(cli): pin the unexecuted-receipt rollup bucket
asto18089 Oct 13, 2026
784b696
test(kimi): pin the anyOf and ref minItems positions
asto18089 Oct 13, 2026
3281b4d
docs(tool-search): truthful gate prose and child entries
asto18089 Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions crates/cli/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,24 @@ fn read_audit_log(path: &Path, since: Option<DateTime<Utc>>, rollup: &mut Rollup
stats.elapsed_samples += 1;
}

// Success / failure
// Success / failure. A receipt the engine marked
// `executed: false` (a deferred-tool hydration receipt, an
// oversize-hydration refusal) answered the call without the
// tool ever running, so it counts as neither — counting it
// as a success would inflate the tool's real execution rate.
let success = v
.pointer("/details/success")
.or_else(|| v.pointer("/payload/success"))
.and_then(|b| b.as_bool())
.unwrap_or(true);
if success {
let executed = v
.pointer("/details/executed")
.or_else(|| v.pointer("/payload/executed"))
.and_then(|b| b.as_bool())
.unwrap_or(true);
if success && executed {
stats.successes += 1;
} else {
} else if !success {
stats.failures += 1;
}
}
Expand Down Expand Up @@ -1015,6 +1024,39 @@ mod tests {
assert_eq!(rollup.tools["read_file"].calls, 1);
}

#[test]
fn tool_result_rollup_counts_unexecuted_receipts_as_neither() {
use std::io::Write;
let mut tmp = tempfile::NamedTempFile::new().unwrap();
// A plain success, a plain failure, an `executed: false` receipt
// (a hydration receipt or an oversize-hydration refusal — the tool
// never ran), and an executed-marked success.
let lines = [
r#"{"event":"tool.result","ts":"2026-04-01T10:00:00+00:00","details":{"tool_name":"exec_shell","success":true}}"#,
r#"{"event":"tool.result","ts":"2026-04-01T10:01:00+00:00","details":{"tool_name":"exec_shell","success":false}}"#,
r#"{"event":"tool.result","ts":"2026-04-01T10:02:00+00:00","details":{"tool_name":"deferred_tool","success":true,"executed":false}}"#,
r#"{"event":"tool.result","ts":"2026-04-01T10:03:00+00:00","details":{"tool_name":"other_tool","success":true,"executed":true}}"#,
];
for line in lines {
writeln!(tmp, "{line}").unwrap();
}

let mut rollup = Rollup::default();
read_audit_log(tmp.path(), None, &mut rollup);

assert_eq!(rollup.tools["exec_shell"].calls, 2);
assert_eq!(rollup.tools["exec_shell"].successes, 1);
assert_eq!(rollup.tools["exec_shell"].failures, 1);
// The unexecuted receipt counts as neither success nor failure:
// counting it as a success would inflate the tool's real execution
// rate. Deleting the `executed` consult turns this pin red.
assert_eq!(rollup.tools["deferred_tool"].calls, 1);
assert_eq!(rollup.tools["deferred_tool"].successes, 0);
assert_eq!(rollup.tools["deferred_tool"].failures, 0);
assert_eq!(rollup.tools["other_tool"].calls, 1);
assert_eq!(rollup.tools["other_tool"].successes, 1);
}

#[test]
fn total_tool_calls_sums_across_tools() {
let mut rollup = Rollup::default();
Expand Down
49 changes: 49 additions & 0 deletions crates/tui/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6402,6 +6402,55 @@ mod tests {
}
}

/// The live-found browser catalog shape (`evaluate_script.args = {"type":
/// "array", "items": {}}`) used to fail MFJS validation and silently
/// disappear from every Kimi request. The wire must carry the tool with
/// the empty `items` keyword repaired away, not drop it.
#[tokio::test]
async fn moonshot_keeps_empty_items_tool_on_the_wire() {
for streaming in [false, true] {
let mut request =
k3_request_fixture(crate::config::KIMI_CODE_K3_MODEL, Some("low"), streaming);
let mut browser = test_tool("mcp_browser_evaluate_script");
browser.input_schema = json!({
"type": "object",
"properties": {
"function": {"type": "string"},
"args": {"type": "array", "items": {}}
},
"required": ["function"],
"additionalProperties": true
});
request.tools = Some(vec![browser]);
request.tool_choice = Some(json!("auto"));

let body = capture_moonshot_chat_request_body(
crate::config::DEFAULT_KIMI_CODE_BASE_URL,
crate::config::KIMI_CODE_K3_MODEL,
request,
)
.await;
let tools = body["tools"]
.as_array()
.expect("the empty-items tool must stay on the wire");
assert_eq!(
tools.len(),
1,
"the repaired tool must not be dropped (streaming={streaming}): {body}"
);
assert_eq!(
tools[0]["function"]["name"],
json!("mcp_browser_evaluate_script"),
"the surviving tool must be the incident tool itself: {body}"
);
assert_eq!(
tools[0]["function"]["parameters"]["properties"]["args"],
json!({"type": "array"}),
"the empty items keyword must be repaired to absent: {body}"
);
}
}

#[tokio::test]
async fn moonshot_rejects_named_choice_for_omitted_tool() {
for streaming in [false, true] {
Expand Down
7 changes: 5 additions & 2 deletions crates/tui/src/core/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3408,8 +3408,11 @@ impl Engine {
// conversation. SyncSession installs a conversation's
// identity, history, and workspace (including the
// generated-ID new-session path), so never carry the
// previous conversation's toolbox across this edge.
// previous conversation's toolbox across this edge —
// including a parked eviction note whose batch never
// recorded a result.
self.session.tool_activation_cache.clear();
self.session.pending_toolbox_eviction_notes.clear();
let plugin_workspace_changed =
self.plugin_registry.workspace() != workspace.as_path();
let previous_session_id = self.session.id.clone();
Expand Down Expand Up @@ -8832,7 +8835,7 @@ use self::streaming::{
sleep_gap_detected, stream_read_error_user_message,
};
use self::tool_catalog::{
CODE_EXECUTION_TOOL_NAME, JS_EXECUTION_TOOL_NAME, MULTI_TOOL_PARALLEL_NAME,
CODE_EXECUTION_TOOL_NAME, DeferredHydration, JS_EXECUTION_TOOL_NAME, MULTI_TOOL_PARALLEL_NAME,
REQUEST_USER_INPUT_NAME, ToolSurfacePolicy, active_tools_for_request,
apply_registry_first_shell_guidance, build_model_tool_catalog_with_surface,
default_synthetic_catalog_tool_names, execute_code_execution_tool, is_tool_search_tool,
Expand Down
Loading
Loading