fix(pi-claude-cli): unblock parameterless MCP tool calls in triage
Triage with claude-sonnet-4-6 via pi-claude-cli kept looping on
fn_review_spec calls that were rejected by pi's validator with
"root: must be object". Parameterless MCP tools (schema
{type:"object", properties:{}}) emit zero input_json_delta events,
so partialJson stayed "" and the catch fell through to
finalArgs = "" — a string, which TypeBox's Type.Object({}) rightly
refuses. Default empty partialJson to {} so the call lands.
Also:
- Add a 2-step reminder loop in triage before swapping to the
fallback planning model — primary models that wrote PROMPT.md
but forgot fn_review_spec recover from a nudge, no need to pay
the cold-start tax of a new triage on a different model.
- Inject @runfusion/fusion's own pi extension into dashboard/
daemon/serve sessions and propagate the path to createFnAgent
via setHostExtensionPaths so fn_* tools register globally
without requiring `pi install npm:@runfusion/fusion`.
- Drop the "historical" qualifier from replayed tool labels —
Claude was reading "TOOL RESULT (historical Read):" as
"previous session, ignore" and looping on verification.
- Remove subprocess-lifecycle stderr debug logs that landed for
hang diagnosis — root cause is fixed, the noise can go.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -323,13 +323,24 @@ export function createEventBridge(
|
||||
partial: output,
|
||||
});
|
||||
} else if (block.type === "tool_use") {
|
||||
// Final JSON parse with fallback to raw string
|
||||
// Final JSON parse with fallback to raw string.
|
||||
// Special case: parameterless MCP tools (e.g. fn_review_spec, schema
|
||||
// `{type:"object", properties:{}}`) emit ZERO input_json_delta events,
|
||||
// so `partialJson` stays "". Without this guard we'd JSON.parse("")
|
||||
// → throw → fall through to `finalArgs = ""` (raw string), and pi's
|
||||
// TypeBox validator then rejects with "root: must be object" because
|
||||
// an empty string is not an object. Default to `{}` so the call lands.
|
||||
let finalArgs: Record<string, unknown> | string;
|
||||
try {
|
||||
const parsed = JSON.parse(block.partialJson);
|
||||
finalArgs = translateClaudeArgsToPi(block.claudeName, parsed);
|
||||
} catch {
|
||||
finalArgs = block.partialJson;
|
||||
const trimmedJson = block.partialJson.trim();
|
||||
if (trimmedJson === "") {
|
||||
finalArgs = {};
|
||||
} else {
|
||||
try {
|
||||
const parsed = JSON.parse(trimmedJson);
|
||||
finalArgs = translateClaudeArgsToPi(block.claudeName, parsed);
|
||||
} catch {
|
||||
finalArgs = block.partialJson;
|
||||
}
|
||||
}
|
||||
|
||||
// Update output.content with final arguments
|
||||
|
||||
Reference in New Issue
Block a user