@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.
- package/dist/agents/subagent.js +17 -14
- package/dist/core/auto-compaction.d.ts +2 -0
- package/dist/core/auto-compaction.js +2 -1
- package/dist/core/mcp.d.ts +9 -0
- package/dist/core/mcp.js +60 -6
- package/dist/core/runner/prepare-task.d.ts +5 -0
- package/dist/core/runner/prepare-task.js +40 -2
- package/dist/core/runner/runtask.js +42 -9
- package/dist/core/runner/tool-disclosure.d.ts +9 -2
- package/dist/core/runner/tool-disclosure.js +39 -11
- package/dist/core/session-reconcile.d.ts +1 -0
- package/dist/core/session-reconcile.js +40 -20
- package/dist/core/skills-directory.d.ts +13 -0
- package/dist/core/skills-directory.js +214 -0
- package/dist/core/task-registry-agent.d.ts +2 -0
- package/dist/core/task-registry-agent.js +11 -1
- package/dist/core/task-registry-shared.d.ts +1 -0
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/task-registry.js +11 -0
- package/dist/core/trace.d.ts +3 -0
- package/dist/core/types.d.ts +11 -3
- package/dist/engine/compaction/compaction.d.ts +5 -0
- package/dist/engine/compaction/compaction.js +68 -2
- package/dist/engine/compaction/utils.d.ts +6 -0
- package/dist/engine/compaction/utils.js +53 -3
- package/dist/engine/harness/messages.d.ts +1 -1
- package/dist/engine/harness/messages.js +11 -3
- package/dist/engine/loop/types.d.ts +2 -0
- package/dist/engine/session/import-validate.js +30 -1
- package/dist/engine/session/session.js +7 -5
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/orchestration/workflow-types.d.ts +1 -0
- package/dist/orchestration/workflow.js +10 -1
- package/dist/tools/fs/fs-bash.js +11 -5
- package/package.json +1 -1
package/dist/agents/subagent.js
CHANGED
|
@@ -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);
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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 ?? {}) },
|
|
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
|
|
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
|
|
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,
|
|
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:
|
|
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:
|
|
729
|
-
reason:
|
|
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: "
|
|
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
|
|
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
|
|
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;
|