@sema-agent/core 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/agents/subagent.js +17 -14
  2. package/dist/core/auto-compaction.d.ts +2 -0
  3. package/dist/core/auto-compaction.js +2 -1
  4. package/dist/core/mcp.d.ts +9 -0
  5. package/dist/core/mcp.js +60 -6
  6. package/dist/core/runner/prepare-task.d.ts +5 -0
  7. package/dist/core/runner/prepare-task.js +40 -2
  8. package/dist/core/runner/runtask.js +42 -9
  9. package/dist/core/runner/tool-disclosure.d.ts +9 -2
  10. package/dist/core/runner/tool-disclosure.js +39 -11
  11. package/dist/core/session-reconcile.d.ts +1 -0
  12. package/dist/core/session-reconcile.js +40 -20
  13. package/dist/core/skills-directory.d.ts +13 -0
  14. package/dist/core/skills-directory.js +214 -0
  15. package/dist/core/task-registry-agent.d.ts +2 -0
  16. package/dist/core/task-registry-agent.js +11 -1
  17. package/dist/core/task-registry-shared.d.ts +1 -0
  18. package/dist/core/task-registry.d.ts +2 -0
  19. package/dist/core/task-registry.js +11 -0
  20. package/dist/core/trace.d.ts +3 -0
  21. package/dist/core/types.d.ts +11 -3
  22. package/dist/engine/compaction/compaction.d.ts +5 -0
  23. package/dist/engine/compaction/compaction.js +68 -2
  24. package/dist/engine/compaction/utils.d.ts +6 -0
  25. package/dist/engine/compaction/utils.js +53 -3
  26. package/dist/engine/harness/messages.d.ts +1 -1
  27. package/dist/engine/harness/messages.js +11 -3
  28. package/dist/engine/loop/types.d.ts +2 -0
  29. package/dist/engine/session/import-validate.js +30 -1
  30. package/dist/engine/session/session.js +7 -5
  31. package/dist/index.d.ts +8 -0
  32. package/dist/index.js +8 -0
  33. package/dist/internal/harness.d.ts +1 -1
  34. package/dist/internal/harness.js +1 -1
  35. package/dist/orchestration/workflow-types.d.ts +1 -0
  36. package/dist/orchestration/workflow.js +10 -1
  37. package/dist/tools/fs/fs-bash.js +11 -5
  38. package/package.json +1 -1
@@ -221,6 +221,9 @@ export function classifySubagentError(child) {
221
221
  : "logic";
222
222
  return { errorKind, retryable: errorKind !== "logic" };
223
223
  }
224
+ function errorKindClause(c) {
225
+ return c !== undefined ? ` (error_kind: ${c.errorKind}, retryable: ${c.retryable})` : "";
226
+ }
224
227
  const FAILED_SESSION_RETAIN_TTL_MS = 15 * 60 * 1000;
225
228
  const PARTIAL_FINDINGS_MAX_CHARS = 1200;
226
229
  const BG_NOTIFY_DRAIN_WINDOW_MS = 2_000;
@@ -427,7 +430,7 @@ export function createSubagentResume(deps) {
427
430
  ...resultSettleFields(child.result),
428
431
  ...(status !== "completed" ? { error: (child.errorMessage ?? `resumed run ${status}`).slice(0, 500) } : {}),
429
432
  ...(errCodeRevive !== undefined ? { errorCode: errCodeRevive } : {}),
430
- ...(errClassRevive !== undefined ? { retryable: errClassRevive.retryable } : {}),
433
+ ...(errClassRevive !== undefined ? { retryable: errClassRevive.retryable, errorKind: errClassRevive.errorKind } : {}),
431
434
  });
432
435
  if (winner !== undefined)
433
436
  status = winner;
@@ -440,7 +443,7 @@ export function createSubagentResume(deps) {
440
443
  const reviveName = deps.rowDescription ?? `sub-agent ${marker}`;
441
444
  const failReasonRevive = status === "failed" ? child.errorMessage : undefined;
442
445
  const reviveTerminalSummary = failReasonRevive !== undefined
443
- ? `Agent "${reviveName}" (resumed) failed: ${failReasonRevive}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300)
446
+ ? `Agent "${reviveName}" (resumed) failed: ${failReasonRevive}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300) + errorKindClause(errClassRevive)
444
447
  : `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(Date.now() - reviveStartedAt)}`;
445
448
  const ownsTerminalFacesRevive = deps.registry !== undefined && deps.taskId !== undefined ? deps.registry.claimAgentTerminalNotify(deps.taskId) : true;
446
449
  if (ownsTerminalFacesRevive)
@@ -509,7 +512,7 @@ export function createSubagentResume(deps) {
509
512
  status: abort.signal.aborted ? "killed" : "failed",
510
513
  error: msgRevive.slice(0, 500),
511
514
  ...(errCodeReviveReject !== undefined ? { errorCode: errCodeReviveReject } : {}),
512
- ...(errClassReviveReject !== undefined ? { retryable: errClassReviveReject.retryable } : {}),
515
+ ...(errClassReviveReject !== undefined ? { retryable: errClassReviveReject.retryable, errorKind: errClassReviveReject.errorKind } : {}),
513
516
  });
514
517
  }
