Skip to content

Commit 1e2b983

Browse files
authored
🤖 feat: add advisor same-step context handoff (#3168)
## Summary Adds same-step context handoff to the advisor tool so the nested advisor model call understands **why it was invoked at that exact moment**, not just the prior transcript. ## Background The advisor tool previously sent only the full conversation transcript to the nested model. While this gives the advisor the overall context, it had no information about: - **What question** the caller model wants answered - **What reasoning/commentary** the caller was producing in the current step that led to the advisor call - **What the pending tool call looks like** This gap meant the advisor couldn't distinguish "why was I called now?" from "what happened in the conversation so far?" ## Implementation Three layered changes, each building on the previous: ### Phase 1: Advisor tool contract (`question` field) - Extended `AdvisorToolInputSchema` to accept an optional `question: z.string().min(1).nullish()` field - Updated `ADVISOR_TOOL_DESCRIPTION` to instruct the caller to pass a brief question - Added normalization in `execute()` (trim, empty→undefined) - Renders the question in `AdvisorToolCall.tsx` when present ### Phase 2: Same-step context capture - Added `AdvisorToolCallSnapshot` and `AdvisorStepCaptureRef` types - Extended `StreamRequestConfig` with an `onChunk` callback, threaded through to `streamText()` - In `aiService.ts`, wired `onChunk` to accumulate `text-delta` → `currentStepText` and `reasoning-delta` → `currentStepReasoning` - When a `tool-call` chunk with `toolName === "advisor"` arrives, freezes a snapshot keyed by `toolCallId` - `prepareStep` resets per-step buffers to prevent cross-step leakage - Exposed `takeToolCallSnapshot(toolCallId)` with consume-on-read semantics ### Phase 3: Handoff composition - In `advisor.ts execute()`, retrieves the frozen snapshot and builds a synthetic `user` handoff message - Appended as the final message: `[...transcript, handoffMessage]` - Sections included only when content exists: `Question`, `Current-step commentary`, `Current-step reasoning`, `Pending tool call` - Tail-biased truncation for same-step text/reasoning (configurable via `ADVISOR_HANDOFF_MAX_TEXT_CHARS` / `ADVISOR_HANDOFF_MAX_REASONING_CHARS`) - Falls back to raw transcript when no question/snapshot exists ## Validation - `make typecheck` ✅ - `make lint` ✅ - All targeted tests pass: `advisor.test.ts`, `streamManager.test.ts`, `aiService.test.ts`, `toolDefinitions.test.ts`, `AdvisorToolCall.test.tsx` - **Live dogfooding** via dev-server sandbox: - Triggered a real advisor tool call with the experiment flag enabled - Verified in the Debug tab that the nested advisor request contains the synthetic `## Advisor Handoff` user message with `**Question:**` and `**Pending tool call:**` sections - Captured screenshots and video recordings as evidence ## Risks - **Context bloat**: Mitigated by tail-biased truncation with configurable max char limits - **Multiple advisor calls per step**: Mitigated by per-`toolCallId` snapshot keying in a `Map` - **Rollout safety**: `question` field is optional; existing `{}` calls still work --- <details> <summary>📋 Implementation Plan</summary> # Advisor same-step context handoff implementation plan ## Goal Make the nested `advisor` model call understand **why it was invoked at that exact moment** without dropping the existing full-transcript context. The implementation should keep the current full transcript, then add a **structured advisor handoff** that captures the immediate same-step rationale: 1. the explicit question the caller wants answered 2. any visible same-step commentary/text emitted before the tool call 3. any visible same-step reasoning emitted before the tool call 4. the exact pending `advisor` tool call snapshot keyed by `toolCallId` ## Non-goals - Do **not** attempt to forward raw hidden reasoning / chain-of-thought tokens. - Do **not** patch or fork the AI SDK's `ToolExecutionOptions` shape. - Do **not** replace the full transcript with a compact handoff; the handoff is additive. ## Verified evidence driving this plan - `src/node/services/tools/advisor.ts` sends `messages: transcript` into `generateText()` and currently rejects all tool args. - `src/node/services/aiService.ts` populates `advisorTranscriptRef.messages` from the live stream via `prepareStep`. - `src/node/services/streamManager.ts` forwards only the **pre-step** message transcript today (`prepareStep({ messages })`). - `node_modules/@ai-sdk/provider-utils/src/types/tool.ts` documents that tool execution `messages` exclude the assistant response that contained the tool call. - `node_modules/ai/src/generate-text/stream-text.ts` and `run-tools-transformation.ts` show the AI SDK already exposes the right primitives for a better handoff: - `prepareStep` for per-step reset / input transcript - `onChunk` for same-step `text-delta`, `reasoning-delta`, and `tool-call` - parsed `tool-call` chunks with `toolCallId`, `toolName`, and `input` - `src/browser/features/RightSidebar/DevToolsTab/DevToolsStepCard.tsx` already renders the raw AI SDK request JSON, so if we append a synthetic handoff message to the advisor request it should become visible in Debug Logs without extra devtools plumbing. ## Decision summary ### Recommended approach: full transcript + frozen same-step snapshot + synthetic final handoff message Keep the advisor request shape conceptually as: ```ts generateText({ model, system: ADVISOR_SYSTEM_PROMPT, messages: [ ...fullTranscript, syntheticAdvisorHandoffMessage, ], tools: {}, }); ``` Where `syntheticAdvisorHandoffMessage` is a final **`user`** message derived from the current advisor invocation, for example: ```ts { role: "user", content: [ "Advisor handoff", `Question: ${question}`, `Current-step commentary: ${stepTextExcerpt}`, `Current-step reasoning: ${stepReasoningExcerpt}`, `Pending tool call: advisor(${serializedArgs})`, ].join("\n\n"), } ``` This keeps the base transcript intact while making the immediate consultation request explicit and inspectable. ## Approaches considered | Approach | Summary | Product net LoC estimate* | Recommendation | |---|---|---:|---| | A | Add `question` only and append a simple handoff message | 40–80 | Good fallback / rollout step, but incomplete | | B | **Recommended:** add `question`, capture same-step visible context, append structured handoff | 140–230 | **Primary path** | | C | Inject custom step context by patching AI SDK tool execution options | 220–400 | Avoid | \*Product-code estimate only; tests/docs/dogfooding artifacts excluded. <details> <summary>Why Approach B wins</summary> - Solves the actual gap: the advisor learns not only the whole transcript, but also the immediate rationale for the current consultation. - Uses supported AI SDK hooks instead of patching internal tool execution contracts. - Produces a handoff that is readable in Debug Logs and easy to reason about in tests. - Can fall back safely when same-step text/reasoning or `question` is missing. </details> ## Planned implementation ## Phase 1 — Extend the advisor tool contract (safe rollout) ### Changes 1. Update `src/common/utils/tools/toolDefinitions.ts` - Change `AdvisorToolInputSchema` from empty-object-only to a staged rollout schema: - `question: z.string().min(1).nullish()` - Keep the field optional for the first rollout so existing model behavior does not hard-fail if the model still emits `{}`. - Update `TOOL_DEFINITIONS.advisor.description` / `ADVISOR_TOOL_DESCRIPTION` to explicitly instruct the caller to pass a brief question summarizing the decision or ambiguity. 2. Update `src/node/services/tools/advisor.ts` - Replace the current `assert(Object.keys(args).length === 0)` with validation that accepts the staged `question` field. - Normalize `question` defensively: - trim whitespace - treat empty / whitespace-only strings as absent 3. Optional but recommended UI polish in `src/browser/features/Tools/AdvisorToolCall.tsx` - Display `args.question` when present so advisor invocations are self-describing in the chat transcript. ### Why this phase exists This gives the parent model an explicit way to say what it wants help with, even before same-step capture lands. ### Quality gate after Phase 1 - Targeted unit tests around the advisor schema / tool execution pass. - Existing empty-args advisor calls still work. - Tool description clearly nudges the model to supply `question`. ## Phase 2 — Capture same-step visible context at the tool-call boundary ### Snapshot model Add an internal advisor-only capture shape along these lines: ```ts type AdvisorToolCallSnapshot = { toolCallId: string; toolName: "advisor"; input: { question?: string | null }; stepText: string; stepReasoning: string; }; ``` And a mutable per-stream capture ref in `AIService`: ```ts type AdvisorStepCaptureRef = { currentStepText: string; currentStepReasoning: string; frozenSnapshotsByToolCallId: Map<string, AdvisorToolCallSnapshot>; }; ``` ### Changes 1. Extend `src/common/utils/tools/tools.ts` - Add advisor runtime accessors that let the tool read and consume a frozen snapshot by `toolCallId`, for example: - `takeToolCallSnapshot(toolCallId: string): AdvisorToolCallSnapshot | undefined` 2. Update `src/node/services/streamManager.ts` - Extend `StreamRequestConfig` with an optional chunk callback (e.g. `onChunk?: (part) => void`). - In `createStreamResult()`, pass the callback through to `streamText({ onChunk })`. - Leave existing `prepareStep` behavior intact. 3. Update `src/node/services/aiService.ts` - Add a new mutable ref beside `advisorTranscriptRef` for same-step capture. - Reset `currentStepText`, `currentStepReasoning`, and any stale frozen snapshots when `prepareStep` starts a new step. - In the new chunk callback: - append `text-delta` to `currentStepText` - append `reasoning-delta` to `currentStepReasoning` - when a parsed `tool-call` chunk for `toolName === "advisor"` arrives, freeze a snapshot into `frozenSnapshotsByToolCallId` keyed by `toolCallId` ### Defensive programming requirements - Freeze snapshots **per `toolCallId`**, not globally, so multiple advisor calls in one step cannot overwrite each other. - Clone the current strings when freezing; do not keep shared mutable references inside the frozen snapshot. - Consume and delete the frozen snapshot once `execute()` reads it. - Reset per-step buffers in `prepareStep` so data cannot leak across steps. - Assert that any snapshot returned for a tool call either matches `toolName === "advisor"` or is discarded. ### Why this phase exists This is the key fix for the current gap. It captures the same-step rationale **at the moment the advisor tool call is parsed**, which is the closest supported boundary to “why did the parent model decide to call advisor?” ### Quality gate after Phase 2 - New unit/integration coverage proves: - same-step text and reasoning accumulate during a step - advisor snapshots are frozen when the advisor tool call is parsed - two advisor tool calls in the same step do not clobber each other - step resets clear stale buffers ## Phase 3 — Compose the advisor handoff request ### Changes 1. Update `src/node/services/tools/advisor.ts` - Keep `const transcript = runtime.getTranscriptSnapshot()` as the base context. - Retrieve the frozen snapshot with `runtime.takeToolCallSnapshot(toolCallId)`. - Build a final synthetic `user` handoff message using: - normalized `question` (preferred explicit ask) - same-step commentary text excerpt - same-step reasoning excerpt - pending tool-call summary (serialized advisor args) - Call the nested advisor model with: - `messages: [...transcript, syntheticHandoffMessage]` when any handoff content exists - otherwise fall back to `messages: transcript` 2. Add truncation / formatting constants (prefer colocated shared constants rather than magic numbers) - Example constants to add near advisor constants: - `ADVISOR_HANDOFF_MAX_TEXT_CHARS` - `ADVISOR_HANDOFF_MAX_REASONING_CHARS` - Use **tail-biased** truncation so the advisor sees the most recent commentary/reasoning closest to the tool call. 3. Keep `ADVISOR_SYSTEM_PROMPT` mostly static - Do not overload the system prompt with runtime question/context. - If needed, add a small static instruction explaining that a final user handoff message may summarize the immediate consultation request. ### Formatting rules for the synthetic handoff - Prefer a deterministic labeled block so tests and Debug Logs stay legible. - Include only sections that have content. - Suggested section order: 1. `Question` 2. `Current-step commentary` 3. `Current-step reasoning` 4. `Pending tool call` - If both question and same-step context are missing, skip the synthetic handoff entirely. ### Why `user` message over dynamic system prompt augmentation - Keeps runtime context visible in raw request logs. - Preserves a stable system prompt. - Avoids mixing permanent instructions with per-invocation consultation context. - Makes replay/debugging easier because the “why now?” handoff is explicit in the message list. ### Quality gate after Phase 3 - Advisor tests verify the nested `generateText()` call still receives the full transcript. - When a question/snapshot exists, the final request contains one appended handoff message. - When no question/snapshot exists, behavior falls back cleanly to today’s transcript-only request. ## Phase 4 — Test coverage and regression protection ### Targeted tests 1. `src/node/services/tools/advisor.test.ts` - transcript-only fallback still works - question-only handoff appends one final message - snapshot + question handoff appends the expected labeled sections - snapshot is consumed once and not reused accidentally 2. `src/node/services/streamManager.test.ts` and/or nearby focused tests - new chunk callback plumbing fires for `text-delta`, `reasoning-delta`, and `tool-call` - advisor snapshots freeze at the tool-call boundary - multiple advisor tool calls in one step keep separate snapshots 3. If needed, add a focused `AIService`-level test around the new advisor runtime refs - prove `prepareStep` reset + chunk accumulation + snapshot freeze all work together 4. `src/browser/features/Tools/AdvisorToolCall.test.tsx` (if Phase 1 UI polish lands) - renders `question` when present ### Validation commands Run at minimum: - targeted advisor tests - targeted stream manager / AI service tests covering the new capture path - targeted browser test if `AdvisorToolCall.tsx` changes - `make typecheck` - `make lint` If the touched test surface is small, keep unit coverage targeted first, then run the broader static checks. ## Acceptance criteria - The nested advisor request still includes the full transcript captured for the parent step. - The advisor request includes a final explicit handoff message whenever there is a question and/or same-step context to hand off. - The handoff message is visible in Debug Logs’ raw AI SDK request view. - The handoff explains why the advisor was called in terms of: - explicit question when available - visible same-step commentary when available - visible same-step reasoning when available - pending advisor tool call summary - Snapshot capture is keyed by `toolCallId`, not a single global mutable snapshot. - Missing question or missing same-step context does not break the advisor tool; it falls back gracefully. - No other tool execution path regresses. ## Risks and mitigations - **Risk: context bloat** - Mitigation: cap same-step text/reasoning with explicit constants and tail-biased truncation. - **Risk: multiple advisor calls in one step** - Mitigation: freeze snapshots into a `Map<toolCallId, snapshot>` and consume per tool call. - **Risk: same-step commentary is often empty** - Mitigation: make the handoff resilient; question + pending tool-call summary are still valuable. - **Risk: stale snapshot leakage across steps or retries** - Mitigation: reset buffers at `prepareStep`, consume snapshots on execute, and clear stale maps defensively on step resets. - **Risk: rollout breakage if the model still emits `{}`** - Mitigation: keep `question` optional in the first rollout, then consider tightening later once dogfooding shows consistent usage. ## Deferred follow-ups (not part of the first implementation) - Tighten `question` from optional to required after rollout confidence. - Add prompt-side guidance encouraging a short preamble before advisor calls if dogfooding shows that current-step commentary is usually empty. - Add richer devtools affordances only if the raw request view still feels too opaque after the explicit handoff lands. ## Dogfooding and proof plan ### Setup 1. Run the desktop app locally (`make dev` or the project’s normal Electron dev workflow). 2. Open a scratch workspace where the model is likely to call `advisor` (plan mode or an ambiguous architecture task works well). 3. Use the `electron` skill (preferred) or equivalent browser automation against the running app to drive the scenario reproducibly. ### Dogfood scenario 1. Trigger a prompt likely to cause an advisor consultation. 2. Wait for the parent step to emit reasoning / commentary and then call `advisor`. 3. Open Debug Logs and inspect: - the parent step output - the nested advisor step request in raw AI SDK view 4. Verify the advisor request contains: - the prior transcript - the final handoff message with the expected sections 5. Verify the advisor response is more clearly targeted to the explicit question / current dilemma than before. ### Required reviewer evidence Capture and attach: 1. **Screenshot A** — parent step showing the reasoning/commentary that led to the advisor call 2. **Screenshot B** — advisor nested request JSON showing the appended handoff message 3. **Screenshot C** — rendered advisor tool call card showing the `question` (if UI polish lands) 4. **Short video recording** — end-to-end flow from prompt → advisor invocation → Debug Logs inspection ### Dogfood quality gates - After Phase 2: verify the advisor request still lacks the final handoff (expected intermediate state) but internal tests prove the snapshot is frozen correctly. - After Phase 3: repeat the same scenario and confirm the raw advisor request now contains the explicit handoff. - Before sign-off: collect screenshots/video and confirm they match the acceptance criteria above. </details> --- _Generated with `mux` • Model: `anthropic:claude-opus-4-6` • Thinking: `xhigh` • Cost: `$39.11`_ <!-- mux-attribution: model=anthropic:claude-opus-4-6 thinking=xhigh costs=39.11 -->
1 parent 7b1f94c commit 1e2b983

14 files changed

Lines changed: 848 additions & 15 deletions

File tree

docs/hooks/tools.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,15 @@ If a value is too large for the environment, it may be omitted (not set). Mux al
217217

218218
{/* BEGIN TOOL_HOOK_ENV_VARS */}
219219

220+
<details>
221+
<summary>advisor (1)</summary>
222+
223+
| Env var | JSON path | Type | Description |
224+
| ------------------------- | ---------- | ------ | ----------- |
225+
| `MUX_TOOL_INPUT_QUESTION` | `question` | string ||
226+
227+
</details>
228+
220229
<details>
221230
<summary>agent_report (2)</summary>
222231

src/browser/features/Tools/AdvisorToolCall.test.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,26 @@ describe("AdvisorToolCall", () => {
111111
expect(timers[0]?.dataset.active).toBe("true");
112112
});
113113

114+
test("renders the advisor question when present", () => {
115+
useAdvisorToolLivePhaseMock.mockReturnValue(undefined);
116+
117+
const view = renderAdvisorToolCall({
118+
args: { question: " Should we split the refactor into smaller commits? " },
119+
status: "completed",
120+
result: {
121+
type: "advice",
122+
advice: "Prefer the smaller diff so reviewers can verify it quickly.",
123+
advisorModel: "openai:gpt-4.1-mini",
124+
remainingUses: 1,
125+
},
126+
});
127+
128+
fireEvent.click(view.getByText("advisor"));
129+
130+
expect(view.getByText("Question")).toBeTruthy();
131+
expect(view.getByText("Should we split the refactor into smaller commits?")).toBeTruthy();
132+
});
133+
114134
test("continues rendering completed advice results", () => {
115135
useAdvisorToolLivePhaseMock.mockReturnValue(undefined);
116136

src/browser/features/Tools/AdvisorToolCall.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ function isNonEmptyString(value: unknown): value is string {
8787
return typeof value === "string" && value.trim().length > 0;
8888
}
8989

90+
function getAdvisorQuestion(args: Record<string, unknown>): string | undefined {
91+
const rawQuestion = args.question;
92+
if (rawQuestion == null || typeof rawQuestion !== "string") {
93+
return undefined;
94+
}
95+
96+
const question = rawQuestion.trim();
97+
return question.length > 0 ? question : undefined;
98+
}
99+
90100
function isRemainingUses(value: unknown): value is number | null {
91101
return value === null || (typeof value === "number" && Number.isInteger(value) && value >= 0);
92102
}
@@ -203,7 +213,7 @@ const AdvisorMetadata: React.FC<{ advisorModel: string; reasoningLevel?: string
203213
};
204214

205215
export const AdvisorToolCall: React.FC<AdvisorToolCallProps> = ({
206-
args: _args,
216+
args,
207217
result,
208218
status,
209219
workspaceId,
@@ -213,6 +223,7 @@ export const AdvisorToolCall: React.FC<AdvisorToolCallProps> = ({
213223
const { expanded, toggleExpanded } = useToolExpansion();
214224
const toolStatus = isToolStatus(status) ? status : "pending";
215225
const livePhase = useAdvisorToolLivePhase(workspaceId, toolCallId);
226+
const question = getAdvisorQuestion(args);
216227
const advisorResult = isAdvisorToolResult(result) ? result : null;
217228
const hasUnrecognizedResult = result !== undefined && result !== null && advisorResult === null;
218229
const detailsText =
@@ -255,6 +266,13 @@ export const AdvisorToolCall: React.FC<AdvisorToolCallProps> = ({
255266

256267
{expanded && (
257268
<ToolDetails text={detailsText}>
269+
{question && (
270+
<DetailSection>
271+
<DetailLabel>Question</DetailLabel>
272+
<DetailContent className="text-secondary px-2 py-1.5">{question}</DetailContent>
273+
</DetailSection>
274+
)}
275+
258276
{advisorResult?.type === "advice" && (
259277
<>
260278
<AdvisorMetadata

src/common/constants/advisor.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
/** Default per-turn usage cap for the experimental advisor tool. */
22
export const ADVISOR_DEFAULT_MAX_USES_PER_TURN = 3;
33

4+
/** Tail-biased truncation budget for same-step commentary included in advisor handoffs. */
5+
export const ADVISOR_HANDOFF_MAX_TEXT_CHARS = 4000;
6+
7+
/** Tail-biased truncation budget for same-step reasoning included in advisor handoffs. */
8+
export const ADVISOR_HANDOFF_MAX_REASONING_CHARS = 4000;
9+
410
/**
511
* Shared guidance for when the advisor tool is appropriate.
612
* Reused by the tool description now and future system-prompt wiring later.
@@ -12,7 +18,8 @@ export const ADVISOR_USAGE_GUIDANCE =
1218
/** Description shown to the model when the advisor tool is registered. */
1319
export const ADVISOR_TOOL_DESCRIPTION =
1420
"Ask a stronger model for strategic advice based on the live conversation transcript. " +
15-
ADVISOR_USAGE_GUIDANCE;
21+
ADVISOR_USAGE_GUIDANCE +
22+
" When you call this tool, pass a brief `question` summarizing the decision or ambiguity you need help with.";
1623

1724
/**
1825
* System prompt for the nested advisor model call.
@@ -40,4 +47,5 @@ Do not narrate tool use, file inspection, or implementation steps as your own ac
4047
You may suggest user-facing wording when helpful, but keep the response addressed to the calling assistant.
4148
4249
If the current direction already looks sound, confirm it briefly and explain why.
43-
Keep the response concise and pointed.`;
50+
Keep the response concise and pointed.
51+
The final user message may contain a structured advisor handoff summarizing the immediate consultation request and same-step context.`;

src/common/utils/tools/toolDefinitions.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,20 @@ describe("TOOL_DEFINITIONS", () => {
247247
);
248248
});
249249

250+
it("accepts an optional advisor question and encourages passing one", () => {
251+
expect(TOOL_DEFINITIONS.advisor.schema.safeParse({}).success).toBe(true);
252+
expect(TOOL_DEFINITIONS.advisor.schema.safeParse({ question: null }).success).toBe(true);
253+
254+
const parsed = TOOL_DEFINITIONS.advisor.schema.safeParse({
255+
question: "Should we split this refactor into smaller commits?",
256+
});
257+
258+
expect(parsed.success).toBe(true);
259+
if (parsed.success) {
260+
expect(parsed.data.question).toBe("Should we split this refactor into smaller commits?");
261+
}
262+
});
263+
250264
it("encourages compact task briefs and best-of delegation discipline", () => {
251265
expect(TOOL_DEFINITIONS.task.description).toContain("compact task brief");
252266
expect(TOOL_DEFINITIONS.task.description).toContain("plan file");

src/common/utils/tools/toolDefinitions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,11 @@ export const AskUserQuestionToolResultSchema = z.union([
153153
// advisor (nested strategic guidance)
154154
// -----------------------------------------------------------------------------
155155

156-
export const AdvisorToolInputSchema = z.object({}).strict();
156+
export const AdvisorToolInputSchema = z
157+
.object({
158+
question: z.string().min(1).max(500).nullish(),
159+
})
160+
.strict();
157161

158162
const AdvisorToolAdviceResultSchema = z
159163
.object({

src/common/utils/tools/tools.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ export interface ToolModelUsageEvent {
6868
timestamp: number;
6969
}
7070

71+
export interface AdvisorToolCallSnapshot {
72+
toolCallId: string;
73+
toolName: "advisor";
74+
input: Record<string, unknown>;
75+
stepText: string;
76+
stepReasoning: string;
77+
}
78+
79+
export interface AdvisorStepCaptureRef {
80+
currentStepText: string;
81+
currentStepReasoning: string;
82+
frozenSnapshotsByToolCallId: Map<string, AdvisorToolCallSnapshot>;
83+
}
84+
7185
/**
7286
* Configuration for tools that need runtime context
7387
*/
@@ -145,6 +159,8 @@ export interface ToolConfiguration {
145159
maxOutputTokens?: number;
146160
/** Returns the live conversation transcript up to the current tool call */
147161
getTranscriptSnapshot: () => ModelMessage[];
162+
/** Returns the frozen same-step capture snapshot for a specific advisor tool call, if available. */
163+
takeToolCallSnapshot: (toolCallId: string) => AdvisorToolCallSnapshot | undefined;
148164
/** Creates a LanguageModel from a model string (delegates to providerModelFactory) */
149165
createModel: (modelString: string) => Promise<LanguageModel>;
150166
/** The abort signal from the parent stream */

src/node/services/agentSkills/builtInSkillContent.generated.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4025,6 +4025,15 @@ export const BUILTIN_SKILL_FILES: Record<string, Record<string, string>> = {
40254025
"{/* BEGIN TOOL_HOOK_ENV_VARS */}",
40264026
"",
40274027
"<details>",
4028+
"<summary>advisor (1)</summary>",
4029+
"",
4030+
"| Env var | JSON path | Type | Description |",
4031+
"| ------------------------- | ---------- | ------ | ----------- |",
4032+
"| `MUX_TOOL_INPUT_QUESTION` | `question` | string | — |",
4033+
"",
4034+
"</details>",
4035+
"",
4036+
"<details>",
40284037
"<summary>agent_report (2)</summary>",
40294038
"",
40304039
"| Env var | JSON path | Type | Description |",

0 commit comments

Comments
 (0)