@sema-agent/core 2.10.0 → 2.12.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/send-message-tool.js +15 -20
- package/dist/agents/subagent.js +4 -2
- package/dist/core/checkpoint-store.d.ts +1 -0
- package/dist/core/checkpoint-store.js +3 -0
- package/dist/core/consolidate-scope.js +4 -2
- package/dist/core/context-edit.js +8 -12
- package/dist/core/mcp.d.ts +2 -0
- package/dist/core/mcp.js +89 -6
- package/dist/core/memory.d.ts +4 -2
- package/dist/core/message-utils.d.ts +2 -1
- package/dist/core/remote-env.d.ts +3 -0
- package/dist/core/remote-env.js +19 -1
- package/dist/core/runner/assemble-result.d.ts +3 -0
- package/dist/core/runner/assemble-result.js +4 -1
- package/dist/core/runner/prepare-task.d.ts +5 -0
- package/dist/core/runner/prepare-task.js +126 -34
- package/dist/core/runner/runtask.js +22 -2
- package/dist/core/session-store.d.ts +3 -0
- package/dist/core/session-store.js +10 -0
- package/dist/core/session.d.ts +2 -0
- package/dist/core/tool-result-budget.js +2 -0
- package/dist/core/types.d.ts +9 -0
- package/dist/engine/harness/types.d.ts +1 -1
- package/dist/engine/session/session.js +2 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/tools/fs/fs-bash.js +14 -0
- package/dist/tools/fs/fs-write.js +2 -1
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/fs/safety.js +1 -1
- package/dist/tools/scheduler-tools.js +29 -15
- package/package.json +1 -1
|
@@ -8,6 +8,9 @@ import { createSubagentResume } from "./subagent.js";
|
|
|
8
8
|
export const SEND_MESSAGE_TOOL_NAME = "SendMessage";
|
|
9
9
|
let uplinkSeqGlobal = Date.now();
|
|
10
10
|
const UPLINK_RESULT_MAX = 8000;
|
|
11
|
+
function clipCarrierMessage(text) {
|
|
12
|
+
return text.length > UPLINK_RESULT_MAX ? `${text.slice(0, UPLINK_RESULT_MAX)}\n[message truncated: ${text.length} chars total]` : text;
|
|
13
|
+
}
|
|
11
14
|
export const SEND_MESSAGE_SUMMARY_MAX = 200;
|
|
12
15
|
export function clipSendMessageSummary(raw) {
|
|
13
16
|
return raw.length > SEND_MESSAGE_SUMMARY_MAX
|
|
@@ -89,6 +92,7 @@ export function createSendMessageTool(opts) {
|
|
|
89
92
|
isError: true,
|
|
90
93
|
};
|
|
91
94
|
}
|
|
95
|
+
const summary = clipSendMessageSummary(summaryArg);
|
|
92
96
|
const senderId = ctx.taskId ?? opts.owner;
|
|
93
97
|
const [parentTaskId, parentSessionId] = ctx.parentTaskId !== undefined ? [ctx.parentTaskId, ctx.parentSessionId] : [opts.parentTaskId, opts.parentSessionId];
|
|
94
98
|
const senderName = ctx.spawnedAgentName ?? opts.senderName;
|
|
@@ -102,14 +106,12 @@ export function createSendMessageTool(opts) {
|
|
|
102
106
|
}
|
|
103
107
|
if (normalizeAgentName(to) === "main") {
|
|
104
108
|
if (opts.uplink && senderId !== undefined) {
|
|
105
|
-
const uplinkSummaryRaw = typeof a.summary === "string" && a.summary.trim() !== "" ? clipSendMessageSummary(a.summary.trim()) : message.slice(0, 80);
|
|
106
|
-
const fromLabel = senderName ?? senderId;
|
|
107
109
|
try {
|
|
108
110
|
opts.uplink({
|
|
109
111
|
task_id: senderId,
|
|
110
112
|
task_type: "background_agent",
|
|
111
113
|
status: "event",
|
|
112
|
-
summary: `message from ${
|
|
114
|
+
summary: `message from ${senderLabel}: ${summary}`,
|
|
113
115
|
result: message.length > UPLINK_RESULT_MAX ? `${message.slice(0, UPLINK_RESULT_MAX)}\n[uplink truncated: ${message.length} chars total — read the agent's transcript for the rest]` : message,
|
|
114
116
|
seq: ++uplinkSeqGlobal,
|
|
115
117
|
}, { priority: "next" });
|
|
@@ -270,11 +272,9 @@ export function createSendMessageTool(opts) {
|
|
|
270
272
|
isError: true,
|
|
271
273
|
};
|
|
272
274
|
}
|
|
273
|
-
const clipped = message
|
|
274
|
-
const t3Summary = typeof a.summary === "string" && a.summary.trim() !== "" ? clipSendMessageSummary(a.summary.trim()) : undefined;
|
|
275
|
-
const t3From = senderLabel;
|
|
275
|
+
const clipped = clipCarrierMessage(message);
|
|
276
276
|
try {
|
|
277
|
-
await opts.mailbox.append(scope, handle, { from:
|
|
277
|
+
await opts.mailbox.append(scope, handle, { from: senderLabel, content: `[${summary}] ${clipped}`, sentAt: now });
|
|
278
278
|
}
|
|
279
279
|
catch (e) {
|
|
280
280
|
await rollback();
|
|
@@ -418,17 +418,13 @@ export function createSendMessageTool(opts) {
|
|
|
418
418
|
}
|
|
419
419
|
if (row.status === "running" || row.status === "pending") {
|
|
420
420
|
return await withTargetLane(targetLaneKey(access.scope, targetId), async () => {
|
|
421
|
-
const
|
|
422
|
-
const
|
|
423
|
-
const s2Clipped = message.length > UPLINK_RESULT_MAX
|
|
424
|
-
? `${message.slice(0, UPLINK_RESULT_MAX)}\n[message truncated: ${message.length} chars total]`
|
|
425
|
-
: message;
|
|
426
|
-
const teammateXml = frameTeammateMessage({ from: fromLabel, ...(s2Summary !== undefined ? { summary: s2Summary } : {}), text: s2Clipped });
|
|
421
|
+
const s2Clipped = clipCarrierMessage(message);
|
|
422
|
+
const teammateXml = frameTeammateMessage({ from: senderLabel, summary, text: s2Clipped });
|
|
427
423
|
const delivered = await opts.registry.deliverToRunningAgent(targetId, resolvedAccess, {
|
|
428
424
|
task_id: senderLabel,
|
|
429
425
|
task_type: "background_agent",
|
|
430
426
|
status: "event",
|
|
431
|
-
summary: `message from ${
|
|
427
|
+
summary: `message from ${senderLabel}: ${summary}`,
|
|
432
428
|
result: teammateXml,
|
|
433
429
|
seq: ++uplinkSeqGlobal,
|
|
434
430
|
}, { priority: "next" });
|
|
@@ -440,7 +436,7 @@ export function createSendMessageTool(opts) {
|
|
|
440
436
|
: `Message queued for delivery to ${who} at its next turn. If it finishes before reading it, the message may not survive — you will be notified of its completion either way; resend then if it went unanswered. Continue with other work; do not poll.`;
|
|
441
437
|
return {
|
|
442
438
|
content: receiptText,
|
|
443
|
-
details: { type: "send-message", status: "delivered_running", disposition: delivered.disposition, to, task_id: targetId,
|
|
439
|
+
details: { type: "send-message", status: "delivered_running", disposition: delivered.disposition, to, task_id: targetId, summary },
|
|
444
440
|
};
|
|
445
441
|
}
|
|
446
442
|
if (delivered.reason === "no_channel") {
|
|
@@ -531,16 +527,15 @@ export function createSendMessageTool(opts) {
|
|
|
531
527
|
...(row.rootSessionId !== undefined ? { rowRootSessionId: row.rootSessionId } : {}),
|
|
532
528
|
...(opts.notify ? { currentParentNotify: opts.notify } : {}),
|
|
533
529
|
});
|
|
534
|
-
const
|
|
535
|
-
const fromPrefix = parentTaskId !== undefined ? `(message from teammate "${senderName ?? senderId ?? "unknown"}")\n` : "";
|
|
530
|
+
const fromPrefix = senderIsChild ? `(message from teammate "${senderLabel}")\n` : "";
|
|
536
531
|
try {
|
|
537
|
-
const safeSummary =
|
|
532
|
+
const safeSummary = escapeEnvelopeTag(TEAMMATE_MESSAGE_TAG, summary);
|
|
538
533
|
const safeMessage = escapeEnvelopeTag(TEAMMATE_MESSAGE_TAG, message);
|
|
539
|
-
const marker = await resume(`${fromPrefix}
|
|
534
|
+
const marker = await resume(`${fromPrefix}[${safeSummary}] ${safeMessage}`);
|
|
540
535
|
return {
|
|
541
536
|
content: `Message sent — ${who} resumed in the background with its prior context intact (correlation marker [${marker}]).\n` +
|
|
542
537
|
`You will be notified automatically when it completes; its reply will carry [${marker}]. Continue with other work — do not poll.`,
|
|
543
|
-
details: { type: "send-message", status: "resumed", to, task_id: targetId, marker,
|
|
538
|
+
details: { type: "send-message", status: "resumed", to, task_id: targetId, marker, summary },
|
|
544
539
|
};
|
|
545
540
|
}
|
|
546
541
|
catch (e) {
|
package/dist/agents/subagent.js
CHANGED
|
@@ -1372,8 +1372,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1372
1372
|
const editedFiles = stepRecorder.editedFiles();
|
|
1373
1373
|
return { ...(recentSteps ? { recentSteps } : {}), ...(editedFiles ? { editedFiles } : {}) };
|
|
1374
1374
|
};
|
|
1375
|
+
const treeScope = reviveClaim?.row.scope ?? ctx.principal ?? opts.background?.scope;
|
|
1375
1376
|
const childInternals = {
|
|
1376
1377
|
...(inheritedManifestScope ? { inheritedManifestScope } : {}),
|
|
1378
|
+
...(treeScope !== undefined ? { registryScope: treeScope } : {}),
|
|
1377
1379
|
...(ctx.inheritedGateForChildren ? { inheritedGate: ctx.inheritedGateForChildren() } : {}),
|
|
1378
1380
|
...(childDefaultPersona !== undefined ? { defaultSystemPrompt: childDefaultPersona } : {}),
|
|
1379
1381
|
isDelegatedChild: true,
|
|
@@ -1632,7 +1634,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1632
1634
|
}
|
|
1633
1635
|
const shortDesc = `fork: ${(typeof a.description === "string" && a.description.trim() ? a.description.trim() : prompt).slice(0, 180)}`;
|
|
1634
1636
|
const bgOwner = sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
|
|
1635
|
-
const bgScope =
|
|
1637
|
+
const bgScope = treeScope;
|
|
1636
1638
|
let taskId;
|
|
1637
1639
|
try {
|
|
1638
1640
|
taskId = bg.registry.registerBackgroundAgent({
|
|
@@ -2037,7 +2039,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2037
2039
|
}
|
|
2038
2040
|
const shortDesc = reviveRow?.description ?? String(a.description ?? "sub-agent").slice(0, 200);
|
|
2039
2041
|
const bgOwner = reviveRow !== undefined ? reviveRow.owner : sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
|
|
2040
|
-
const bgScope =
|
|
2042
|
+
const bgScope = treeScope;
|
|
2041
2043
|
let taskId;
|
|
2042
2044
|
try {
|
|
2043
2045
|
taskId = bg.registry.registerBackgroundAgent(reviveRow !== undefined
|
|
@@ -242,6 +242,7 @@ export interface CheckpointSummary {
|
|
|
242
242
|
contentKind?: "content_ask";
|
|
243
243
|
createdAt?: number;
|
|
244
244
|
toolInput?: string;
|
|
245
|
+
restoreMode?: "snapshot" | "park_only";
|
|
245
246
|
preview?: unknown;
|
|
246
247
|
}
|
|
247
248
|
export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
|
|
@@ -181,6 +181,9 @@ export function summarizeCheckpoint(cp) {
|
|
|
181
181
|
...(tool !== undefined ? { toolCallId: tool.toolCallId, toolName: tool.toolName } : {}),
|
|
182
182
|
...(tool?.toolName === ASK_USER_QUESTION_TOOL_NAME ? { contentKind: "content_ask" } : {}),
|
|
183
183
|
...(toolInput !== undefined ? { toolInput } : {}),
|
|
184
|
+
...(cp.state.workspaceHandle !== undefined
|
|
185
|
+
? { restoreMode: cp.state.workspaceHandle.restoreMode === "park_only" ? "park_only" : "snapshot" }
|
|
186
|
+
: {}),
|
|
184
187
|
...(tool?.preview !== undefined ? { preview: tool.preview } : {}),
|
|
185
188
|
};
|
|
186
189
|
}
|
|
@@ -23,8 +23,10 @@ export async function consolidateScope(scope, deps, opts = {}) {
|
|
|
23
23
|
"(getConsolidationCursor/setConsolidationCursor) — skipped (no-op), NOT degraded to a full re-consolidation"));
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
|
-
if (!supportsConsolidation(store) ||
|
|
27
|
-
|
|
26
|
+
if (!supportsConsolidation(store) ||
|
|
27
|
+
typeof store.listStructuredNotes !== "function" ||
|
|
28
|
+
typeof store.getByIds !== "function") {
|
|
29
|
+
onWarn?.(new Error("consolidateScope: store is not id-addressable / has no manifest read pair — skipped (no-op)"));
|
|
28
30
|
return undefined;
|
|
29
31
|
}
|
|
30
32
|
const release = deps.acquire ? await deps.acquire(scope) : () => { };
|
|
@@ -76,29 +76,28 @@ export function clearStaleToolResults(messages, opts) {
|
|
|
76
76
|
}
|
|
77
77
|
const keep = opts.keepRecentToolResults ?? 3;
|
|
78
78
|
const compactable = opts.compactableTools ?? COMPACTABLE_TOOLS;
|
|
79
|
-
const
|
|
80
|
-
const clearable =
|
|
79
|
+
const toolResultCandidates = messages.flatMap((m, i) => isToolResult(m) && !isCleared(m) && compactable.has(m.toolName) ? [{ idx: i, target: m }] : []);
|
|
80
|
+
const clearable = toolResultCandidates.slice(0, Math.max(0, toolResultCandidates.length - keep));
|
|
81
81
|
if (clearable.length === 0) {
|
|
82
82
|
return messages;
|
|
83
83
|
}
|
|
84
84
|
const out = messages.slice();
|
|
85
85
|
let current = total;
|
|
86
|
-
for (const idx of clearable) {
|
|
86
|
+
for (const { idx, target } of clearable) {
|
|
87
87
|
if (current <= opts.budgetTokens) {
|
|
88
88
|
break;
|
|
89
89
|
}
|
|
90
|
-
const before = estimateTokens(
|
|
91
|
-
const
|
|
92
|
-
const rawContent = Array.isArray(msg.content) ? msg.content : [];
|
|
90
|
+
const before = estimateTokens(target, cpt);
|
|
91
|
+
const rawContent = Array.isArray(target.content) ? target.content : [];
|
|
93
92
|
let ref;
|
|
94
93
|
if (opts.offload) {
|
|
95
94
|
const fullText = rawContent
|
|
96
95
|
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
97
96
|
.map((c) => c.text)
|
|
98
97
|
.join("\n");
|
|
99
|
-
if (
|
|
98
|
+
if (target.toolCallId && fullText.trim().length > 0) {
|
|
100
99
|
try {
|
|
101
|
-
ref = refNote(opts.offload.persist(
|
|
100
|
+
ref = refNote(opts.offload.persist(target.toolCallId, fullText));
|
|
102
101
|
}
|
|
103
102
|
catch {
|
|
104
103
|
ref = undefined;
|
|
@@ -107,10 +106,7 @@ export function clearStaleToolResults(messages, opts) {
|
|
|
107
106
|
}
|
|
108
107
|
const mediaBlocks = rawContent.filter((c) => c?.type !== "text");
|
|
109
108
|
const marker = clearedMarker([ref, mediaBlocks.length > 0 ? mediaNote(mediaBlocks) : undefined]);
|
|
110
|
-
const cleared = {
|
|
111
|
-
...out[idx],
|
|
112
|
-
content: [{ type: "text", text: marker }],
|
|
113
|
-
};
|
|
109
|
+
const cleared = { ...target, content: [{ type: "text", text: marker }] };
|
|
114
110
|
out[idx] = cleared;
|
|
115
111
|
current -= idx > anchorIdx ? before - estimateTokens(cleared, cpt) : 0;
|
|
116
112
|
}
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface McpServerStatus {
|
|
|
57
57
|
};
|
|
58
58
|
toolNames?: string[];
|
|
59
59
|
error?: string;
|
|
60
|
+
transportClosed?: boolean;
|
|
60
61
|
}
|
|
61
62
|
export declare const MCP_PREFIX = "mcp__";
|
|
62
63
|
export declare function resolveMcpDeclaredResultSize(meta: Record<string, unknown> | undefined): number | undefined;
|
|
@@ -69,6 +70,7 @@ export declare const MCP_IDLE_TIMEOUT_STDIO_DEFAULT_MS: number;
|
|
|
69
70
|
export declare const MCP_IDLE_TIMEOUT_HTTP_DEFAULT_MS: number;
|
|
70
71
|
export declare function mcpIdleTimeoutMs(kind: "stdio" | "http"): number;
|
|
71
72
|
export declare function describeMcpSpecErrorCode(code: unknown): string | undefined;
|
|
73
|
+
export declare function collapseMcpErrorPrefix(message: string): string;
|
|
72
74
|
export declare function normalizeMcpName(name: string): string;
|
|
73
75
|
export declare function clampNameSegment(seg: string, max?: number): string;
|
|
74
76
|
export * from "./image-downsample.js";
|
package/dist/core/mcp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
3
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
|
+
import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
5
|
import { lstat, mkdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
@@ -145,8 +145,24 @@ const MCP_SPEC_ERROR_CODE_NAMES = new Map([
|
|
|
145
145
|
export function describeMcpSpecErrorCode(code) {
|
|
146
146
|
return typeof code === "number" ? MCP_SPEC_ERROR_CODE_NAMES.get(code) : undefined;
|
|
147
147
|
}
|
|
148
|
+
export function collapseMcpErrorPrefix(message) {
|
|
149
|
+
let out = message;
|
|
150
|
+
for (;;) {
|
|
151
|
+
const m = /^MCP error (-?\d+): (?=MCP error \1: )/.exec(out);
|
|
152
|
+
if (m === null)
|
|
153
|
+
return out;
|
|
154
|
+
out = out.slice(m[0].length);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function collapseMcpErrorStampInPlace(err) {
|
|
158
|
+
if (!(err instanceof McpError))
|
|
159
|
+
return;
|
|
160
|
+
const collapsed = collapseMcpErrorPrefix(err.message);
|
|
161
|
+
if (collapsed !== err.message)
|
|
162
|
+
err.message = collapsed;
|
|
163
|
+
}
|
|
148
164
|
function namedMcpFailureText(err) {
|
|
149
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
165
|
+
const detail = err instanceof Error ? collapseMcpErrorPrefix(err.message) : String(err);
|
|
150
166
|
const condition = err instanceof McpError ? describeMcpSpecErrorCode(err.code) : undefined;
|
|
151
167
|
return condition !== undefined ? `${condition}: ${detail}` : detail;
|
|
152
168
|
}
|
|
@@ -155,6 +171,53 @@ function isTransportLost(err) {
|
|
|
155
171
|
return true;
|
|
156
172
|
return err instanceof Error && /not connected|connection closed/i.test(err.message);
|
|
157
173
|
}
|
|
174
|
+
const NETWORK_CODES_NEVER_DELIVERED = new Set([
|
|
175
|
+
"ECONNREFUSED",
|
|
176
|
+
"ENOTFOUND",
|
|
177
|
+
"EAI_AGAIN",
|
|
178
|
+
"EHOSTUNREACH",
|
|
179
|
+
"ENETUNREACH",
|
|
180
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
181
|
+
]);
|
|
182
|
+
function networkErrorCode(err, depth = 0) {
|
|
183
|
+
if (depth > 5 || !(err instanceof Error))
|
|
184
|
+
return undefined;
|
|
185
|
+
const code = err.code;
|
|
186
|
+
if (typeof code === "string" && /^(?:E[A-Z]+|UND_ERR_[A-Z_]+)$/.test(code))
|
|
187
|
+
return code;
|
|
188
|
+
if (err instanceof AggregateError) {
|
|
189
|
+
for (const inner of err.errors) {
|
|
190
|
+
const found = networkErrorCode(inner, depth + 1);
|
|
191
|
+
if (found !== undefined)
|
|
192
|
+
return found;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return networkErrorCode(err.cause, depth + 1);
|
|
196
|
+
}
|
|
197
|
+
function describeHttpTransportFailure(err) {
|
|
198
|
+
if (err instanceof McpError)
|
|
199
|
+
return undefined;
|
|
200
|
+
if (err instanceof StreamableHTTPError) {
|
|
201
|
+
const status = typeof err.code === "number" && err.code > 0 ? err.code : undefined;
|
|
202
|
+
return {
|
|
203
|
+
condition: status !== undefined
|
|
204
|
+
? `its HTTP endpoint answered ${status} instead of an MCP response`
|
|
205
|
+
: "its HTTP endpoint answered something that is not an MCP response",
|
|
206
|
+
delivered: "unknown",
|
|
207
|
+
...(status !== undefined ? { httpStatus: status } : {}),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const code = networkErrorCode(err);
|
|
211
|
+
if (code !== undefined) {
|
|
212
|
+
return NETWORK_CODES_NEVER_DELIVERED.has(code)
|
|
213
|
+
? { condition: `its HTTP endpoint could not be reached (${code})`, delivered: "no" }
|
|
214
|
+
: { condition: `the connection to its HTTP endpoint failed (${code})`, delivered: "unknown" };
|
|
215
|
+
}
|
|
216
|
+
if (err instanceof TypeError && /fetch failed|terminated|network/i.test(err.message)) {
|
|
217
|
+
return { condition: "the HTTP request to its endpoint failed at the network layer", delivered: "unknown" };
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
158
221
|
function writeEffectWarning(writeEffect) {
|
|
159
222
|
return writeEffect
|
|
160
223
|
? " This tool is write-capable: treat its side effects as POSSIBLY APPLIED and verify the actual state before retrying."
|
|
@@ -173,6 +236,7 @@ function rethrowHonestMcpError(err, ctx) {
|
|
|
173
236
|
}
|
|
174
237
|
if (ctx.signal?.aborted)
|
|
175
238
|
throw err;
|
|
239
|
+
collapseMcpErrorStampInPlace(err);
|
|
176
240
|
const serverLabel = inlineUntrusted(ctx.server);
|
|
177
241
|
if (err instanceof McpError && err.code === ErrorCode.RequestTimeout) {
|
|
178
242
|
const data = err.data;
|
|
@@ -193,6 +257,21 @@ function rethrowHonestMcpError(err, ctx) {
|
|
|
193
257
|
e.details = { transportLost: true, server: ctx.server };
|
|
194
258
|
throw e;
|
|
195
259
|
}
|
|
260
|
+
const httpFailure = describeHttpTransportFailure(err);
|
|
261
|
+
if (httpFailure !== undefined) {
|
|
262
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
263
|
+
const fenced = `\nThe transport error follows as external/untrusted data:\n${delimitUntrusted(`${ctx.server} transport error`, truncateMcpErrorText(detail))}`;
|
|
264
|
+
const e = new Error(httpFailure.delivered === "no"
|
|
265
|
+
? `${ctx.what} could not reach MCP server "${serverLabel}": ${httpFailure.condition}. The request was not delivered, so the server did not execute it. This server's tools and resources will keep failing until its endpoint is reachable again — do not retry them; use an alternative if one exists.${fenced}`
|
|
266
|
+
: `${ctx.what} failed at the transport layer of MCP server "${serverLabel}": ${httpFailure.condition}. The request may or may not have executed on the server — the outcome is unknown.${writeEffectWarning(ctx.writeEffect)}${fenced}`, { cause: err });
|
|
267
|
+
e.errorKind = httpFailure.delivered === "no" ? "server_disconnected" : "transport_lost";
|
|
268
|
+
e.details = {
|
|
269
|
+
server: ctx.server,
|
|
270
|
+
...(httpFailure.delivered === "no" ? {} : { transportLost: true }),
|
|
271
|
+
...(httpFailure.httpStatus !== undefined ? { httpStatus: httpFailure.httpStatus } : {}),
|
|
272
|
+
};
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
196
275
|
if (err instanceof McpError) {
|
|
197
276
|
const condition = describeMcpSpecErrorCode(err.code);
|
|
198
277
|
if (condition !== undefined) {
|
|
@@ -546,12 +625,16 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
|
|
|
546
625
|
serverInstructions.push({ server: spec.name, text: s.instructions });
|
|
547
626
|
}
|
|
548
627
|
const hasInstructions = s.instructions !== undefined;
|
|
549
|
-
|
|
550
|
-
if (
|
|
628
|
+
const announceClose = () => {
|
|
629
|
+
if (disposing)
|
|
630
|
+
return;
|
|
631
|
+
s.status.transportClosed = true;
|
|
632
|
+
if (hasInstructions)
|
|
551
633
|
instructionsDelta.pendingRemovals.push(spec.name);
|
|
552
634
|
};
|
|
553
|
-
|
|
554
|
-
|
|
635
|
+
s.announce.fn = announceClose;
|
|
636
|
+
if (s.health.dead)
|
|
637
|
+
announceClose();
|
|
555
638
|
if (s.resourceServer)
|
|
556
639
|
resourceServers.push(s.resourceServer);
|
|
557
640
|
for (const d of s.dropped)
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -47,8 +47,10 @@ export interface MemoryNoteHeader {
|
|
|
47
47
|
export interface MemoryNoteRecord extends MemoryNoteHeader {
|
|
48
48
|
text: string;
|
|
49
49
|
}
|
|
50
|
-
export
|
|
51
|
-
export declare function
|
|
50
|
+
export type ConsolidationCapableStore = MemoryStore & Required<Pick<MemoryStore, "searchScored" | "update" | "delete">>;
|
|
51
|
+
export declare function supportsConsolidation(store: MemoryStore): store is ConsolidationCapableStore;
|
|
52
|
+
export type PeriodicConsolidationCapableStore = MemoryStore & Required<Pick<MemoryStore, "getConsolidationCursor" | "setConsolidationCursor">>;
|
|
53
|
+
export declare function supportsPeriodicConsolidation(store: MemoryStore): store is PeriodicConsolidationCapableStore;
|
|
52
54
|
export declare const CALLER_AUTHORED_TYPES: ReadonlySet<string>;
|
|
53
55
|
export declare const DEFAULT_CALLER_AUTHORED_TYPE: MemoryNoteType;
|
|
54
56
|
export type UtilityGate = (stats: unknown) => boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { AgentMessage } from "../internal/harness-types.js";
|
|
2
|
+
import type { ToolResultMessage } from "../internal/llm.js";
|
|
2
3
|
export declare function messageRole(m: AgentMessage): string | undefined;
|
|
3
|
-
export declare function isToolResult(m: AgentMessage):
|
|
4
|
+
export declare function isToolResult(m: AgentMessage): m is ToolResultMessage;
|
|
@@ -44,6 +44,8 @@ export interface ExecStreamOptions {
|
|
|
44
44
|
maxOutputBytes?: number;
|
|
45
45
|
}
|
|
46
46
|
export type RemoteExecutionErrorCode = "suspended" | "command_in_flight" | "connect_failed" | "post_resume_failed" | "aborted" | "timeout" | "unsupported" | "auth_transient" | "auth_failed" | "transport_lost" | "unknown";
|
|
47
|
+
export declare const RETRYABLE_REMOTE_ERROR_CODES: readonly RemoteExecutionErrorCode[];
|
|
48
|
+
export declare function isRetryableRemoteErrorCode(code: RemoteExecutionErrorCode): boolean;
|
|
47
49
|
export declare class RemoteExecutionError extends Error {
|
|
48
50
|
readonly code: RemoteExecutionErrorCode;
|
|
49
51
|
constructor(code: RemoteExecutionErrorCode, message: string, cause?: Error);
|
|
@@ -101,5 +103,6 @@ export interface ExecutionEnvFactoryContext {
|
|
|
101
103
|
export type ExecutionEnvFactory = (ctx: ExecutionEnvFactoryContext) => ExecutionEnv | Promise<ExecutionEnv>;
|
|
102
104
|
export declare function hasDestroy(env: ExecutionEnv): env is ExecutionEnv & Pick<RemoteExecutionEnv, "destroy">;
|
|
103
105
|
export declare function isRemoteExecutionEnv(env: ExecutionEnv): env is RemoteExecutionEnv;
|
|
106
|
+
export declare function missingRestoreSurface(env: ExecutionEnv): readonly ("resumeVM" | "postResumeInit")[];
|
|
104
107
|
export declare function isSuspendable(env: ExecutionEnv): env is RemoteExecutionEnv;
|
|
105
108
|
export declare function isIsolated(env: ExecutionEnv): boolean;
|
package/dist/core/remote-env.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export const RETRYABLE_REMOTE_ERROR_CODES = [
|
|
2
|
+
"auth_transient",
|
|
3
|
+
"connect_failed",
|
|
4
|
+
"timeout",
|
|
5
|
+
"transport_lost",
|
|
6
|
+
];
|
|
7
|
+
export function isRetryableRemoteErrorCode(code) {
|
|
8
|
+
return RETRYABLE_REMOTE_ERROR_CODES.includes(code);
|
|
9
|
+
}
|
|
1
10
|
export class RemoteExecutionError extends Error {
|
|
2
11
|
code;
|
|
3
12
|
constructor(code, message, cause) {
|
|
@@ -18,8 +27,17 @@ export function isRemoteExecutionEnv(env) {
|
|
|
18
27
|
typeof e.capabilities === "object" &&
|
|
19
28
|
e.capabilities !== null);
|
|
20
29
|
}
|
|
30
|
+
export function missingRestoreSurface(env) {
|
|
31
|
+
const e = env;
|
|
32
|
+
const missing = [];
|
|
33
|
+
if (typeof e.resumeVM !== "function")
|
|
34
|
+
missing.push("resumeVM");
|
|
35
|
+
if (typeof e.postResumeInit !== "function")
|
|
36
|
+
missing.push("postResumeInit");
|
|
37
|
+
return missing;
|
|
38
|
+
}
|
|
21
39
|
export function isSuspendable(env) {
|
|
22
|
-
return isRemoteExecutionEnv(env) && env.capabilities.suspendable;
|
|
40
|
+
return isRemoteExecutionEnv(env) && env.capabilities.suspendable && missingRestoreSurface(env).length === 0;
|
|
23
41
|
}
|
|
24
42
|
export function isIsolated(env) {
|
|
25
43
|
return isRemoteExecutionEnv(env) ? env.capabilities.isolation : false;
|
|
@@ -70,6 +70,7 @@ export interface ResultFlags {
|
|
|
70
70
|
model?: string;
|
|
71
71
|
unpricedSpend?: boolean;
|
|
72
72
|
rewindNotes?: TaskResult["rewindNotes"];
|
|
73
|
+
remoteEnvFailures?: TaskResult["remoteEnvFailures"];
|
|
73
74
|
abortedForTimeout: boolean;
|
|
74
75
|
abortedForTurns: boolean;
|
|
75
76
|
abortedLive?: boolean;
|
|
@@ -81,10 +82,12 @@ export interface ResultFlags {
|
|
|
81
82
|
suspendRef?: {
|
|
82
83
|
token: import("../checkpoint-store.js").CheckpointToken;
|
|
83
84
|
gate: import("../checkpoint-store.js").CheckpointGate;
|
|
85
|
+
restoreMode?: "snapshot" | "park_only";
|
|
84
86
|
};
|
|
85
87
|
reviewRef?: {
|
|
86
88
|
token: import("../checkpoint-store.js").CheckpointToken;
|
|
87
89
|
gate: import("../checkpoint-store.js").CheckpointGate;
|
|
90
|
+
restoreMode?: "snapshot" | "park_only";
|
|
88
91
|
};
|
|
89
92
|
}
|
|
90
93
|
export declare function errorCodeOf(err: unknown): string | undefined;
|
|
@@ -54,6 +54,7 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
54
54
|
let salvagedOutput;
|
|
55
55
|
let checkpointToken;
|
|
56
56
|
let checkpointGate;
|
|
57
|
+
let workspaceRestoreMode;
|
|
57
58
|
const isDegenerate = final?.stopReason === "error" && final.errorMessage === DEGENERATE_MESSAGE;
|
|
58
59
|
const isWalltimeCutoff = final?.stopReason === "error" && final.errorMessage === WALLTIME_CUTOFF_MESSAGE;
|
|
59
60
|
if (flags.outputInvalid) {
|
|
@@ -105,12 +106,14 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
105
106
|
status = "suspended";
|
|
106
107
|
checkpointToken = flags.suspendRef.token;
|
|
107
108
|
checkpointGate = flags.suspendRef.gate;
|
|
109
|
+
workspaceRestoreMode = flags.suspendRef.restoreMode;
|
|
108
110
|
}
|
|
109
111
|
else if (flags.reviewRef) {
|
|
110
112
|
status = "needs_review";
|
|
111
113
|
errorCode = "review.pending";
|
|
112
114
|
checkpointToken = flags.reviewRef.token;
|
|
113
115
|
checkpointGate = flags.reviewRef.gate;
|
|
116
|
+
workspaceRestoreMode = flags.reviewRef.restoreMode;
|
|
114
117
|
}
|
|
115
118
|
else if (flags.abortedLive || final?.stopReason === "aborted") {
|
|
116
119
|
status = flags.abortedForTimeout ? "timeout" : "failed";
|
|
@@ -143,5 +146,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
143
146
|
void _internalCompaction;
|
|
144
147
|
if (flags.unpricedSpend)
|
|
145
148
|
delete publicStats.costMicroUsd;
|
|
146
|
-
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), stats: publicStats };
|
|
149
|
+
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), stats: publicStats };
|
|
147
150
|
}
|
|
@@ -127,12 +127,15 @@ export interface Prepared {
|
|
|
127
127
|
token?: CheckpointToken;
|
|
128
128
|
gate?: CheckpointGate;
|
|
129
129
|
scope?: string;
|
|
130
|
+
restoreMode?: "snapshot" | "park_only";
|
|
130
131
|
};
|
|
131
132
|
reviewRef: {
|
|
132
133
|
token?: CheckpointToken;
|
|
133
134
|
gate?: CheckpointGate;
|
|
134
135
|
scope?: string;
|
|
136
|
+
restoreMode?: "snapshot" | "park_only";
|
|
135
137
|
};
|
|
138
|
+
remoteEnvFailures: NonNullable<TaskResult["remoteEnvFailures"]>;
|
|
136
139
|
suspendLoopRef: {
|
|
137
140
|
hit: boolean;
|
|
138
141
|
};
|
|
@@ -308,6 +311,7 @@ export interface RunInternals {
|
|
|
308
311
|
parentTaskId?: string;
|
|
309
312
|
parentSessionId?: string;
|
|
310
313
|
rootSessionId?: string;
|
|
314
|
+
registryScope?: string;
|
|
311
315
|
parentCenterArtifactDigest?: string;
|
|
312
316
|
parentCenterSourceRevision?: string;
|
|
313
317
|
promptProfile?: "simple" | "classic";
|
|
@@ -339,4 +343,5 @@ export declare function batchContextAt(messages: AgentMessage[], currentId: stri
|
|
|
339
343
|
completedCallIds: string[];
|
|
340
344
|
};
|
|
341
345
|
export declare function rebaseWorkspacePath(p: string, fromRaw: string, toRaw: string): string;
|
|
346
|
+
export declare function rebaseWorkspacePathAcross(p: string, froms: readonly string[], to: string): string;
|
|
342
347
|
export declare function prepareTask(spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, internals?: RunInternals, runnerSelf?: Runner): Promise<Prepared>;
|