@sema-agent/core 2.9.0 → 2.11.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 +80 -3
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/defaults.js +1 -0
- package/dist/core/consolidate-scope.js +4 -2
- package/dist/core/context-edit.js +8 -12
- package/dist/core/memory.d.ts +4 -2
- package/dist/core/message-utils.d.ts +2 -1
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +3 -1
- package/dist/core/runner/prepare-task.js +55 -24
- package/dist/core/runner/runtask.js +18 -5
- package/dist/core/runner/tool-disclosure.d.ts +1 -1
- package/dist/core/runner/tool-disclosure.js +18 -3
- 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/task-registry-agent.js +3 -0
- package/dist/core/tool-result-budget.js +2 -0
- package/dist/core/types.d.ts +4 -0
- package/dist/engine/session/session.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow.js +18 -10
- 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/web.d.ts +15 -0
- package/dist/tools/web.js +42 -0
- 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
|
@@ -17,7 +17,7 @@ import { addWorktree } from "../core/git-worktree-env.js";
|
|
|
17
17
|
import { shellQuote } from "../tools/fs/search.js";
|
|
18
18
|
import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
|
|
19
19
|
import { extractErrorCode } from "../brain/errors.js";
|
|
20
|
-
import { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../config/defaults.js";
|
|
20
|
+
import { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
|
|
21
21
|
export { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
|
|
22
22
|
import { SUBAGENT_RESUME_CAP, SubagentRetainLedger, getOrCreateSessionRetainLedger, ensureSessionReapHook, createResumePrompt, } from "./retain-ledger.js";
|
|
23
23
|
import { recordRosterSpawn } from "./roster-store.js";
|
|
@@ -227,6 +227,21 @@ function errorKindClause(c) {
|
|
|
227
227
|
const FAILED_SESSION_RETAIN_TTL_MS = 15 * 60 * 1000;
|
|
228
228
|
const PARTIAL_FINDINGS_MAX_CHARS = 1200;
|
|
229
229
|
const BG_NOTIFY_DRAIN_WINDOW_MS = 2_000;
|
|
230
|
+
function createBgActivityBeat(parentToolCallId, emitTick) {
|
|
231
|
+
let beats = 0;
|
|
232
|
+
let starts = 0;
|
|
233
|
+
return (e) => {
|
|
234
|
+
if (e.type !== "tool_start" && e.type !== "tool_end")
|
|
235
|
+
return;
|
|
236
|
+
if (parentToolCallId === undefined || e.parentToolCallId !== parentToolCallId)
|
|
237
|
+
return;
|
|
238
|
+
if (e.type === "tool_start")
|
|
239
|
+
starts += 1;
|
|
240
|
+
beats += 1;
|
|
241
|
+
if (beats === 1 || beats % RUNNING_AGENT_OBSERVE_EVERY_BEATS === 0)
|
|
242
|
+
emitTick(starts);
|
|
243
|
+
};
|
|
244
|
+
}
|
|
230
245
|
function markerFragment() {
|
|
231
246
|
return uuidv7().replace(/-/g, "").slice(-12);
|
|
232
247
|
}
|
|
@@ -351,6 +366,25 @@ export function createSubagentResume(deps) {
|
|
|
351
366
|
}
|
|
352
367
|
}
|
|
353
368
|
reviveStartedAt = Date.now();
|
|
369
|
+
const reviveActivityBeat = createBgActivityBeat(deps.parentToolCallId, (toolStarts) => {
|
|
370
|
+
if (reviveEmit === undefined)
|
|
371
|
+
return;
|
|
372
|
+
const currentAction = resumeStepRecorder.currentAction();
|
|
373
|
+
const currentTool = resumeStepRecorder.currentActionStructured();
|
|
374
|
+
reviveEmit({
|
|
375
|
+
kind: "tick",
|
|
376
|
+
taskId: deps.taskId,
|
|
377
|
+
sessionScoped: deps.sessionScoped === true,
|
|
378
|
+
...(deps.rowAgentType !== undefined ? { agentType: deps.rowAgentType } : {}),
|
|
379
|
+
transcriptId: entry.childSessionId,
|
|
380
|
+
sessionId: entry.childSessionId,
|
|
381
|
+
...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
|
|
382
|
+
progressTaskId: entry.childSessionId,
|
|
383
|
+
...(currentAction !== undefined ? { currentAction } : {}),
|
|
384
|
+
...(currentTool !== undefined ? { currentTool } : {}),
|
|
385
|
+
usage: { toolUses: toolStarts },
|
|
386
|
+
});
|
|
387
|
+
});
|
|
354
388
|
stream = childRunner.runTaskStream(resumeSpec, undefined, {
|
|
355
389
|
...entry.internalsSnapshot,
|
|
356
390
|
...(true
|
|
@@ -362,6 +396,7 @@ export function createSubagentResume(deps) {
|
|
|
362
396
|
}
|
|
363
397
|
catch {
|
|
364
398
|
}
|
|
399
|
+
reviveActivityBeat(e);
|
|
365
400
|
if (reviveEmit !== undefined && e.type === "task_progress") {
|
|
366
401
|
reviveEmit({
|
|
367
402
|
kind: "tick",
|
|
@@ -1337,8 +1372,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1337
1372
|
const editedFiles = stepRecorder.editedFiles();
|
|
1338
1373
|
return { ...(recentSteps ? { recentSteps } : {}), ...(editedFiles ? { editedFiles } : {}) };
|
|
1339
1374
|
};
|
|
1375
|
+
const treeScope = reviveClaim?.row.scope ?? ctx.principal ?? opts.background?.scope;
|
|
1340
1376
|
const childInternals = {
|
|
1341
1377
|
...(inheritedManifestScope ? { inheritedManifestScope } : {}),
|
|
1378
|
+
...(treeScope !== undefined ? { registryScope: treeScope } : {}),
|
|
1342
1379
|
...(ctx.inheritedGateForChildren ? { inheritedGate: ctx.inheritedGateForChildren() } : {}),
|
|
1343
1380
|
...(childDefaultPersona !== undefined ? { defaultSystemPrompt: childDefaultPersona } : {}),
|
|
1344
1381
|
isDelegatedChild: true,
|
|
@@ -1597,7 +1634,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1597
1634
|
}
|
|
1598
1635
|
const shortDesc = `fork: ${(typeof a.description === "string" && a.description.trim() ? a.description.trim() : prompt).slice(0, 180)}`;
|
|
1599
1636
|
const bgOwner = sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
|
|
1600
|
-
const bgScope =
|
|
1637
|
+
const bgScope = treeScope;
|
|
1601
1638
|
let taskId;
|
|
1602
1639
|
try {
|
|
1603
1640
|
taskId = bg.registry.registerBackgroundAgent({
|
|
@@ -1698,6 +1735,23 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1698
1735
|
const s2ForkNotifyReady = (inject) => {
|
|
1699
1736
|
bg.registry.attachAgentNotify(taskId, inject);
|
|
1700
1737
|
};
|
|
1738
|
+
const bgForkActivityBeat = createBgActivityBeat(ctx.toolCallId, (toolStarts) => {
|
|
1739
|
+
const currentAction = stepRecorder.currentAction();
|
|
1740
|
+
const currentTool = stepRecorder.currentActionStructured();
|
|
1741
|
+
sinkEmit({
|
|
1742
|
+
kind: "tick",
|
|
1743
|
+
taskId,
|
|
1744
|
+
sessionScoped: sessionScopedBg === true,
|
|
1745
|
+
transcriptId: forkedId,
|
|
1746
|
+
sessionId: forkedId,
|
|
1747
|
+
parentToolCallId: ctx.toolCallId,
|
|
1748
|
+
progressTaskId: forkedId,
|
|
1749
|
+
agentType: spawnAgentType,
|
|
1750
|
+
...(currentAction !== undefined ? { currentAction } : {}),
|
|
1751
|
+
...(currentTool !== undefined ? { currentTool } : {}),
|
|
1752
|
+
usage: { toolUses: toolStarts },
|
|
1753
|
+
});
|
|
1754
|
+
});
|
|
1701
1755
|
const bgForkInternals = bgSink
|
|
1702
1756
|
? {
|
|
1703
1757
|
...forkInternals,
|
|
@@ -1708,6 +1762,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1708
1762
|
}
|
|
1709
1763
|
catch {
|
|
1710
1764
|
}
|
|
1765
|
+
bgForkActivityBeat(e);
|
|
1711
1766
|
if (e.type === "task_progress") {
|
|
1712
1767
|
const currentAction = stepRecorder.currentAction();
|
|
1713
1768
|
const currentTool = stepRecorder.currentActionStructured();
|
|
@@ -1764,6 +1819,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1764
1819
|
? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
|
|
1765
1820
|
: undefined;
|
|
1766
1821
|
const settledBg = bg.registry.settleBackgroundAgent(taskId, {
|
|
1822
|
+
cycle: 0,
|
|
1767
1823
|
status: okBg ? "completed" : reapedBg ? "killed" : "failed",
|
|
1768
1824
|
...resultSettleFields(child.result),
|
|
1769
1825
|
...(!okBg ? { error: reapedBg ? (collateralBg ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : child.errorMessage ?? String(child.status) } : {}),
|
|
@@ -1835,6 +1891,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1835
1891
|
const errCodeForkReject = killed ? undefined : extractErrorCode(msgFork);
|
|
1836
1892
|
const errClassForkReject = killed ? undefined : classifySubagentError({ status: "failed", errorMessage: msgFork });
|
|
1837
1893
|
const settledBg = bg.registry.settleBackgroundAgent(taskId, {
|
|
1894
|
+
cycle: 0,
|
|
1838
1895
|
status: killed ? "killed" : "failed",
|
|
1839
1896
|
error: killed ? BG_AGENT_REAP_STOP_ERROR : msgFork,
|
|
1840
1897
|
...(errCodeForkReject !== undefined ? { errorCode: errCodeForkReject } : {}),
|
|
@@ -1982,7 +2039,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1982
2039
|
}
|
|
1983
2040
|
const shortDesc = reviveRow?.description ?? String(a.description ?? "sub-agent").slice(0, 200);
|
|
1984
2041
|
const bgOwner = reviveRow !== undefined ? reviveRow.owner : sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
|
|
1985
|
-
const bgScope =
|
|
2042
|
+
const bgScope = treeScope;
|
|
1986
2043
|
let taskId;
|
|
1987
2044
|
try {
|
|
1988
2045
|
taskId = bg.registry.registerBackgroundAgent(reviveRow !== undefined
|
|
@@ -2157,6 +2214,23 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2157
2214
|
bg.registry.finalizeParkedResume(taskId);
|
|
2158
2215
|
reviveAttachedResolve?.();
|
|
2159
2216
|
};
|
|
2217
|
+
const bgActivityBeat = createBgActivityBeat(ctx.toolCallId, (toolStarts) => {
|
|
2218
|
+
const currentAction = stepRecorder.currentAction();
|
|
2219
|
+
const currentTool = stepRecorder.currentActionStructured();
|
|
2220
|
+
sinkEmit({
|
|
2221
|
+
kind: "tick",
|
|
2222
|
+
taskId,
|
|
2223
|
+
sessionScoped: sessionScopedBg === true,
|
|
2224
|
+
transcriptId: bgChildSessionId,
|
|
2225
|
+
sessionId: bgChildSessionId,
|
|
2226
|
+
parentToolCallId: ctx.toolCallId,
|
|
2227
|
+
progressTaskId: bgChildSessionId,
|
|
2228
|
+
agentType: spawnAgentType,
|
|
2229
|
+
...(currentAction !== undefined ? { currentAction } : {}),
|
|
2230
|
+
...(currentTool !== undefined ? { currentTool } : {}),
|
|
2231
|
+
usage: { toolUses: toolStarts },
|
|
2232
|
+
});
|
|
2233
|
+
});
|
|
2160
2234
|
const bgInternals = bgSink
|
|
2161
2235
|
? {
|
|
2162
2236
|
...childInternals,
|
|
@@ -2167,6 +2241,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2167
2241
|
}
|
|
2168
2242
|
catch {
|
|
2169
2243
|
}
|
|
2244
|
+
bgActivityBeat(e);
|
|
2170
2245
|
if (e.type === "task_progress") {
|
|
2171
2246
|
const currentAction = stepRecorder.currentAction();
|
|
2172
2247
|
const currentTool = stepRecorder.currentActionStructured();
|
|
@@ -2400,6 +2475,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2400
2475
|
? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
|
|
2401
2476
|
: undefined;
|
|
2402
2477
|
const settled = bg.registry.settleBackgroundAgent(taskId, {
|
|
2478
|
+
cycle: 0,
|
|
2403
2479
|
status: ok ? "completed" : reaped ? "killed" : "failed",
|
|
2404
2480
|
seq: seqAtSettle ?? 1,
|
|
2405
2481
|
...resultSettleFields(child.result),
|
|
@@ -2496,6 +2572,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2496
2572
|
const errCodeBgReject = killed ? undefined : extractErrorCode(msg);
|
|
2497
2573
|
const errClassBgReject = killed ? undefined : classifySubagentError({ status: "failed", errorMessage: msg });
|
|
2498
2574
|
const settled = bg.registry.settleBackgroundAgent(taskId, {
|
|
2575
|
+
cycle: 0,
|
|
2499
2576
|
status: killed ? "killed" : "failed",
|
|
2500
2577
|
error: msg,
|
|
2501
2578
|
seq: seqAtSettle ?? 1,
|
|
@@ -4,3 +4,4 @@ export declare const SESSION_BG_DEFAULT_TIMEOUT_SEC: number;
|
|
|
4
4
|
export declare const RETAIN_DEFAULT_TTL_MS: number;
|
|
5
5
|
export declare const RETAIN_DEFAULT_MAX = 16;
|
|
6
6
|
export declare const SESSION_DEFAULT_TTL_DAYS = 7;
|
|
7
|
+
export declare const RUNNING_AGENT_OBSERVE_EVERY_BEATS = 4;
|
package/dist/config/defaults.js
CHANGED
|
@@ -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/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;
|
|
@@ -143,5 +143,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
143
143
|
void _internalCompaction;
|
|
144
144
|
if (flags.unpricedSpend)
|
|
145
145
|
delete publicStats.costMicroUsd;
|
|
146
|
-
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, stats: publicStats };
|
|
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 };
|
|
147
147
|
}
|
|
@@ -17,7 +17,7 @@ import { type CwdRef } from "../../tools/fs/index.js";
|
|
|
17
17
|
import type { Runner } from "./runtask.js";
|
|
18
18
|
import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type ResourceLimitReason } from "../checkpoint-store.js";
|
|
19
19
|
import type { AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
|
|
20
|
-
import type { RunnerDeps, TaskEvent, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
|
|
20
|
+
import type { RunnerDeps, TaskEvent, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
|
|
21
21
|
import type { RepairBundle } from "../../agents/repair-loop.js";
|
|
22
22
|
export declare const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
|
|
23
23
|
export declare function checkpointScopeOf(spec: {
|
|
@@ -48,6 +48,7 @@ export interface Prepared {
|
|
|
48
48
|
tasks: number;
|
|
49
49
|
costMicroUsd: number;
|
|
50
50
|
};
|
|
51
|
+
rewindNotes?: NonNullable<TaskResult["rewindNotes"]>;
|
|
51
52
|
cwdRef?: CwdRef;
|
|
52
53
|
worktreeSessionRef?: {
|
|
53
54
|
current?: {
|
|
@@ -307,6 +308,7 @@ export interface RunInternals {
|
|
|
307
308
|
parentTaskId?: string;
|
|
308
309
|
parentSessionId?: string;
|
|
309
310
|
rootSessionId?: string;
|
|
311
|
+
registryScope?: string;
|
|
310
312
|
parentCenterArtifactDigest?: string;
|
|
311
313
|
parentCenterSourceRevision?: string;
|
|
312
314
|
promptProfile?: "simple" | "classic";
|
|
@@ -409,7 +409,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
409
409
|
}));
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
|
-
const taskScope = spec.principal ?? "default";
|
|
412
|
+
const taskScope = internals?.registryScope ?? spec.principal ?? "default";
|
|
413
413
|
defaultTaskRegistry.maybeGc();
|
|
414
414
|
const forgetOnThrow = () => forgetQuietly(sessions, sessionId);
|
|
415
415
|
const extraBodyCollisions = reservedCollisions(model.extraBody, reservedFor(model.api));
|
|
@@ -616,8 +616,42 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
616
616
|
catch {
|
|
617
617
|
}
|
|
618
618
|
}
|
|
619
|
-
|
|
619
|
+
const rewindNotes = [];
|
|
620
|
+
const rewindCaptureRequested = spec.rewindFiles === true;
|
|
621
|
+
if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
|
|
622
|
+
const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when rewindFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
|
|
623
|
+
e.code = "rewind.conflicting_targets";
|
|
624
|
+
throw e;
|
|
625
|
+
}
|
|
626
|
+
let rewindTarget = spec.resumeAt !== undefined ? (rewindCaptureRequested ? spec.resumeAt : undefined) : spec.rewindFilesTo;
|
|
620
627
|
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
|
|
628
|
+
if (spec.resumeAt !== undefined && !rewindCaptureRequested) {
|
|
629
|
+
rewindNotes.push({
|
|
630
|
+
code: "conversation_only",
|
|
631
|
+
message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without rewindFiles, so files remain at their current state`,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
if (!deps.fileSnapshotStore) {
|
|
635
|
+
if (rewindTarget !== undefined) {
|
|
636
|
+
const e = new Error(`rewind-files: restoring the working tree to entry "${rewindTarget}" requires a snapshot backend, but this deployment wired no RunnerDeps.fileSnapshotStore — no snapshot was ever captured, so the files were NOT rewound`);
|
|
637
|
+
e.code = "rewind.store_unconfigured";
|
|
638
|
+
throw e;
|
|
639
|
+
}
|
|
640
|
+
if (rewindCaptureRequested) {
|
|
641
|
+
rewindNotes.push({
|
|
642
|
+
code: "snapshot_store_unconfigured",
|
|
643
|
+
message: "rewindFiles was requested but no RunnerDeps.fileSnapshotStore is wired — no working-tree snapshot was captured for this turn, so it cannot be rewound to later",
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
else if (!handsEnabled) {
|
|
648
|
+
if (rewindTarget !== undefined || rewindCaptureRequested) {
|
|
649
|
+
rewindNotes.push({
|
|
650
|
+
code: "files_env_unsupported",
|
|
651
|
+
message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no working-tree snapshot was captured or restored",
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
621
655
|
if (rewindTarget !== undefined && deps.fileSnapshotStore && handsEnabled) {
|
|
622
656
|
if (rewindBefore) {
|
|
623
657
|
let anchor;
|
|
@@ -642,18 +676,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
642
676
|
const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
|
|
643
677
|
const restored = await deps.fileSnapshotStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
|
|
644
678
|
if (!restored.ok) {
|
|
645
|
-
if (restored.error.code === "not_found"
|
|
646
|
-
const e = new Error(
|
|
679
|
+
if (restored.error.code === "not_found") {
|
|
680
|
+
const e = new Error(rewindBefore
|
|
681
|
+
? `rewind-files: the resolved "before" snapshot anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
|
|
682
|
+
: `rewind-files: no file snapshot exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound (only a COMPLETED turn that ran with rewindFiles is snapshotted, and snapshots are keyed by that turn's END leaf; earlier turns that ran without rewindFiles, mid-turn entries, and reaped snapshots have none)`);
|
|
647
683
|
e.code = "rewind_snapshot.unresolvable";
|
|
648
684
|
throw e;
|
|
649
685
|
}
|
|
650
|
-
if (restored.error.code === "not_found") {
|
|
651
|
-
try {
|
|
652
|
-
deps.onError?.(new Error(`rewind-files: no snapshot for entry "${rewindTarget}" — files left unchanged`), { phase: "rewind", sessionId });
|
|
653
|
-
}
|
|
654
|
-
catch {
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
686
|
else {
|
|
658
687
|
const e = new Error(`rewind-files restore failed (${restored.error.code}): ${restored.error.message}`);
|
|
659
688
|
e.code = "rewind.restore_failed";
|
|
@@ -1809,15 +1838,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1809
1838
|
}
|
|
1810
1839
|
const activeTools = new Set();
|
|
1811
1840
|
const fpRef = {};
|
|
1812
|
-
if (rebuildHarnessToolsRef.current === undefined) {
|
|
1813
|
-
rebuildHarnessToolsRef.current = async () => {
|
|
1814
|
-
const list = [...tools];
|
|
1815
|
-
await harnessRef.current.setTools(list, list.map((t) => t.name));
|
|
1816
|
-
if (fpRef.current)
|
|
1817
|
-
fpRef.current.tools = toolsToFingerprintInputs(list);
|
|
1818
|
-
turnSnapshotRef.current?.refreshTools(toolsToFingerprintInputs(list));
|
|
1819
|
-
};
|
|
1820
|
-
}
|
|
1821
1841
|
const turnSnapshotRef = {};
|
|
1822
1842
|
let harnessTools = tools;
|
|
1823
1843
|
const failedMcpServers = mcp.statuses
|
|
@@ -1852,7 +1872,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1852
1872
|
...(executionMode !== undefined ? { executionMode } : {}),
|
|
1853
1873
|
activate: async () => {
|
|
1854
1874
|
if (activeTools.has(name))
|
|
1855
|
-
return;
|
|
1875
|
+
return undefined;
|
|
1856
1876
|
activeTools.add(name);
|
|
1857
1877
|
try {
|
|
1858
1878
|
await rematerialize(activeTools);
|
|
@@ -1861,6 +1881,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1861
1881
|
activeTools.delete(name);
|
|
1862
1882
|
throw e;
|
|
1863
1883
|
}
|
|
1884
|
+
return listingRideRef.current?.([name]);
|
|
1864
1885
|
},
|
|
1865
1886
|
};
|
|
1866
1887
|
};
|
|
@@ -1883,7 +1904,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1883
1904
|
if (deferred.has(n))
|
|
1884
1905
|
activeTools.add(n);
|
|
1885
1906
|
}
|
|
1886
|
-
|
|
1907
|
+
const callableToolNames = () => {
|
|
1887
1908
|
const s = new Set(tools.map((t) => t.name));
|
|
1888
1909
|
for (const n of deferred)
|
|
1889
1910
|
if (!activeTools.has(n))
|
|
@@ -1891,6 +1912,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1891
1912
|
s.add(TOOL_SEARCH_NAME);
|
|
1892
1913
|
return s;
|
|
1893
1914
|
};
|
|
1915
|
+
offloadReachableToolsRef.current = callableToolNames;
|
|
1894
1916
|
let toolSearch;
|
|
1895
1917
|
const buildToolList = (active) => {
|
|
1896
1918
|
const list = tools.map((t) => (deferred.has(t.name) && !active.has(t.name) ? placeholders.get(t.name) : t));
|
|
@@ -1932,11 +1954,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1932
1954
|
active: activeTools,
|
|
1933
1955
|
rematerialize,
|
|
1934
1956
|
listingRide: (newly) => listingRideRef.current?.(newly),
|
|
1935
|
-
mountedNames:
|
|
1957
|
+
mountedNames: callableToolNames,
|
|
1936
1958
|
directCallEnabled: spec.deferSelfResolve !== false,
|
|
1937
1959
|
});
|
|
1938
1960
|
harnessTools = buildToolList(activeTools);
|
|
1939
1961
|
}
|
|
1962
|
+
else {
|
|
1963
|
+
rebuildHarnessToolsRef.current = async () => {
|
|
1964
|
+
const list = [...tools];
|
|
1965
|
+
await harnessRef.current.setTools(list, list.map((t) => t.name));
|
|
1966
|
+
if (fpRef.current)
|
|
1967
|
+
fpRef.current.tools = toolsToFingerprintInputs(list);
|
|
1968
|
+
turnSnapshotRef.current?.refreshTools(toolsToFingerprintInputs(list));
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1940
1971
|
if (spec.agents !== undefined && spec.agents.length > 0) {
|
|
1941
1972
|
const known = new Set(tools.flatMap((t) => [canonicalToolName(t.name), ...(t.aliases ?? []).map((a) => canonicalToolName(a))]));
|
|
1942
1973
|
for (const def of spec.agents) {
|
|
@@ -3357,7 +3388,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3357
3388
|
: undefined;
|
|
3358
3389
|
overheadState.promptChars = systemPrompt.length;
|
|
3359
3390
|
const preparedHolder = {};
|
|
3360
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3391
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3361
3392
|
const prepared = buildPrepared();
|
|
3362
3393
|
preparedHolder.current = prepared;
|
|
3363
3394
|
return prepared;
|
|
@@ -2764,6 +2764,7 @@ export class Runner {
|
|
|
2764
2764
|
threw,
|
|
2765
2765
|
model: prepared.model.id,
|
|
2766
2766
|
unpricedSpend: rs.telemetry.unpricedSpend,
|
|
2767
|
+
rewindNotes: prepared.rewindNotes,
|
|
2767
2768
|
abortedForTimeout: timeout.fired,
|
|
2768
2769
|
abortedForTurns: rs.limits.turnsExceeded,
|
|
2769
2770
|
abortedLive,
|
|
@@ -3045,10 +3046,19 @@ export class Runner {
|
|
|
3045
3046
|
if (!leafId)
|
|
3046
3047
|
return;
|
|
3047
3048
|
const root = prepared.taskRootPath;
|
|
3048
|
-
const
|
|
3049
|
-
if (
|
|
3050
|
-
if (Date.now() - refusedAt < SNAPSHOT_TOO_LARGE_TTL_MS)
|
|
3049
|
+
const refusal = this.snapshotTooLargeRoots.get(root);
|
|
3050
|
+
if (refusal !== undefined) {
|
|
3051
|
+
if (Date.now() - refusal.refusedAt < SNAPSHOT_TOO_LARGE_TTL_MS) {
|
|
3052
|
+
if (!refusal.skipAnnounced) {
|
|
3053
|
+
refusal.skipAnnounced = true;
|
|
3054
|
+
try {
|
|
3055
|
+
this.deps.onError?.(new Error(`rewind-files snapshot skipped for this working-tree root: a too_large refusal is still inside its ${SNAPSHOT_TOO_LARGE_TTL_MS / 60_000}-minute cooldown (re-probed at ${new Date(refusal.refusedAt + SNAPSHOT_TOO_LARGE_TTL_MS).toISOString()}) — turns completed during the cooldown capture no snapshot and cannot be rewound to. Announced once per cooldown window.`), { phase: "rewind", sessionId: prepared.sessionId, classification: "too_large" });
|
|
3056
|
+
}
|
|
3057
|
+
catch {
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3051
3060
|
return;
|
|
3061
|
+
}
|
|
3052
3062
|
this.snapshotTooLargeRoots.delete(root);
|
|
3053
3063
|
}
|
|
3054
3064
|
const ac = new AbortController();
|
|
@@ -3071,11 +3081,14 @@ export class Runner {
|
|
|
3071
3081
|
}
|
|
3072
3082
|
if (!r.ok) {
|
|
3073
3083
|
const tooLarge = r.error.code === "too_large";
|
|
3084
|
+
const refusedAt = Date.now();
|
|
3074
3085
|
if (tooLarge)
|
|
3075
|
-
this.snapshotTooLargeRoots.set(root,
|
|
3086
|
+
this.snapshotTooLargeRoots.set(root, { refusedAt, skipAnnounced: false });
|
|
3076
3087
|
try {
|
|
3077
3088
|
this.deps.onError?.(new Error(`rewind-files snapshot failed (${r.error.code}): ${r.error.message}` +
|
|
3078
|
-
(tooLarge
|
|
3089
|
+
(tooLarge
|
|
3090
|
+
? ` — skipping further snapshots for this root (this process) during a ${SNAPSHOT_TOO_LARGE_TTL_MS / 60_000}-minute cooldown, until ${new Date(refusedAt + SNAPSHOT_TOO_LARGE_TTL_MS).toISOString()}, when the tree is re-probed automatically. Raise the store's snapshotBounds to accept a tree this size; shrinking the tree also re-enables rewind, but only at that re-probe — not immediately`
|
|
3091
|
+
: "")), { phase: "rewind", sessionId: prepared.sessionId, ...(tooLarge ? { classification: "too_large" } : {}) });
|
|
3079
3092
|
}
|
|
3080
3093
|
catch {
|
|
3081
3094
|
}
|
|
@@ -31,7 +31,7 @@ export declare function buildDeferredRegistry(deferred: ReadonlySet<string>, too
|
|
|
31
31
|
export interface PlaceholderDirectCall {
|
|
32
32
|
resolveReal: () => PlaceholderDirectTarget | undefined;
|
|
33
33
|
executionMode?: ToolExecutionMode;
|
|
34
|
-
activate: () => Promise<
|
|
34
|
+
activate: () => Promise<string | undefined>;
|
|
35
35
|
}
|
|
36
36
|
export interface PlaceholderDirectTarget {
|
|
37
37
|
parameters: TSchema;
|
|
@@ -79,8 +79,11 @@ export function createPlaceholderTool(info, direct) {
|
|
|
79
79
|
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
80
80
|
const real = direct.resolveReal();
|
|
81
81
|
if (real !== undefined && Value.Check(real.parameters, params)) {
|
|
82
|
-
await direct.activate();
|
|
83
|
-
|
|
82
|
+
const ride = await direct.activate();
|
|
83
|
+
const result = await real.invoke(toolCallId, params, signal, onUpdate);
|
|
84
|
+
if (ride === undefined || ride === "")
|
|
85
|
+
return result;
|
|
86
|
+
return { ...result, content: [...result.content, { type: "text", text: ride }] };
|
|
84
87
|
}
|
|
85
88
|
return teachingRejection();
|
|
86
89
|
},
|
|
@@ -168,15 +171,27 @@ export function resolveToolSearch(args, registry) {
|
|
|
168
171
|
}
|
|
169
172
|
export function extractDiscoveredToolNames(messages, registry) {
|
|
170
173
|
const names = new Set();
|
|
174
|
+
const pendingDirect = new Map();
|
|
171
175
|
for (const m of messages) {
|
|
176
|
+
if (m.role === "toolResult") {
|
|
177
|
+
const name = pendingDirect.get(m.toolCallId);
|
|
178
|
+
if (name !== undefined && m.isError !== true)
|
|
179
|
+
names.add(name);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
172
182
|
if (m.role !== "assistant")
|
|
173
183
|
continue;
|
|
174
184
|
for (const part of m.content) {
|
|
175
|
-
if (part.type
|
|
185
|
+
if (part.type !== "toolCall")
|
|
186
|
+
continue;
|
|
187
|
+
if (part.name === TOOL_SEARCH_NAME) {
|
|
176
188
|
for (const n of resolveToolSearch(part.arguments, registry)) {
|
|
177
189
|
names.add(n);
|
|
178
190
|
}
|
|
179
191
|
}
|
|
192
|
+
else if (registry.has(part.name)) {
|
|
193
|
+
pendingDirect.set(part.id, part.name);
|
|
194
|
+
}
|
|
180
195
|
}
|
|
181
196
|
}
|
|
182
197
|
return [...names];
|
|
@@ -12,6 +12,7 @@ export interface TtlSessionStoreOptions {
|
|
|
12
12
|
export declare class TtlSessionStore implements SessionStore {
|
|
13
13
|
private repo;
|
|
14
14
|
private entries;
|
|
15
|
+
private owners;
|
|
15
16
|
private pending;
|
|
16
17
|
private pinned;
|
|
17
18
|
private defaultTtlMs;
|
|
@@ -31,6 +32,8 @@ export declare class TtlSessionStore implements SessionStore {
|
|
|
31
32
|
forget(sessionId: string): void;
|
|
32
33
|
pin(sessionId: string): void;
|
|
33
34
|
unpin(sessionId: string): void;
|
|
35
|
+
ownerOf(sessionId: string): Promise<string | null | undefined>;
|
|
36
|
+
register(sessionId: string, owner: string | null): Promise<void>;
|
|
34
37
|
sweep(now?: number): void;
|
|
35
38
|
get size(): number;
|
|
36
39
|
dispose(): void;
|
|
@@ -6,6 +6,7 @@ export { SESSION_DEFAULT_TTL_DAYS };
|
|
|
6
6
|
export class TtlSessionStore {
|
|
7
7
|
repo;
|
|
8
8
|
entries = new Map();
|
|
9
|
+
owners = new Map();
|
|
9
10
|
pending = new Map();
|
|
10
11
|
pinned = new Set();
|
|
11
12
|
defaultTtlMs;
|
|
@@ -132,6 +133,7 @@ export class TtlSessionStore {
|
|
|
132
133
|
this.entries.delete(sessionId);
|
|
133
134
|
if (this.evictPolicy === "delete") {
|
|
134
135
|
await this.repo.delete(await e.session.getMetadata());
|
|
136
|
+
this.owners.delete(sessionId);
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
forget(sessionId) {
|
|
@@ -143,6 +145,13 @@ export class TtlSessionStore {
|
|
|
143
145
|
unpin(sessionId) {
|
|
144
146
|
this.pinned.delete(sessionId);
|
|
145
147
|
}
|
|
148
|
+
async ownerOf(sessionId) {
|
|
149
|
+
return this.owners.has(sessionId) ? this.owners.get(sessionId) : undefined;
|
|
150
|
+
}
|
|
151
|
+
async register(sessionId, owner) {
|
|
152
|
+
if (!this.owners.has(sessionId))
|
|
153
|
+
this.owners.set(sessionId, owner);
|
|
154
|
+
}
|
|
146
155
|
sweep(now = Date.now()) {
|
|
147
156
|
for (const [id, e] of this.entries) {
|
|
148
157
|
if (this.pinned.has(id)) {
|
|
@@ -152,6 +161,7 @@ export class TtlSessionStore {
|
|
|
152
161
|
this.entries.delete(id);
|
|
153
162
|
if (this.evictPolicy === "delete") {
|
|
154
163
|
void this.repo.delete({ id, createdAt: "" });
|
|
164
|
+
this.owners.delete(id);
|
|
155
165
|
}
|
|
156
166
|
}
|
|
157
167
|
}
|
package/dist/core/session.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface SessionStore {
|
|
|
31
31
|
noteTaskRun?(sessionId: string, taskId: string): void | Promise<void>;
|
|
32
32
|
list?(): Promise<SessionStoreSummary[]>;
|
|
33
33
|
fork?(sourceId: string, owner?: string | null): Promise<string | null>;
|
|
34
|
+
ownerOf?(sessionId: string): Promise<string | null | undefined>;
|
|
35
|
+
register?(sessionId: string, owner: string | null): Promise<void>;
|
|
34
36
|
readonly size: number;
|
|
35
37
|
dispose(): void | Promise<void>;
|
|
36
38
|
}
|
|
@@ -652,6 +652,9 @@ export function settleBackgroundAgentLane(core, id, outcome) {
|
|
|
652
652
|
const handle = core.handles.get(id);
|
|
653
653
|
if (!handle || handle.type !== "background_agent")
|
|
654
654
|
return undefined;
|
|
655
|
+
if (outcome.cycle === undefined && (handle.reviveCycle ?? 0) !== 0) {
|
|
656
|
+
throw new Error(`settleBackgroundAgent("${id}"): the row has been revived (cycle ${handle.reviveCycle}) — pass the cycle this settle speaks for, or use settleRevivedAgent`);
|
|
657
|
+
}
|
|
655
658
|
if ((outcome.cycle ?? 0) !== (handle.reviveCycle ?? 0))
|
|
656
659
|
return undefined;
|
|
657
660
|
if (handle.status !== "running") {
|
|
@@ -21,6 +21,8 @@ function isOffloadedPreview(content) {
|
|
|
21
21
|
return first?.type === "text" && typeof first.text === "string" && first.text.startsWith(PERSISTED_OUTPUT_PREFIX);
|
|
22
22
|
}
|
|
23
23
|
function replaceTextBlock(m, text) {
|
|
24
|
+
if (!isToolResult(m))
|
|
25
|
+
return m;
|
|
24
26
|
const images = m.content.filter((b) => b.type !== "text");
|
|
25
27
|
return { ...m, content: [{ type: "text", text }, ...images] };
|
|
26
28
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -390,6 +390,10 @@ export interface TaskResult {
|
|
|
390
390
|
atTurn: number;
|
|
391
391
|
};
|
|
392
392
|
structuredOutput?: unknown;
|
|
393
|
+
rewindNotes?: Array<{
|
|
394
|
+
code: "conversation_only" | "files_env_unsupported" | "snapshot_store_unconfigured";
|
|
395
|
+
message: string;
|
|
396
|
+
}>;
|
|
393
397
|
stats: {
|
|
394
398
|
turns: number;
|
|
395
399
|
tokens: number;
|
|
@@ -34,8 +34,9 @@ export function buildSessionContext(pathEntries, opts) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
const messages = [];
|
|
37
|
+
const isAssistantMessage = (m) => m.role === "assistant";
|
|
37
38
|
const stripAssistantUsage = (message) => {
|
|
38
|
-
if (message
|
|
39
|
+
if (!isAssistantMessage(message)) {
|
|
39
40
|
return message;
|
|
40
41
|
}
|
|
41
42
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { createSkillsFromDirectory, type SkillsDirectoryOptions, type SkillsDire
|
|
|
7
7
|
export { REPORT_FINDINGS_TOOL_NAME, type ReportedFinding } from "./core/runner/synthetic-tools.js";
|
|
8
8
|
export { formatToolError, formatZodValidationError, formatValidationPath, truncateError, errorClassOf } from "./core/tool-errors.js";
|
|
9
9
|
export type { WorkerErrorClass } from "./core/tool-errors.js";
|
|
10
|
-
export { createWebFetchTool, webFetchToolSpec, htmlToText, type WebFetchConfig, createWebSearchTool, type WebSearchConfig, createWebFetchSummarizer, WEBFETCH_SUMMARY_MAX_CONTENT, WEBFETCH_SUMMARY_GUIDELINES, } from "./tools/web.js";
|
|
10
|
+
export { createWebFetchTool, webFetchToolSpec, htmlToText, type WebFetchConfig, createWebSearchTool, type WebSearchConfig, createSearxngSearchBackend, probeSearchBackend, type SearxngBackendOptions, createWebFetchSummarizer, WEBFETCH_SUMMARY_MAX_CONTENT, WEBFETCH_SUMMARY_GUIDELINES, } from "./tools/web.js";
|
|
11
11
|
export { createTodoWriteTool } from "./tools/todo.js";
|
|
12
12
|
export { createTaskListTools, createMemoryTaskListStore, assertJsonMetadata, type TaskListItem, type TaskListStore } from "./tools/task-list.js";
|
|
13
13
|
export { assembleCodeTools, type CodeToolsConfig, CODE_ROLE } from "./scenarios/full-body.js";
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { SKILL_TOOL_NAME } from "./core/runner/synthetic-tools.js";
|
|
|
5
5
|
export { createSkillsFromDirectory, } from "./core/skills-directory.js";
|
|
6
6
|
export { REPORT_FINDINGS_TOOL_NAME } from "./core/runner/synthetic-tools.js";
|
|
7
7
|
export { formatToolError, formatZodValidationError, formatValidationPath, truncateError, errorClassOf } from "./core/tool-errors.js";
|
|
8
|
-
export { createWebFetchTool, webFetchToolSpec, htmlToText, createWebSearchTool, createWebFetchSummarizer, WEBFETCH_SUMMARY_MAX_CONTENT, WEBFETCH_SUMMARY_GUIDELINES, } from "./tools/web.js";
|
|
8
|
+
export { createWebFetchTool, webFetchToolSpec, htmlToText, createWebSearchTool, createSearxngSearchBackend, probeSearchBackend, createWebFetchSummarizer, WEBFETCH_SUMMARY_MAX_CONTENT, WEBFETCH_SUMMARY_GUIDELINES, } from "./tools/web.js";
|
|
9
9
|
export { createTodoWriteTool } from "./tools/todo.js";
|
|
10
10
|
export { createTaskListTools, createMemoryTaskListStore, assertJsonMetadata } from "./tools/task-list.js";
|
|
11
11
|
export { assembleCodeTools, CODE_ROLE } from "./scenarios/full-body.js";
|
|
@@ -9,6 +9,7 @@ import { combinePolicies, createAllowDenyPolicy } from "../core/tool-policy.js";
|
|
|
9
9
|
import { callKeyOrdinal, oversizeJournalResult, journalOversizeTombstone, JOURNAL_OVERSIZE_ERROR_CODE, MAX_JOURNAL_RESULT_BYTES } from "../core/workflow-journal-store.js";
|
|
10
10
|
import { isWorkflowRunActive, closeWorkflowChannel, markWorkflowActive, publishWorkflowEvent } from "./workflow-observe.js";
|
|
11
11
|
import { isDurablePause, mapNestedSuspend } from "../agents/suspend-guard.js";
|
|
12
|
+
import { RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
|
|
12
13
|
import { boundInputHashOf } from "../core/canonical-json.js";
|
|
13
14
|
import { boundedRedactedSummary } from "../core/untrusted-egress.js";
|
|
14
15
|
import { delimitUntrusted } from "../core/untrusted-text.js";
|
|
@@ -20,7 +21,6 @@ const MAX_TRANSCRIPT_CHARS = 4000;
|
|
|
20
21
|
const WORKFLOW_RESULT_MAX = 4000;
|
|
21
22
|
const WORKFLOW_RESULT_FULL_MAX = 200_000;
|
|
22
23
|
const MAX_ACTIVITY = 30;
|
|
23
|
-
const RUNNING_AGENT_PERSIST_EVERY_BEATS = 4;
|
|
24
24
|
function workflowModelLabel(spec) {
|
|
25
25
|
const model = spec.model;
|
|
26
26
|
if (model === undefined)
|
|
@@ -453,16 +453,17 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
453
453
|
};
|
|
454
454
|
const waIdOf = (callKey) => `wa${createHash("sha256").update(`${runId}:${callKey}`).digest("hex").slice(0, 16)}`;
|
|
455
455
|
const bceLive = new Map();
|
|
456
|
-
const bceSpawn = (callKey, label, agentType, replayed) => {
|
|
456
|
+
const bceSpawn = (callKey, label, agentType, replayed, sessionId) => {
|
|
457
457
|
if (!bceSink)
|
|
458
458
|
return;
|
|
459
459
|
const id = waIdOf(callKey);
|
|
460
|
-
bceLive.set(id, { callKey, label, ...(agentType !== undefined ? { agentType } : {}) });
|
|
460
|
+
bceLive.set(id, { callKey, label, ...(agentType !== undefined ? { agentType } : {}), ...(sessionId !== undefined ? { sessionId } : {}) });
|
|
461
461
|
bceEmit({
|
|
462
462
|
kind: "spawn",
|
|
463
463
|
taskId: id,
|
|
464
464
|
sessionScoped: false,
|
|
465
465
|
owner: runId,
|
|
466
|
+
...(sessionId !== undefined ? { sessionId, transcriptId: sessionId } : {}),
|
|
466
467
|
...(scope !== undefined ? { scope } : {}),
|
|
467
468
|
description: replayed ? `${label} (replayed)` : label,
|
|
468
469
|
agentType: agentType ?? "workflow-agent",
|
|
@@ -474,6 +475,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
474
475
|
startedAt: Date.now(),
|
|
475
476
|
});
|
|
476
477
|
};
|
|
478
|
+
const bceBindSession = (callKey, sessionId) => {
|
|
479
|
+
const row = bceLive.get(waIdOf(callKey));
|
|
480
|
+
if (row !== undefined)
|
|
481
|
+
row.sessionId = sessionId;
|
|
482
|
+
};
|
|
477
483
|
const bceTick = (callKey, e) => {
|
|
478
484
|
if (!bceSink)
|
|
479
485
|
return;
|
|
@@ -489,6 +495,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
489
495
|
workflowRunId: runId,
|
|
490
496
|
...(scope !== undefined ? { scope } : {}),
|
|
491
497
|
...(row.agentType !== undefined ? { agentType: row.agentType } : { agentType: "workflow-agent" }),
|
|
498
|
+
...(row.sessionId !== undefined ? { sessionId: row.sessionId, transcriptId: row.sessionId } : {}),
|
|
492
499
|
name: e.name ?? row.label,
|
|
493
500
|
progressTaskId: e.taskId,
|
|
494
501
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
@@ -635,7 +642,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
635
642
|
rec.toolCalls = (rec.toolCalls ?? 0) + 1;
|
|
636
643
|
rec.activity = tail;
|
|
637
644
|
beatCount += 1;
|
|
638
|
-
if (beatCount === 1 || beatCount %
|
|
645
|
+
if (beatCount === 1 || beatCount % RUNNING_AGENT_OBSERVE_EVERY_BEATS === 0) {
|
|
639
646
|
void persist("update");
|
|
640
647
|
}
|
|
641
648
|
};
|
|
@@ -768,7 +775,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
768
775
|
agentPhaseOf.set(replayRec, phaseInstance);
|
|
769
776
|
emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(model !== undefined ? { model } : {}), replayed: true, ts: at });
|
|
770
777
|
emit({ type: "agent_end", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), status: replayRec.status, output: cachedOutput, ...(rs.toolCalls !== undefined ? { toolCalls: rs.toolCalls } : {}), replayed: true, ts: at });
|
|
771
|
-
bceSpawn(callKey, label, agentOpts.agentType, true);
|
|
778
|
+
bceSpawn(callKey, label, agentOpts.agentType, true, r.sessionId || undefined);
|
|
772
779
|
bceTerminal(callKey, replayRec.status === "completed" ? "completed" : "failed", cachedOutput, r.sessionId || undefined, replayRec.stats);
|
|
773
780
|
accumulateStats(r, false);
|
|
774
781
|
void persist("update");
|
|
@@ -810,7 +817,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
810
817
|
if (finalized)
|
|
811
818
|
throw new Error("workflow run already finalized — ctx.agent cannot spawn after the run ended");
|
|
812
819
|
rec.startedAt = now();
|
|
813
|
-
|
|
820
|
+
const bornChildSessionId = resolveChildSessionIdAtSpawn(spec);
|
|
821
|
+
bceSpawn(callKey, label, agentOpts.agentType, false, bornChildSessionId);
|
|
814
822
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
815
823
|
const typedSpec = typedSpec0.model === undefined && inheritedModelSnap !== undefined ? { ...typedSpec0, model: inheritedModelSnap } : typedSpec0;
|
|
816
824
|
const framedSpec = withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema);
|
|
@@ -852,10 +860,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
852
860
|
const onCallerAbort = () => attemptCtl.abort(new Error("workflow aborted"));
|
|
853
861
|
effectiveSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
854
862
|
armWatchdog();
|
|
855
|
-
const attemptSessionId = resolveChildSessionIdAtSpawn(runSpec);
|
|
863
|
+
const attemptSessionId = attempts === 1 ? bornChildSessionId : resolveChildSessionIdAtSpawn(runSpec);
|
|
856
864
|
const attemptSpec = attemptSessionId !== undefined ? { ...runSpec, sessionId: attemptSessionId, signal: attemptCtl.signal } : { ...runSpec, signal: attemptCtl.signal };
|
|
857
865
|
if (attemptSessionId !== undefined && !finalized) {
|
|
858
866
|
rec.sessionId = attemptSessionId;
|
|
867
|
+
bceBindSession(callKey, attemptSessionId);
|
|
859
868
|
void persist("update");
|
|
860
869
|
}
|
|
861
870
|
const attemptInternals = {
|
|
@@ -1143,9 +1152,9 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1143
1152
|
throw new Error(finalized ? "workflow run already finalized — ctx.agentStream cannot spawn after the run ended" : "workflow aborted");
|
|
1144
1153
|
}
|
|
1145
1154
|
rec.startedAt = now();
|
|
1146
|
-
|
|
1155
|
+
const childSessionId = resolveChildSessionIdAtSpawn(spec);
|
|
1156
|
+
bceSpawn(callKey, label, agentOpts.agentType, false, childSessionId);
|
|
1147
1157
|
let stream;
|
|
1148
|
-
let childSessionId;
|
|
1149
1158
|
try {
|
|
1150
1159
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
1151
1160
|
const typedSpec = typedSpec0.model === undefined && inheritedModelSnap !== undefined ? { ...typedSpec0, model: inheritedModelSnap } : typedSpec0;
|
|
@@ -1154,7 +1163,6 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1154
1163
|
const baseRunSpec = agentOpts.schema
|
|
1155
1164
|
? { ...framedSpec, ...authInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
|
|
1156
1165
|
: { ...framedSpec, ...authInherit, signal: effectiveSignal };
|
|
1157
|
-
childSessionId = resolveChildSessionIdAtSpawn(baseRunSpec);
|
|
1158
1166
|
const runSpec = childSessionId !== undefined ? { ...baseRunSpec, sessionId: childSessionId } : baseRunSpec;
|
|
1159
1167
|
const enrichedForwardS = opts.onForwardEvent !== undefined
|
|
1160
1168
|
? (e) => {
|
|
@@ -152,6 +152,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
152
152
|
return errorResult(`Error (Edit): "${path}" has a truncated UTF-16 body; repair/convert it with bash first.`);
|
|
153
153
|
const original = decoded.text;
|
|
154
154
|
const entry = state.get(r.key);
|
|
155
|
+
const readWasTruncated = entry?.truncated === true;
|
|
155
156
|
const stale = checkStale(entry, sha256(original));
|
|
156
157
|
if (stale)
|
|
157
158
|
return errorResult(violationText("Edit", stale));
|
|
@@ -180,7 +181,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
180
181
|
oldS = resolvedEsc;
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
|
-
const match = checkEditMatch(working, oldS, e.replace_all === true,
|
|
184
|
+
const match = checkEditMatch(working, oldS, e.replace_all === true, readWasTruncated);
|
|
184
185
|
if (match) {
|
|
185
186
|
const escNote = match.code === "ambiguous_edit" && escapeMatchWasAttempted(e.old_string) && !working.includes(oldS) ? ESCAPE_MATCH_MISS_NOTE : "";
|
|
186
187
|
return errorResult(batch ? `Error (Edit): ${where}${match.message}${escNote} (no changes written — the batch is atomic).` : `${violationText("Edit", match)}${escNote}`);
|
|
@@ -58,7 +58,7 @@ export declare const OVERSIZE_READ_ESCAPE_HINT = "(This file is over the Read to
|
|
|
58
58
|
export declare const PARTIAL_VIEW_READ_ESCAPE_HINT = "(Your last Read of this file returned only a PARTIAL view \u2014 the output token cap paginated it, so a default Read will keep returning the same page. Re-read it with explicit offset/limit (start from the page marker's next-page hint) until you have seen the part you are about to change; an explicit slice that fits satisfies the read-first rule. Or inspect/transform it with bash (e.g. `sed -n`, `grep`) instead.)";
|
|
59
59
|
export declare const WRITE_ENCODING_DEADLOCK_ESCAPE_HINT = "(If the Read tool refuses this file (binary/unknown encoding), overwrite or convert it with bash instead \u2014 e.g. `rm` + rewrite, or `iconv`.)";
|
|
60
60
|
export declare function checkNoChange(oldString: string, newString: string): FsViolation | undefined;
|
|
61
|
-
export declare function checkStale(entry: ReadEntry, currentHash: string): FsViolation | undefined;
|
|
61
|
+
export declare function checkStale(entry: ReadEntry | undefined, currentHash: string): FsViolation | undefined;
|
|
62
62
|
export declare function countOccurrences(haystack: string, needle: string): number;
|
|
63
63
|
export declare function similarNameSuggestion(siblingNames: readonly string[], missingName: string): string | undefined;
|
|
64
64
|
export declare function normalizeQuotes(s: string): string;
|
package/dist/tools/fs/safety.js
CHANGED
|
@@ -276,7 +276,7 @@ export function checkNoChange(oldString, newString) {
|
|
|
276
276
|
return undefined;
|
|
277
277
|
}
|
|
278
278
|
export function checkStale(entry, currentHash) {
|
|
279
|
-
if (entry.hash !== currentHash) {
|
|
279
|
+
if (entry === undefined || entry.hash !== currentHash) {
|
|
280
280
|
return { code: "stale", message: "File has been modified since read, either by the user or by a linter. Read it again before attempting to write it." };
|
|
281
281
|
}
|
|
282
282
|
return undefined;
|
package/dist/tools/web.d.ts
CHANGED
|
@@ -29,3 +29,18 @@ export interface WebSearchConfig {
|
|
|
29
29
|
}
|
|
30
30
|
export declare function clipCodePoints(s: string, max: number): string;
|
|
31
31
|
export declare function createWebSearchTool(config: WebSearchConfig): ToolSpec;
|
|
32
|
+
export interface SearxngBackendOptions {
|
|
33
|
+
fetchImpl?: typeof fetch;
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
extraParams?: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
export declare function createSearxngSearchBackend(baseUrl: string, options?: SearxngBackendOptions): WebSearchConfig["search"];
|
|
38
|
+
export declare function probeSearchBackend(search: WebSearchConfig["search"], options?: {
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
}): Promise<{
|
|
41
|
+
ok: true;
|
|
42
|
+
results: number;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
error: string;
|
|
46
|
+
}>;
|
package/dist/tools/web.js
CHANGED
|
@@ -798,3 +798,45 @@ export function createWebSearchTool(config) {
|
|
|
798
798
|
},
|
|
799
799
|
};
|
|
800
800
|
}
|
|
801
|
+
export function createSearxngSearchBackend(baseUrl, options = {}) {
|
|
802
|
+
const doFetch = options.fetchImpl ?? globalThis.fetch;
|
|
803
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
804
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
805
|
+
return async (query, signal, opts) => {
|
|
806
|
+
const q = opts?.allowedDomains && opts.allowedDomains.length > 0
|
|
807
|
+
? `${opts.allowedDomains.map((d) => `site:${d}`).join(" OR ")} ${query}`
|
|
808
|
+
: query;
|
|
809
|
+
const url = new URL(`${base}/search`);
|
|
810
|
+
url.searchParams.set("q", q);
|
|
811
|
+
url.searchParams.set("format", "json");
|
|
812
|
+
for (const [k, v] of Object.entries(options.extraParams ?? {}))
|
|
813
|
+
url.searchParams.set(k, v);
|
|
814
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
815
|
+
const res = await doFetch(url, { signal: signal ? AbortSignal.any([signal, timeout]) : timeout, headers: { accept: "application/json" } });
|
|
816
|
+
if (!res.ok)
|
|
817
|
+
throw new Error(`SearXNG ${res.status} ${res.statusText} from ${base}/search`);
|
|
818
|
+
const body = (await res.json());
|
|
819
|
+
if (!Array.isArray(body.results))
|
|
820
|
+
throw new Error(`SearXNG returned no results array from ${base}/search — is format=json enabled on this instance?`);
|
|
821
|
+
return body.results
|
|
822
|
+
.filter((r) => typeof r.url === "string" && r.url !== "")
|
|
823
|
+
.map((r) => ({
|
|
824
|
+
title: typeof r.title === "string" && r.title !== "" ? r.title : r.url,
|
|
825
|
+
url: r.url,
|
|
826
|
+
snippet: typeof r.content === "string" ? r.content : "",
|
|
827
|
+
}));
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
export async function probeSearchBackend(search, options) {
|
|
831
|
+
const budget = options?.timeoutMs ?? 15_000;
|
|
832
|
+
try {
|
|
833
|
+
const results = await Promise.race([
|
|
834
|
+
search("connectivity probe", AbortSignal.timeout(budget)),
|
|
835
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`probe timed out after ${budget}ms`)), budget)),
|
|
836
|
+
]);
|
|
837
|
+
return { ok: true, results: results.length };
|
|
838
|
+
}
|
|
839
|
+
catch (e) {
|
|
840
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
841
|
+
}
|
|
842
|
+
}
|