515
518
  catch {
@@ -530,7 +533,7 @@ export function createSubagentResume(deps) {
530
533
  status: abort.signal.aborted ? "killed" : "failed",
531
534
  seq: entry.cycleSeq,
532
535
  ...(stoppedByReject !== undefined ? { stoppedBy: stoppedByReject } : {}),
533
- summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : "failed"}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300),
536
+ summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : "failed"}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300) + (abort.signal.aborted ? "" : errorKindClause(errClassReviveReject)),
534
537
  ...(completionIdRejectRevive !== undefined ? { completionId: completionIdRejectRevive } : {}),
535
538
  });
536
539
  try {
@@ -543,7 +546,7 @@ export function createSubagentResume(deps) {
543
546
  status: abort.signal.aborted ? "killed" : "failed",
544
547
  seq: entry.cycleSeq,
545
548
  ...(stoppedByReject !== undefined ? { stoppedBy: stoppedByReject } : {}),
546
- summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : `failed: ${msgRevive}`}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300),
549
+ summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : `failed: ${msgRevive}`}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300) + (abort.signal.aborted ? "" : errorKindClause(errClassReviveReject)),
547
550
  ...(abort.signal.aborted ? {} : { error: msgRevive.slice(0, REPORT_FIELD_MAX) }),
548
551
  ...(!abort.signal.aborted && errCodeReviveReject !== undefined ? { errorCode: errCodeReviveReject } : {}),
549
552
  ...(completionIdRejectRevive !== undefined ? { completionId: completionIdRejectRevive } : {}),
@@ -1753,7 +1756,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1753
1756
  ...resultSettleFields(child.result),
1754
1757
  ...(!okBg ? { error: reapedBg ? (collateralBg ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : child.errorMessage ?? String(child.status) } : {}),
1755
1758
  ...(errCodeFork !== undefined ? { errorCode: errCodeFork } : {}),
1756
- ...(errClassFork !== undefined ? { retryable: errClassFork.retryable } : {}),
1759
+ ...(errClassFork !== undefined ? { retryable: errClassFork.retryable, errorKind: errClassFork.errorKind } : {}),
1757
1760
  }) ?? (abort.signal.aborted ? "killed" : okBg ? "completed" : "failed");
1758
1761
  const stoppedByBg = settledBg === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
1759
1762
  const completionIdFork = bg.registry.getCompletionId(taskId);
@@ -1770,7 +1773,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1770
1773
  const forkTranscriptId = child.sessionId ?? forkedId;
1771
1774
  const failReasonFork = settledBg === "failed" ? child.errorMessage : undefined;
1772
1775
  const forkTerminalSummary = failReasonFork !== undefined
1773
- ? `Agent "${shortDesc}" failed: ${failReasonFork}${ccElapsedTag(Date.now() - forkBgStartedAt)}`.slice(0, 300)
1776
+ ? `Agent "${shortDesc}" failed: ${failReasonFork}${ccElapsedTag(Date.now() - forkBgStartedAt)}`.slice(0, 300) + errorKindClause(errClassFork)
1774
1777
  : `${ccCompletionText(shortDesc, settledBg, String(child.status), Date.now() - forkBgStartedAt)}`;
1775
1778
  const ownsTerminalFacesFork = bg.registry.claimAgentTerminalNotify(taskId);
1776
1779
  if (ownsTerminalFacesFork)
@@ -1823,11 +1826,11 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1823
1826
  status: killed ? "killed" : "failed",
1824
1827
  error: killed ? BG_AGENT_REAP_STOP_ERROR : msgFork,
1825
1828
  ...(errCodeForkReject !== undefined ? { errorCode: errCodeForkReject } : {}),
1826
- ...(errClassForkReject !== undefined ? { retryable: errClassForkReject.retryable } : {}),
1829
+ ...(errClassForkReject !== undefined ? { retryable: errClassForkReject.retryable, errorKind: errClassForkReject.errorKind } : {}),
1827
1830
  }) ?? (killed ? "killed" : "failed");
1828
1831
  const stoppedByBg = settledBg === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
1829
1832
  const completionIdForkReject = bg.registry.getCompletionId(taskId);
1830
- const summaryBg = `${shortDesc} — ${settledBg === "failed" ? `failed: ${msgFork}` : settledBg}`.slice(0, 300);
1833
+ const summaryBg = `${shortDesc} — ${settledBg === "failed" ? `failed: ${msgFork}` : settledBg}`.slice(0, 300) + (settledBg === "failed" ? errorKindClause(errClassForkReject) : "");
1831
1834
  const ownsTerminalFacesForkReject = bg.registry.claimAgentTerminalNotify(taskId);
1832
1835
  if (ownsTerminalFacesForkReject)
