Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/agent/director.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1901,6 +1901,9 @@ describe("chatDirector LSP auto-activation", () => {
test.each([
{ tool: "read_file", path: "src/foo.ts", expected: [lspEmit] },
{ tool: "edit_file", path: "lib/bar.rs", expected: [lspEmit] },
// Wire names the model actually emits; the director must resolve them.
{ tool: "read", path: "src/foo.ts", expected: [lspEmit] },
{ tool: "edit", path: "lib/bar.rs", expected: [lspEmit] },
// Non-code file.
{ tool: "read_file", path: "README.md", expected: [] },
// Failed result never activates.
Expand Down
10 changes: 6 additions & 4 deletions src/agent/director.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { canonicalToolName } from "./canonical-tool-name.js";
import {
DefaultDirector,
type ExtendedInferenceOptions,
Expand Down Expand Up @@ -446,7 +447,7 @@ function applyManageTasksToolCall(
tasks: Task[],
block: { name: string; arguments: unknown },
): Task[] | null {
if (block.name !== "manage_tasks") return null;
if (canonicalToolName(block.name) !== "manage_tasks") return null;
const taskArgs = parseManageTasksArgs(block.arguments);
return taskArgs !== null ? applyManageTasks(tasks, taskArgs) : null;
}
Expand Down Expand Up @@ -1173,7 +1174,8 @@ class ChatDirectorImpl extends DefaultDirector {
}
for (const block of event.turn.content) {
if (block.type !== "tool_call") continue;
if (block.name === "manage_tasks") {
const engineName = canonicalToolName(block.name);
if (engineName === "manage_tasks") {
const next = applyManageTasksToolCall(this.tasks, block);
if (next !== null) {
this.tasks = next;
Expand All @@ -1183,7 +1185,7 @@ class ChatDirectorImpl extends DefaultDirector {
}),
);
}
} else if (block.name === "read_file" || block.name === "edit_file") {
} else if (engineName === "read_file" || engineName === "edit_file") {
const pathResult = PathArgSchema(block.arguments);
const path = pathResult instanceof type.errors ? "" : pathResult.path;
if (isCodeFile(path)) this.lspTriggerCalls.add(block.id);
Expand All @@ -1194,7 +1196,7 @@ class ChatDirectorImpl extends DefaultDirector {
args: block.arguments,
});
}
if (block.name === "ask_operator") {
if (engineName === "ask_operator") {
this.askOperatorCalls.add(block.id);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/subagent/poll-exempt.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { canonicalToolName } from "../agent/canonical-tool-name.js";
import { type } from "arktype";
import type { ToolCall, ToolResult } from "@intx/types/runtime";
import { isLiveWaitStatus, type WaitJSONStatus } from "./lifecycle.js";
Expand Down Expand Up @@ -50,12 +51,13 @@ export function isPollOnlyPendingBatch(
return calls.every((call, index) => {
const result = results[index];
if (result === undefined) return false;
if (call.name !== "wait_agents" && call.name !== "shell_collect") {
const name = canonicalToolName(call.name);
if (name !== "wait_agents" && name !== "shell_collect") {
return false;
}
const payload = resultPayload(result);
if (payload === undefined) return false;
return call.name === "wait_agents"
return name === "wait_agents"
? isWaitAgentsPending(payload)
: isShellCollectPending(payload);
});
Expand Down
3 changes: 2 additions & 1 deletion src/tui/mcp-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* for the pagination cursor.
*/

import { canonicalToolName } from "../agent/canonical-tool-name.js";
import {
fg as fgChunk,
bold as boldChunk,
Expand Down Expand Up @@ -501,7 +502,7 @@ const USE_SKILL_TOOL = "use_skill";
* future wording) simply does not collapse.
*/
function loadedSkillName(name: string, content: string): string | undefined {
if (name !== USE_SKILL_TOOL) return undefined;
if (canonicalToolName(name) !== USE_SKILL_TOOL) return undefined;
return /^Skill "([^"]+)"/.exec(content)?.[1];
}

Expand Down
7 changes: 5 additions & 2 deletions src/tui/runtime-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* later waves can bind real agent APIs). Not the production CLI entry.
*/

import { canonicalToolName } from "../agent/canonical-tool-name.js";
import {
cancelItem,
createSessionQueue,
Expand Down Expand Up @@ -1005,8 +1006,9 @@ export function flushStreamRowUpdates(shell: AppShell): void {
function applyToolCall(
shell: AppShell,
bag: BridgeBag,
event: Extract<BridgeInboundEvent, { type: "tool_call" }>,
raw: Extract<BridgeInboundEvent, { type: "tool_call" }>,
): void {
const event = { ...raw, name: canonicalToolName(raw.name) };
if (event.name === MANAGE_TASKS_TOOL_NAME) {
// Remembered so the matching result is dropped too — suppressing only the
// call would leave its result to land as an unpaired row. Checklist lives
Expand Down Expand Up @@ -1057,8 +1059,9 @@ function applyToolCall(
function applyToolResult(
shell: AppShell,
bag: BridgeBag,
event: Extract<BridgeInboundEvent, { type: "tool_result" }>,
raw: Extract<BridgeInboundEvent, { type: "tool_result" }>,
): void {
const event = { ...raw, name: canonicalToolName(raw.name) };
if (event.callId !== undefined && bag.panelOnlyCallIds.delete(event.callId))
return;
const result = toolResultRow({
Expand Down
9 changes: 6 additions & 3 deletions src/tui/stall-watchdog.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { canonicalToolName } from "../agent/canonical-tool-name.js";
import type { TurnStatus } from "./chrome-state.js";

// How long the run can be continuously awaiting a response with no new content
Expand Down Expand Up @@ -74,10 +75,12 @@ function silentPastThreshold(
* mapping-owning sibling resolves first and clears the shared slot.
*/
function isStallBoundedToolName(name: string | null | undefined): boolean {
if (name === null || name === undefined) return false;
const engine = canonicalToolName(name);
return (
name === "shell_collect" ||
name === "wait_agents" ||
name === "ask_director"
engine === "shell_collect" ||
engine === "wait_agents" ||
engine === "ask_director"
);
}

Expand Down
11 changes: 7 additions & 4 deletions src/tui/tool-execution-watchdog.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { canonicalToolName } from "../agent/canonical-tool-name.js";
import { AsyncLocalStorage } from "node:async_hooks";
import {
formatMcpToolTimeoutMessage,
Expand Down Expand Up @@ -112,13 +113,15 @@ export function resolveToolExecutionTimeoutMs(
config?: ToolWatchdogConfig,
call?: ToolCall,
): number | undefined {
const engineName =
call !== undefined ? canonicalToolName(call.name) : undefined;
if (
call?.name === "spawn_agent" ||
call?.name === "wait_agents" ||
call?.name === "ask_director" ||
engineName === "spawn_agent" ||
engineName === "wait_agents" ||
engineName === "ask_director" ||
// shell_collect with wait_ms is a capped poll over a background shell that
// outlives the turn; aborting the collect would not stop the process.
call?.name === "shell_collect"
engineName === "shell_collect"
) {
return undefined;
}
Expand Down
7 changes: 6 additions & 1 deletion src/tui/turns-to-blocks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { canonicalToolName } from "../agent/canonical-tool-name.js";
import type {
ContentBlock as RuntimeContentBlock,
ConversationTurn,
Expand Down Expand Up @@ -165,7 +166,11 @@ function finalizeResumeToolBlocks(
// so an errored or missing result must not leave the raw rows behind.
for (let i = 0; i < blocks.length; i += 1) {
const call = blocks[i];
if (call?.type !== "tool_call" || call.name !== "manage_tasks") continue;
if (
call?.type !== "tool_call" ||
canonicalToolName(call.name) !== "manage_tasks"
)
continue;
indicesToRemove.add(i);
const resultIndex =
call.callId !== undefined
Expand Down
Loading