diff --git a/packages/coding-agent/src/modes/components/tool-execution.ts b/packages/coding-agent/src/modes/components/tool-execution.ts index 3910fd9b9..b3141996e 100644 --- a/packages/coding-agent/src/modes/components/tool-execution.ts +++ b/packages/coding-agent/src/modes/components/tool-execution.ts @@ -196,7 +196,7 @@ export const SPINNER_GLYPH_ADVANCE_MS = 80; /** Phase-locked spinner glyph index shared by every live tool block so parallel * spinners advance in lockstep instead of each tracking its own start time. */ -export function sharedSpinnerFrame(frameCount: number, now: number = performance.now()): number { +export function sharedSpinnerFrame(frameCount: number, now: number = Date.now()): number { return frameCount > 0 ? Math.floor(now / SPINNER_GLYPH_ADVANCE_MS) % frameCount : 0; } @@ -620,10 +620,10 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac // If a detached task interval from an older render path is still live, // stop it the instant the block leaves the repaintable region. if (this.#maybeFreezeBackgroundTask()) return; - const now = performance.now(); const frameCount = theme.spinnerFrames.length; - this.#spinnerFrame = sharedSpinnerFrame(frameCount, now); + this.#spinnerFrame = frameCount > 0 ? ((this.#spinnerFrame ?? 0) + 1) % frameCount : 0; this.#renderState.spinnerFrame = this.#spinnerFrame; + this.#updateDisplay(); // Component-scoped: a spinner tick only changes this tool block, so // the TUI reuses every other root subtree instead of walking the // whole tree (issue #4377). diff --git a/packages/coding-agent/src/prompts/tools/eval.md b/packages/coding-agent/src/prompts/tools/eval.md index 8ba73935c..b77603a39 100644 --- a/packages/coding-agent/src/prompts/tools/eval.md +++ b/packages/coding-agent/src/prompts/tools/eval.md @@ -18,7 +18,7 @@ env(key?=None, value?=None) → str | None | dict output(*ids, format?="raw", query?=None, offset?=None, limit?=None) → str | dict | list[dict] tool.(args) → unknown completion(prompt, model?="default"|"smol"|"slow", system?=None, schema?=None) → str | dict -{{#if spawns}}agent(prompt, agent?="{{spawnDefaultAgent}}", model?=None, schema?=None, handle?=False) → str | dict{{#if spawnAllowedAgentsText}} Allowed: {{spawnAllowedAgentsText}}.{{/if}} +{{#if spawns}}agent(prompt, agent?="{{spawnDefaultAgent}}", model?=None, schema?=None, handle?=False) → str | dict{{#if spawnAllowedAgentsText}} Allowed agents: {{spawnAllowedAgentsText}}. (omit it to use `{{spawnDefaultAgent}}`){{/if}} {{#if js}} JS: agent(prompt, { agent, schema, handle }).{{/if}} {{/if}} parallel(thunks) → list pipeline(items, ...stages) → list diff --git a/packages/coding-agent/src/prompts/tools/task.md b/packages/coding-agent/src/prompts/tools/task.md index d41cf71e3..c111e1f62 100644 --- a/packages/coding-agent/src/prompts/tools/task.md +++ b/packages/coding-agent/src/prompts/tools/task.md @@ -4,7 +4,7 @@ Execution does not block — you receive an ID immediately; the result delivers Agents marked BLOCKING run inline — results return in this call; non-blocking items in the same batch still spawn as background jobs.{{/if}}{{else}}{{#if batchEnabled}}Run subagents synchronously by passing items in a `tasks[]` batch. Execution blocks until all work finishes.{{else}}Run ONE subagent synchronously. Execution blocks until work finishes.{{/if}}{{/if}} # Task Design -- **Agent typing:** Pick each item's `agent` type. Read-only research MUST use `agent: "scout"` (faster model). Use default worker only when no specialist fits. +- **Agent typing:** Pick each item's `agent` type. Read-only research MUST use `agent: "scout"` (faster model). Use the general-purpose worker (`{{defaultAgent}}`) only when no specialist fits. - **No overhead:** Each `task` MUST instruct its agent to skip formatters, linters, and project-wide test suites. Run those once at the end. - **One-pass:** Prefer agents that investigate AND edit in one pass; spin a read-only scout only when affected files are genuinely unknown. @@ -30,6 +30,8 @@ Agents marked BLOCKING run inline — results return in this call; non-blocking # Communication Subagents start blank — no conversation history.{{#if ircEnabled}} Parent-to-subagent IRC delivered immediately as steering.{{/if}} Pass large payloads via `local://` URIs, NEVER inline text. +{{#if spawnAllowedAgentsText}}Current spawn policy allows: {{spawnAllowedAgentsText}}. +{{/if}} # Format Contracts {{#if batchEnabled}} diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index 966fba92e..4244afd73 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -1741,6 +1741,8 @@ export class AgentSession { * trigger gate: the switch fires at the first edit/write AFTER the todo * list exists (sessions without an ACTIVE todo tool skip the gate). */ #prewalkTodoSeen = false; + /** True once a text-only turn has consumed its continuation until tool progress re-arms it. */ + #prewalkContinuationIssued = false; #planYolo: PlanYolo | undefined; #planYoloPreviousTools: string[] | undefined; #planYoloArmed = false; @@ -2238,7 +2240,8 @@ export class AgentSession { // silently killing production SWE-bench runs before any code was ever // written. Force one more turn only in that specific, self-created // hazard window. - if (this.#prewalkPlanInjected && context.toolResults.length === 0) { + if (context.toolResults.length > 0) this.#prewalkContinuationIssued = false; + if (this.#prewalkPlanInjected && context.toolResults.length === 0 && !this.#prewalkContinuationIssued) { this.agent.steer({ role: "custom", customType: PREWALK_CONTINUE_MESSAGE_TYPE, @@ -2247,6 +2250,7 @@ export class AgentSession { display: false, timestamp: Date.now(), }); + this.#prewalkContinuationIssued = true; } // Todo gate: the plan nudge instructs "finish the plan, then init the @@ -2257,6 +2261,9 @@ export class AgentSession { // ACTIVE tool set, not the registry: a registered-but-deactivated todo // (e.g. a restricted active-tool slate) is uncallable and would // deadlock the switch. + if (context.toolResults.some(result => result.toolName === "todo")) { + this.#prewalkTodoSeen = true; + } const todoGateOpen = this.#prewalkTodoSeen || !this.getActiveToolNames().includes("todo"); const action = todoGateOpen ? context.toolResults.find(result => PREWALK_ACTION_TOOLS[result.toolName]) diff --git a/packages/coding-agent/src/task/index.ts b/packages/coding-agent/src/task/index.ts index 2e787c297..1b24442dd 100644 --- a/packages/coding-agent/src/task/index.ts +++ b/packages/coding-agent/src/task/index.ts @@ -202,6 +202,7 @@ function renderDescription( agents: renderedAgents, spawningDisabled, defaultAgent: spawnPolicy.defaultAgent, + spawnAllowedAgentsText: spawnPolicy.allowedPromptText, isolationEnabled, batchEnabled, asyncEnabled,