1833
1836
  sinkEmit({
@@ -2390,7 +2393,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2390
2393
  ...resultSettleFields(child.result),
2391
2394
  ...(!ok ? { error: reaped ? (collateral ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : child.errorMessage ?? String(child.status) } : {}),
2392
2395
  ...(errCodeBg !== undefined ? { errorCode: errCodeBg } : {}),
2393
- ...(errClassBg !== undefined ? { retryable: errClassBg.retryable } : {}),
2396
+ ...(errClassBg !== undefined ? { retryable: errClassBg.retryable, errorKind: errClassBg.errorKind } : {}),
2394
2397
  }) ??
2395
2398
  (abort.signal.aborted ? "killed" : ok ? "completed" : "failed");
2396
2399
  if (!bgRetain && reviveRow === undefined && !(await bgRowConfirmed())) {
@@ -2408,7 +2411,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2408
2411
  const resumableBg = bgRetain !== undefined && settled !== "killed";
2409
2412
  const failReasonBg = settled === "failed" ? child.errorMessage : undefined;
2410
2413
  const bgTerminalSummary = failReasonBg !== undefined
2411
- ? `Agent "${shortDesc}" failed: ${failReasonBg}${ccElapsedTag(Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300)
2414
+ ? `Agent "${shortDesc}" failed: ${failReasonBg}${ccElapsedTag(Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300) + errorKindClause(errClassBg)
2412
2415
  : `${ccCompletionText(shortDesc, settled, String(child.status), Date.now() - bgStartedAt)}${observerNote}`;
2413
2416
  const ownsTerminalFaces = bg.registry.claimAgentTerminalNotify(taskId);
2414
2417
  if (ownsTerminalFaces)
@@ -2485,7 +2488,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2485
2488
  error: msg,
2486
2489
  seq: seqAtSettle ?? 1,
2487
2490
  ...(errCodeBgReject !== undefined ? { errorCode: errCodeBgReject } : {}),
2488
- ...(errClassBgReject !== undefined ? { retryable: errClassBgReject.retryable } : {}),
2491
+ ...(errClassBgReject !== undefined ? { retryable: errClassBgReject.retryable, errorKind: errClassBgReject.errorKind } : {}),
2489
2492
  }) ?? (killed ? "killed" : "failed");
2490
2493
  if (!bgRetain && reviveRow === undefined && !(await bgRowConfirmed())) {
2491
2494
  try {
@@ -2510,7 +2513,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2510
2513
  status: settled,
2511
2514
  ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2512
2515
  ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2513
- summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300),
2516
+ summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300) + (settled === "failed" ? errorKindClause(errClassBgReject) : ""),
2514
2517
  ...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
2515
2518
  });
2516
2519
  try {
@@ -2523,7 +2526,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2523
2526
  sessionId: bgChildSessionId,
2524
2527
  ...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
2525
2528
  ...(stoppedBy !== undefined ? { stoppedBy } : {}),
2526
- summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300),
2529
+ summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300) + (settled === "failed" ? errorKindClause(errClassBgReject) : ""),
2527
2530
  ...(settled === "failed" ? { error: msg.slice(0, REPORT_FIELD_MAX) } : {}),
2528
2531
  ...(settled === "failed" && errCodeBgReject !== undefined ? { errorCode: errCodeBgReject } : {}),
2529
2532
  ...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
@@ -96,11 +96,13 @@ export interface MaybeCompactOptions {
96
96
  droppedChars: number;
97
97
  keptChars: number;
98
98
  }) => void;
99
+ activeTools?: ReadonlyArray<string>;
99
100
  }
100
101
  export declare function maybeCompact(opts: MaybeCompactOptions): Promise<{
101
102
  compacted: boolean;
102
103
  blocked?: boolean;
103
104
  disabled?: boolean;
105
+ noop?: true;
104
106
  tokensBefore?: number;
105
107
  freedTokens?: number;
106
108
  triggerTokens?: number;
@@ -86,7 +86,7 @@ export async function maybeCompact(opts) {
86
86
  throw prep.error;
87
87
  }
88
88
  if (!prep.value) {
89
- return { contextUsage, compacted: false };
89
+ return { contextUsage, compacted: false, noop: true };
90
90
  }
91
91
  const trigger = opts.trigger ?? "auto";
92
92
  let hookInstructions;
@@ -454,6 +454,7 @@ export async function maybeCompact(opts) {
454
454
  ...(details ?? {}),
455
455
  promptEpoch: restatedEpoch,
456
456
  ...(restatedListings !== undefined ? { announcedListings: restatedListings } : {}),
457
+ ...(opts.activeTools !== undefined && opts.activeTools.length > 0 ? { activeTools: [...opts.activeTools] } : {}),
457
458
  }, false);
458
459
  try {
459
460
  opts.onApplied?.(attachedComplete, excludedReadStatePreserveKeys);
@@ -68,6 +68,7 @@ export declare function mcpToolTotalTimeoutMs(perCallMs: number): number;
68
68
  export declare const MCP_IDLE_TIMEOUT_STDIO_DEFAULT_MS: number;
69
69
  export declare const MCP_IDLE_TIMEOUT_HTTP_DEFAULT_MS: number;
70
70
  export declare function mcpIdleTimeoutMs(kind: "stdio" | "http"): number;
71
+ export declare function describeMcpSpecErrorCode(code: unknown): string | undefined;
71
72
  export declare function normalizeMcpName(name: string): string;
72
73
  export declare function clampNameSegment(seg: string, max?: number): string;
73
74
  export * from "./image-downsample.js";
@@ -106,3 +107,11 @@ export type McpSchemaNormalizeResult = {
106
107
  export declare function normalizeMcpToolSchema(schema: unknown): McpSchemaNormalizeResult;
107
108
  export declare function mcpToolSchemaProblem(schema: unknown): string | undefined;
108
109
  export declare function materializeMcpTools(specs: McpServerSpec[], principal?: string, onElicit?: OnElicit, imageResizer?: McpImageResizer): Promise<MaterializedMcp>;
110
+ export declare function classifyDirReadInvalidParams(message: string): "not_found" | "not_directory";
111
+ export declare function parseCallToolResultLenient(data: unknown): {
112
+ success: true;
113
+ data: unknown;
114
+ } | {
115
+ success: false;
116
+ error: unknown;
117
+ };
package/dist/core/mcp.js CHANGED
@@ -5,7 +5,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
5
5
  import { lstat, mkdir, writeFile } from "node:fs/promises";
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
8
- import { ElicitRequestSchema, ErrorCode, ListResourcesResultSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
8
+ import { CallToolResultSchema, ElicitRequestSchema, ErrorCode, ListResourcesResultSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
9
9
  import { MCP_IMAGE_MAX_BASE64, sharpImageResizer } from "./image-downsample.js";
10
10
  import { truncateError } from "./tool-errors.js";
11
11
  import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
@@ -137,6 +137,14 @@ function armMcpIdleWatchdog(health, idleMs, outerSignal) {
137
137
  function mcpStartupTimeoutMs() {
138
138
  return parseEnvMs("MCP_TIMEOUT");
139
139
  }
140
+ const MCP_SPEC_ERROR_CODE_NAMES = new Map([
141
+ [-32020, "header mismatch (the server saw an HTTP header that disagreed with the tool parameter mapped onto it)"],
142
+ [-32021, "missing required client capability (the server requires a capability this client does not declare)"],
143
+ [-32022, "unsupported protocol version (the server implements no protocol revision this client offers)"],
144
+ ]);
145
+ export function describeMcpSpecErrorCode(code) {
146
+ return typeof code === "number" ? MCP_SPEC_ERROR_CODE_NAMES.get(code) : undefined;
147
+ }
140
148
  function isTransportLost(err) {
141
149
  if (err instanceof McpError && err.code === ErrorCode.ConnectionClosed)
142
150
  return true;
@@ -180,6 +188,15 @@ function rethrowHonestMcpError(err, ctx) {
180
188
  e.details = { transportLost: true, server: ctx.server };
181
189
  throw e;
182
190
  }
191
+ if (err instanceof McpError) {
192
+ const condition = describeMcpSpecErrorCode(err.code);
193
+ if (condition !== undefined) {
194
+ const e = new Error(`${ctx.what} on MCP server "${serverLabel}" was rejected with MCP protocol error ${err.code} — ${condition}. The server's error text follows as external/untrusted data:\n${delimitUntrusted(`${ctx.server} error`, truncateMcpErrorText(err.message))}`, { cause: err });
195
+ e.errorKind = "protocol_error";
196
+ e.details = { server: ctx.server, specErrorCode: err.code };
197
+ throw e;
198
+ }
199
+ }
183
200
  if (ctx.attributeServer) {
184
201
  const msg = err instanceof Error ? err.message : String(err);
185
202
  throw new Error(`${ctx.what} on MCP server "${serverLabel}" failed. The server's error text follows as external/untrusted data:\n${delimitUntrusted(`${ctx.server} error`, truncateMcpErrorText(msg))}`, { cause: err });
@@ -653,6 +670,15 @@ const LEGACY_READ_MCP_RESOURCE = "ReadMcpResource";
653
670
  const LEGACY_READ_MCP_RESOURCE_DIR = "ReadMcpResourceDir";
654
671
  const MCP_SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
655
672
  const MAX_DIR_READ_PAGES = 20;
673
+ const DIR_READ_NOT_A_DIRECTORY_RE = /not a directory|isn'?t a directory|not a folder/i;
674
+ const DIR_READ_NOT_FOUND_RE = /not found|no such|does not exist|doesn'?t exist|unknown (?:resource|uri)/i;
675
+ export function classifyDirReadInvalidParams(message) {
676
+ if (DIR_READ_NOT_A_DIRECTORY_RE.test(message))
677
+ return "not_directory";
678
+ if (DIR_READ_NOT_FOUND_RE.test(message))
679
+ return "not_found";
680
+ return "not_directory";
681
+ }
656
682
  function resourceLine(r) {
657
683
  return `${r.uri}${r.name ? ` — ${r.name}` : ""}${r.mimeType ? ` (${r.mimeType})` : ""}${r.description ? `: ${r.description}` : ""}`;
658
684
  }
@@ -672,7 +698,7 @@ async function readDirViaExtension(rs, uri, signal, timeoutMs, watchdog) {
672
698
  if (err instanceof McpError && err.code === ErrorCode.InvalidParams) {
673
699
  if (pages > 0)
674
700
  return { kind: "ok", resources, cursorInvalid: true };
675
- return { kind: "not_directory" };
701
+ return { kind: classifyDirReadInvalidParams(err.message), detail: err.message };
676
702
  }
677
703
  if (err instanceof McpError && err.code === ErrorCode.MethodNotFound)
678
704
  return { kind: "unsupported" };
@@ -866,9 +892,22 @@ function buildResourceTools(resourceServers) {
866
892
  const ext = rs.client.getServerCapabilities()?.extensions?.[MCP_SKILLS_EXTENSION];
867
893
  if (ext?.directoryRead === true) {
868
894
  const r = await readDirViaExtension(rs, uri, signal, timeoutMs, watchdog).catch((err) => rethrowHonestMcpError(err, { server, what, timeoutMs, writeEffect: false, signal, attributeServer: true, idle: { signal: watchdog.idleSignal, idleMs } }));
895
+ const serverSaid = (detail) => `\nThe server's error text follows as external/untrusted data:\n${delimitUntrusted(`${server} error`, truncateMcpErrorText(detail))}`;
896
+ if (r.kind === "not_found") {
897
+ return {
898
+ content: [
899
+ {
900
+ type: "text",
901
+ text: `Resource not found: ${inlineUntrusted(uri)} — the server reports no such resource, so re-reading it will not help either. Use ${LIST_MCP_RESOURCES} to see what this server exposes.${serverSaid(r.detail)}`,
902
+ },
903
+ ],
904
+ details: { resources: [], notFound: true },
905
+ terminate: false,
906
+ };
907
+ }
869
908
  if (r.kind === "not_directory") {
870
909
  return {
871
- content: [{ type: "text", text: `Not a directory resource: ${inlineUntrusted(uri)}. If it is a file resource, use ${READ_MCP_RESOURCE} instead.` }],
910
+ content: [{ type: "text", text: `Not a directory resource: ${inlineUntrusted(uri)}. If it is a file resource, use ${READ_MCP_RESOURCE} instead.${serverSaid(r.detail)}` }],
872
911
  details: { resources: [] },
873
912
  terminate: false,
874
913
  };
@@ -929,6 +968,17 @@ const LenientListToolsResultSchema = {
929
968
  return { success: true, data: { tools, ...(typeof nextCursor === "string" ? { nextCursor } : {}) } };
930
969
  },
931
970
  };
971
+ export function parseCallToolResultLenient(data) {
972
+ if (typeof data !== "object" || data === null || Array.isArray(data) || !("structuredContent" in data)) {
973
+ return CallToolResultSchema.safeParse(data);
974
+ }
975
+ const { structuredContent, ...rest } = data;
976
+ const parsed = CallToolResultSchema.safeParse(rest);
977
+ if (!parsed.success)
978
+ return parsed;
979
+ return { success: true, data: { ...parsed.data, structuredContent } };
980
+ }
981
+ const LENIENT_CALL_TOOL_RESULT_SCHEMA = { safeParse: parseCallToolResultLenient };
932
982
  async function listToolsLenient(client, options) {
933
983
  return client.request({ method: "tools/list", params: {} }, LenientListToolsResultSchema, options);
934
984
  }
@@ -1039,13 +1089,16 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
1039
1089
  if (axis)
1040
1090
  serverAxes.push(axis);
1041
1091
  const writeEffect = (axis?.effect ?? "write") === "write";
1042
- const mcpMaxResultSizeChars = resolveMcpDeclaredResultSize(t._meta);
1092
+ const mcpToolMeta = t._meta;
1093
+ const mcpMaxResultSizeChars = resolveMcpDeclaredResultSize(mcpToolMeta);
1094
+ const mcpAlwaysLoad = mcpToolMeta?.["anthropic/alwaysLoad"] === true;
1043
1095
  serverTools.push({
1044
1096
  name: namespacedName,
1045
1097
  description: effectiveDescription ?? `MCP tool ${remoteName} from ${spec.name}`,
1046
1098
  label: `${spec.name}:${remoteName}`,
1047
1099
  parameters: (effectiveInputSchema ?? { type: "object" }),
1048
1100
  ...(mcpMaxResultSizeChars !== undefined ? { mcpMaxResultSizeChars } : {}),
1101
+ ...(mcpAlwaysLoad ? { mcpAlwaysLoad: true } : {}),
1049
1102
  execute: async (_toolCallId, params, signal) => {
1050
1103
  const what = `The call to tool ${inlineUntrusted(remoteName)}`;
1051
1104
  if (health.dead)
@@ -1056,7 +1109,7 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
1056
1109
  let res;
1057
1110
  try {
1058
1111
  res = await client
1059
- .callTool({ name: remoteName, arguments: (params ?? {}) }, undefined, {
1112
+ .callTool({ name: remoteName, arguments: (params ?? {}) }, LENIENT_CALL_TOOL_RESULT_SCHEMA, {
1060
1113
  signal: watchdog.combinedSignal,
1061
1114
  timeout: timeoutMs,
1062
1115
  resetTimeoutOnProgress: true,
@@ -1103,7 +1156,8 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
1103
1156
  }
1104
1157
  function asServerWarning(spec, err) {
1105
1158
  const detail = err instanceof Error ? err.message : String(err);
1106
- const warning = new Error(`mcp: server "${spec.name}" failed to connect — skipped (${detail})`, { cause: err });
1159
+ const condition = err instanceof McpError ? describeMcpSpecErrorCode(err.code) : undefined;
1160
+ const warning = new Error(`mcp: server "${spec.name}" failed to connect — skipped (${condition !== undefined ? `${condition}: ` : ""}${detail})`, { cause: err });
1107
1161
  warning.code = "mcp.server_unavailable";
1108
1162
  return warning;
1109
1163
  }
@@ -327,6 +327,11 @@ export interface RunInternals {
327
327
  requestedCwd?: string;
328
328
  onSubagentSpawn?: (handle: import("../../agents/subagent.js").SubagentSteerHandle) => void;
329
329
  onActivity?: (activity: ToolActivity) => void;
330
+ onWorkspaceResolved?: (workspace: ResolvedWorkspace) => void;
331
+ }
332
+ export interface ResolvedWorkspace {
333
+ cwd: string;
334
+ isolated: boolean;
330
335
  }
331
336
  export declare function batchContextAt(messages: AgentMessage[], currentId: string): {
332
337
  batchToolCallIds: string[];
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
  import { realpathSync } from "node:fs";
3
3
  import { resolve as resolveFsPath } from "node:path";
4
- import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, summaryOutputBudgetTokens } from "../../internal/harness.js";
4
+ import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, readCompactionActiveTools, summaryOutputBudgetTokens } from "../../internal/harness.js";
5
5
  const PROMPT_HASH_SALT = randomBytes(16);
6
6
  import { sanitizeCompactionSettings } from "../auto-compaction.js";
7
7
  import { createAutoModeDecider } from "../auto-mode.js";
@@ -177,6 +177,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
177
177
  const toolFaceSnapshot = {
178
178
  exclude: spec.excludeTools ? Object.freeze([...spec.excludeTools]) : undefined,
179
179
  defer: spec.deferTools ? Object.freeze([...spec.deferTools]) : undefined,
180
+ alwaysLoad: spec.alwaysLoadTools ? Object.freeze([...spec.alwaysLoadTools]) : undefined,
180
181
  };
181
182
  const promptProfile = resolveModelPromptTraits({ id: "" }, spec, internals).promptProfile;
182
183
  if (spec.resumeAt !== undefined) {
@@ -608,6 +609,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
608
609
  }
609
610
  }
610
611
  }
612
+ if (internals?.onWorkspaceResolved !== undefined) {
613
+ try {
614
+ internals.onWorkspaceResolved({ cwd: taskRootPath, isolated: internals.isolation === "worktree" });
615
+ }
616
+ catch {
617
+ }
618
+ }
611
619
  let rewindTarget = spec.resumeAt !== undefined ? (spec.rewindFiles ? spec.resumeAt : undefined) : spec.rewindFilesTo;
612
620
  const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
613
621
  if (rewindTarget !== undefined && deps.fileSnapshotStore && handsEnabled) {
@@ -1781,6 +1789,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1781
1789
  deferMode: deps.deferMode,
1782
1790
  model,
1783
1791
  deferNames: (toolFaceSnapshot.defer ?? []).filter((n) => tools.some((t) => t.name === n)),
1792
+ alwaysLoadNames: [
1793
+ ...(toolFaceSnapshot.alwaysLoad ?? []),
1794
+ ...mcp.tools.filter((t) => t.mcpAlwaysLoad === true).map((t) => t.name),
1795
+ ],
1784
1796
  });
1785
1797
  for (const n of [...deferred]) {
1786
1798
  if (!tools.some((t) => t.name === n))
@@ -1814,12 +1826,38 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1814
1826
  throw e;
1815
1827
  }
1816
1828
  const registry = buildDeferredRegistry(deferred, tools);
1817
- const placeholders = new Map([...registry.values()].map((i) => [i.name, createPlaceholderTool(i)]));
1829
+ const realByName = new Map(tools.map((t) => [t.name, t]));
1830
+ const directCallFor = (name) => {
1831
+ if (spec.deferSelfResolve === false)
1832
+ return undefined;
1833
+ const real = realByName.get(name);
1834
+ if (real === undefined)
1835
+ return undefined;
1836
+ return {
1837
+ parameters: real.parameters,
1838
+ invoke: (toolCallId, params, signal) => real.execute(toolCallId, params, signal),
1839
+ activate: async () => {
1840
+ if (activeTools.has(name))
1841
+ return;
1842
+ activeTools.add(name);
1843
+ await rematerialize(activeTools);
1844
+ },
1845
+ };
1846
+ };
1847
+ const placeholders = new Map([...registry.values()].map((i) => [i.name, createPlaceholderTool(i, directCallFor(i.name))]));
1818
1848
  const { messages } = await session.buildContext();
1819
1849
  for (const n of extractDiscoveredToolNames(messages, registry)) {
1820
1850
  if (deferred.has(n))
1821
1851
  activeTools.add(n);
1822
1852
  }
1853
+ for (const entry of await session.getBranch()) {
1854
+ if (entry.type !== "compaction" || entry.fromHook === true)
1855
+ continue;
1856
+ for (const n of readCompactionActiveTools(entry.details)) {
1857
+ if (deferred.has(n))
1858
+ activeTools.add(n);
1859
+ }
1860
+ }
1823
1861
  if (resume) {
1824
1862
  for (const n of resume.seed.activeTools)
1825
1863
  if (deferred.has(n))
@@ -538,6 +538,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
538
538
  prepared.reviewRef.token !== undefined) {
539
539
  if (forceManual) {
540
540
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
541
+ queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
541
542
  drainManualCompact("mooted");
542
543
  }
543
544
  return undefined;
@@ -552,6 +553,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
552
553
  reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt",
553
554
  ts: Date.now(),
554
555
  }));
556
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "manual", reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt", ...ident() });
555
557
  drainManualCompact("failed");
556
558
  }
557
559
  return undefined;
@@ -590,6 +592,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
590
592
  if (trimPressureArmed)
591
593
  prepared.trimPressureRef.droppedMessages = false;
592
594
  const trimPressure = trimPressureArmed && !rs.counters.trimForceBackoff;
595
+ const passTrigger = trimPressure ? "forced" : forceManual ? "manual" : "auto";
593
596
  let manualOutcome = "failed";
594
597
  if (rs.counters.finalizeInjected && forceManual) {
595
598
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.manual_in_finalize", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
@@ -617,6 +620,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
617
620
  onInputTruncated: (info) => emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.input_truncated", version: 1, taskId: rs.telemetry.taskId, label: info.label, droppedChars: info.droppedChars, keptChars: info.keptChars, ts: Date.now() })),
618
621
  forceUnderThreshold: trimPressure,
619
622
  overheadTokens: prepared.promptOverheadTokens,
623
+ ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
620
624
  workingFileAttachments: spec.compaction?.attachWorkingFiles !== false && prepared.readTaskFile
621
625
  ? {
622
626
  readFile: prepared.readTaskFile,
@@ -632,7 +636,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
632
636
  ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
633
637
  ...runnerHooks.seamCCompactionOptions(prepared),
634
638
  ...windowSafetyOptions(event.model),
635
- ...runnerHooks.compactionHookOptions(spec, prepared.sessionId, trimPressure ? "forced" : forceManual ? "manual" : "auto"),
639
+ ...runnerHooks.compactionHookOptions(spec, prepared.sessionId, passTrigger),
636
640
  });
637
641
  if (comp.blocked) {
638
642
  emitTrace(rs.telemetry.tracer, () => ({
@@ -670,17 +674,32 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
670
674
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.noop", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
671
675
  }
672
676
  }
677
+ if (!comp.compacted) {
678
+ const outcome = comp.blocked
679
+ ? "blocked"
680
+ : comp.disabled
681
+ ? (forceManual || trimPressure ? "disabled" : undefined)
682
+ : comp.suppressedByFloor
683
+ ? "suppressed"
684
+ : comp.noop || forceManual
685
+ ? "noop"
686
+ : undefined;
687
+ if (outcome !== undefined) {
688
+ queue.push({ type: "compaction_outcome", outcome, trigger: passTrigger, ...ident() });
689
+ }
690
+ }
673
691
  if (comp.compacted && !forceManual) {
674
692
  if (recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
675
693
  compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
676
694
  runnerHooks.onError?.(new Error("compaction.rapid_refill: the context refilled within <3 turns of compaction 3 times in a row — compaction disabled for the rest of this task (thrash spiral; the transcript is dominated by incompressible content)"), { phase: "compaction", sessionId: prepared.sessionId });
695
+ queue.push({ type: "compaction_outcome", outcome: "disabled", trigger: passTrigger, reason: "rapid_refill: compaction disabled for the rest of this task", ...ident() });
677
696
  }
678
697
  }
679
698
  runnerHooks.recordCompactionReuse(prepared, comp);
680
699
  if (comp.compacted) {
681
700
  queue.push({
682
701
  type: "compacted",
683
- trigger: forceManual ? "manual" : "auto",
702
+ trigger: passTrigger,
684
703
  tokensBefore: comp.tokensBefore ?? 0,
685
704
  ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
686
705
  ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
@@ -692,6 +711,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
692
711
  ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
693
712
  ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
694
713
  ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
714
+ ...ident(),
695
715
  });
696
716
  if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
697
717
  const pd = comp.phaseDurations;
@@ -713,6 +733,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
713
733
  const manualCancelAbort = !prepared.abortController.signal.aborted && isCompactionManualCancel(err);
714
734
  if (manualCancelAbort) {
715
735
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
736
+ queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "cancelled", ...ident() });
716
737
  manualOutcome = "mooted";
717
738
  }
718
739
  else if (!prepared.abortController.signal.aborted) {
@@ -721,18 +742,20 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
721
742
  compactionBreaker.failures += 1;
722
743
  runnerHooks.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
723
744
  const msg = String(err instanceof Error ? err.message : err);
745
+ const failReason = walltimeAbort
746
+ ? "walltime soft-deadline abort (engine-initiated write-out protection; not counted toward the breaker)"
747
+ : msg.length > 512
748
+ ? `${msg.slice(0, 512)}…`
749
+ : msg;
724
750
  emitTrace(rs.telemetry.tracer, () => ({
725
751
  kind: "compaction.failed",
726
752
  version: 1,
727
753
  taskId: rs.telemetry.taskId,
728
- trigger: trimPressure ? "forced" : forceManual ? "manual" : "auto",
729
- reason: walltimeAbort
730
- ? "walltime soft-deadline abort (engine-initiated write-out protection; not counted toward the breaker)"
731
- : msg.length > 512
732
- ? `${msg.slice(0, 512)}…`
733
- : msg,
754
+ trigger: passTrigger,
755
+ reason: failReason,
734
756
  ts: Date.now(),
735
757
  }));
758
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: passTrigger, reason: failReason, ...ident() });
736
759
  manualOutcome = "failed";
737
760
  }
738
761
  else {
@@ -821,6 +844,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
821
844
  taskId: rs.telemetry.taskId,
822
845
  model: m.model,
823
846
  provider: m.provider,
847
+ turn: stats.turns + 1,
824
848
  promptTokens: turnInput,
825
849
  completionTokens: u.output || 0,
826
850
  cacheRead: u.cacheRead || 0,
@@ -988,6 +1012,8 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
988
1012
  version: 1,
989
1013
  taskId: rs.telemetry.taskId,
990
1014
  name: event.toolName,
1015
+ toolCallId: event.toolCallId,
1016
+ turn: stats.turns + 1,
991
1017
  durationMs: toolStarted !== undefined ? toolNow - toolStarted : 0,
992
1018
  ok: !event.isError,
993
1019
  ts: toolNow,
@@ -2319,6 +2345,7 @@ export class Runner {
2319
2345
  minTokens: 0,
2320
2346
  force: true,
2321
2347
  overheadTokens: prepared.promptOverheadTokens,
2348
+ ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
2322
2349
  onInputTruncated: (info) => emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.input_truncated", version: 1, taskId: rs.telemetry.taskId, label: info.label, droppedChars: info.droppedChars, keptChars: info.keptChars, ts: Date.now() })),
2323
2350
  workingFileAttachments: spec.compaction?.attachWorkingFiles !== false && prepared.readTaskFile
2324
2351
  ? {
@@ -2346,7 +2373,7 @@ export class Runner {
2346
2373
  }
2347
2374
  queue.push({
2348
2375
  type: "compacted",
2349
- trigger: "auto",
2376
+ trigger: "forced",
2350
2377
  tokensBefore: comp.tokensBefore ?? 0,
2351
2378
  ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
2352
2379
  ...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
@@ -2358,6 +2385,7 @@ export class Runner {
2358
2385
  ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
2359
2386
  ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
2360
2387
  ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
2388
+ ...ident(),
2361
2389
  });
2362
2390
  if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
2363
2391
  const pd = comp.phaseDurations;
@@ -2372,12 +2400,15 @@ export class Runner {
2372
2400
  }
2373
2401
  else {
2374
2402
  compactionBreaker.failures += 1;
2403
+ queue.push({ type: "compaction_outcome", outcome: comp.noop ? "noop" : "failed", trigger: "forced", reason: "prompt-too-long recovery pass did not land", ...ident() });
2375
2404
  }
2376
2405
  return comp.compacted === true;
2377
2406
  }
2378
2407
  catch (err) {
2379
2408
  if (!isCompactionWalltimeAbort(err))
2380
2409
  compactionBreaker.failures += 1;
2410
+ const msg = String(err instanceof Error ? err.message : err);
2411
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...ident() });
2381
2412
  return false;
2382
2413
  }
2383
2414
  },
@@ -2661,6 +2692,7 @@ export class Runner {
2661
2692
  ...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
2662
2693
  ...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
2663
2694
  ...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
2695
+ ...ident(),
2664
2696
  });
2665
2697
  if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
2666
2698
  const pd = comp.phaseDurations;
@@ -3453,6 +3485,7 @@ export class Runner {
3453
3485
  customInstructions: spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
3454
3486
  minTokens: opts?.minTokens,
3455
3487
  overheadTokens: prepared.promptOverheadTokens,
3488
+ ...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
3456
3489
  onInputTruncated: (info) => emitTrace(spec.tracer ?? this.deps.tracer, () => ({
3457
3490
  kind: "compaction.input_truncated",
3458
3491
  version: 1,
@@ -1,4 +1,5 @@
1
- import type { AgentMessage, AgentTool } from "../../internal/harness-types.js";
1
+ import { type TSchema } from "typebox";
2
+ import type { AgentMessage, AgentTool, AgentToolResult } from "../../internal/harness-types.js";
2
3
  import type { Model } from "../../internal/llm.js";
3
4
  import type { ToolSpec } from "../types.js";
4
5
  export declare const TOOL_SEARCH_NAME = "ToolSearch";
@@ -21,12 +22,18 @@ export declare function classifyDeferred(opts: {
21
22
  deferMode?: "auto";
22
23
  model: Model;
23
24
  deferNames?: ReadonlyArray<string>;
25
+ alwaysLoadNames?: ReadonlyArray<string>;
24
26
  }): Set<string>;
25
27
  export declare function buildDeferredRegistry(deferred: ReadonlySet<string>, tools: ReadonlyArray<{
26
28
  name: string;
27
29
  description: string;
28
30
  }>): Map<string, DeferredToolInfo>;
29
- export declare function createPlaceholderTool(info: DeferredToolInfo): AgentTool;
31
+ export interface PlaceholderDirectCall {
32
+ parameters: TSchema;
33
+ invoke: (toolCallId: string, params: unknown, signal?: AbortSignal) => Promise<AgentToolResult<unknown>>;
34
+ activate: () => Promise<void>;
35
+ }
36
+ export declare function createPlaceholderTool(info: DeferredToolInfo, direct?: PlaceholderDirectCall): AgentTool;
30
37
  export declare function scoreToolMatch(query: string, info: DeferredToolInfo): number;
31
38
  export interface ToolSearchArgs {
32
39
  query?: string;