@sema-agent/core 1.451.0 → 1.452.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/observer.js +3 -0
- package/dist/agents/subagent.js +51 -12
- package/dist/core/ask-question.js +5 -5
- package/dist/core/background-agent-store.d.ts +2 -0
- package/dist/core/background-agent-store.js +5 -1
- package/dist/core/exec-output-tail.d.ts +18 -1
- package/dist/core/exec-output-tail.js +38 -5
- package/dist/core/lsp-session.d.ts +1 -0
- package/dist/core/lsp-session.js +24 -6
- package/dist/core/lsp.d.ts +10 -0
- package/dist/core/lsp.js +63 -6
- package/dist/core/mailbox-store.d.ts +3 -2
- package/dist/core/mailbox-store.js +19 -4
- package/dist/core/memory.js +1 -1
- package/dist/core/runner/prepare-task.js +10 -1
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/session-reconcile.js +5 -2
- package/dist/core/task-notification.d.ts +1 -0
- package/dist/core/task-registry.d.ts +15 -9
- package/dist/core/task-registry.js +290 -53
- package/dist/core/tool-result-store.js +2 -2
- package/dist/core/tools.d.ts +5 -0
- package/dist/core/tools.js +3 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/core/workflow-journal-store.d.ts +2 -0
- package/dist/core/workflow-journal-store.js +14 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +1 -0
- package/dist/engine/execution-env/node-execution-env.js +130 -20
- package/dist/engine/lsp/node-lsp-manager.d.ts +3 -1
- package/dist/engine/lsp/node-lsp-manager.js +22 -5
- package/dist/engine/lsp/stdio-lsp-transport.d.ts +1 -1
- package/dist/engine/lsp/stdio-lsp-transport.js +17 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +1 -0
- package/dist/orchestration/run-workflow-tool.js +29 -6
- package/dist/orchestration/workflow.d.ts +9 -0
- package/dist/orchestration/workflow.js +80 -5
- package/dist/stores/cc/mailbox-store.js +6 -1
- package/dist/stores/file/background-agent-store.js +3 -2
- package/dist/stores/file/mailbox-store.d.ts +1 -1
- package/dist/stores/file/mailbox-store.js +9 -7
- package/dist/tools/fs/encoding.d.ts +5 -0
- package/dist/tools/fs/encoding.js +6 -0
- package/dist/tools/fs/index.js +184 -120
- package/dist/tools/fs/notebook.d.ts +43 -0
- package/dist/tools/fs/notebook.js +141 -0
- package/dist/tools/fs/repo-map.js +2 -2
- package/dist/tools/fs/search.js +141 -12
- package/dist/tools/gitea-issue.js +4 -2
- package/dist/tools/monitor.js +12 -8
- package/dist/tools/scheduler-tools.js +16 -16
- package/dist/tools/task-list.js +34 -12
- package/dist/tools/web.d.ts +2 -0
- package/dist/tools/web.js +105 -19
- package/dist/tools/worktree.js +14 -14
- package/package.json +1 -1
package/dist/agents/observer.js
CHANGED
|
@@ -340,18 +340,21 @@ export function createObserverReportToolSpec(opts) {
|
|
|
340
340
|
return {
|
|
341
341
|
content: "ObserverReport is only available to an observer agent; the main session does not have an observed pairing.",
|
|
342
342
|
details: { success: false },
|
|
343
|
+
isError: true,
|
|
343
344
|
};
|
|
344
345
|
}
|
|
345
346
|
if (pairing.state !== "armed") {
|
|
346
347
|
return {
|
|
347
348
|
content: "Your observer pairing is not armed (stopped, retired, or never installed). The report was not delivered.",
|
|
348
349
|
details: { success: false },
|
|
350
|
+
isError: true,
|
|
349
351
|
};
|
|
350
352
|
}
|
|
351
353
|
if (!pairing.observedRunning) {
|
|
352
354
|
return {
|
|
353
355
|
content: `The observed agent (${pairing.observedEnvelopeName}) is not running. The report was not delivered.`,
|
|
354
356
|
details: { success: false },
|
|
357
|
+
isError: true,
|
|
355
358
|
};
|
|
356
359
|
}
|
|
357
360
|
await opts.queueReport(frameObserverReport(pairing.observerAgentName, report));
|
package/dist/agents/subagent.js
CHANGED
|
@@ -650,6 +650,7 @@ function makeSubagentResume(deps) {
|
|
|
650
650
|
}
|
|
651
651
|
}
|
|
652
652
|
const stoppedByRevive = status === "killed" ? deps.registry?.getStopAttribution(deps.taskId ?? "") ?? "system" : undefined;
|
|
653
|
+
const completionIdRevive = deps.registry?.getCompletionId(deps.taskId ?? "");
|
|
653
654
|
const reviveName = deps.rowDescription ?? `sub-agent ${marker}`;
|
|
654
655
|
reviveEmit?.({
|
|
655
656
|
kind: "terminal",
|
|
@@ -664,6 +665,7 @@ function makeSubagentResume(deps) {
|
|
|
664
665
|
summary: `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(Date.now() - reviveStartedAt)}`,
|
|
665
666
|
resumable: status !== "killed",
|
|
666
667
|
usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
|
|
668
|
+
...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
|
|
667
669
|
});
|
|
668
670
|
const resumeFrame = {
|
|
669
671
|
task_id: deps.taskId ?? entry.childSessionId,
|
|
@@ -679,6 +681,7 @@ function makeSubagentResume(deps) {
|
|
|
679
681
|
...resumeResidual(),
|
|
680
682
|
resumable: status !== "killed",
|
|
681
683
|
usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
|
|
684
|
+
...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
|
|
682
685
|
};
|
|
683
686
|
const gateOwner = status === "completed" && deps.notify !== undefined && deps.registry !== undefined && ledger.get(deps.parentToolCallId) === entry
|
|
684
687
|
? entry.childSessionId
|
|
@@ -714,6 +717,7 @@ function makeSubagentResume(deps) {
|
|
|
714
717
|
}
|
|
715
718
|
}
|
|
716
719
|
const stoppedByReject = abort.signal.aborted ? deps.registry?.getStopAttribution(deps.taskId ?? "") ?? "system" : undefined;
|
|
720
|
+
const completionIdRejectRevive = deps.registry?.getCompletionId(deps.taskId ?? "");
|
|
717
721
|
const rejectName = deps.rowDescription ?? `sub-agent ${marker}`;
|
|
718
722
|
reviveEmit?.({
|
|
719
723
|
kind: "terminal",
|
|
@@ -726,6 +730,7 @@ function makeSubagentResume(deps) {
|
|
|
726
730
|
seq: entry.cycleSeq,
|
|
727
731
|
...(stoppedByReject !== undefined ? { stoppedBy: stoppedByReject } : {}),
|
|
728
732
|
summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : "failed"}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300),
|
|
733
|
+
...(completionIdRejectRevive !== undefined ? { completionId: completionIdRejectRevive } : {}),
|
|
729
734
|
});
|
|
730
735
|
try {
|
|
731
736
|
deps.notify?.({
|
|
@@ -737,6 +742,7 @@ function makeSubagentResume(deps) {
|
|
|
737
742
|
seq: entry.cycleSeq,
|
|
738
743
|
...(stoppedByReject !== undefined ? { stoppedBy: stoppedByReject } : {}),
|
|
739
744
|
summary: `Agent "${rejectName}" (resumed) ${abort.signal.aborted ? "stopped" : `failed: ${e instanceof Error ? e.message : String(e)}`}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300),
|
|
745
|
+
...(completionIdRejectRevive !== undefined ? { completionId: completionIdRejectRevive } : {}),
|
|
740
746
|
}, { priority: "later" });
|
|
741
747
|
}
|
|
742
748
|
catch {
|
|
@@ -1805,6 +1811,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1805
1811
|
...(!okBg ? { error: reapedBg ? BG_AGENT_REAP_STOP_ERROR : child.errorMessage ?? String(child.status) } : {}),
|
|
1806
1812
|
}) ?? (abort.signal.aborted ? "killed" : okBg ? "completed" : "failed");
|
|
1807
1813
|
const stoppedByBg = settledBg === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
|
|
1814
|
+
const completionIdFork = bg.registry.getCompletionId(taskId);
|
|
1808
1815
|
const stillbornFork = child.errorCode === "resume.session_not_found";
|
|
1809
1816
|
if (stillbornFork) {
|
|
1810
1817
|
try {
|
|
@@ -1829,6 +1836,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1829
1836
|
...residualFork,
|
|
1830
1837
|
resumable: resumableFork,
|
|
1831
1838
|
usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
|
|
1839
|
+
...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
|
|
1832
1840
|
});
|
|
1833
1841
|
try {
|
|
1834
1842
|
notify?.({
|
|
@@ -1844,6 +1852,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1844
1852
|
...residualFork,
|
|
1845
1853
|
resumable: resumableFork,
|
|
1846
1854
|
usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
|
|
1855
|
+
...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
|
|
1847
1856
|
}, { priority: "later" });
|
|
1848
1857
|
}
|
|
1849
1858
|
catch {
|
|
@@ -1858,6 +1867,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1858
1867
|
error: killed ? BG_AGENT_REAP_STOP_ERROR : e instanceof Error ? e.message : String(e),
|
|
1859
1868
|
}) ?? (killed ? "killed" : "failed");
|
|
1860
1869
|
const stoppedByBg = settledBg === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
|
|
1870
|
+
const completionIdForkReject = bg.registry.getCompletionId(taskId);
|
|
1861
1871
|
const summaryBg = `${shortDesc} — ${settledBg === "failed" ? `failed: ${e instanceof Error ? e.message : String(e)}` : settledBg}`.slice(0, 300);
|
|
1862
1872
|
sinkEmit({
|
|
1863
1873
|
kind: "terminal",
|
|
@@ -1869,6 +1879,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1869
1879
|
status: settledBg,
|
|
1870
1880
|
...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
|
|
1871
1881
|
summary: summaryBg,
|
|
1882
|
+
...(completionIdForkReject !== undefined ? { completionId: completionIdForkReject } : {}),
|
|
1872
1883
|
});
|
|
1873
1884
|
try {
|
|
1874
1885
|
notify?.({
|
|
@@ -1879,6 +1890,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1879
1890
|
sessionId: forkedId,
|
|
1880
1891
|
...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
|
|
1881
1892
|
summary: summaryBg,
|
|
1893
|
+
...(completionIdForkReject !== undefined ? { completionId: completionIdForkReject } : {}),
|
|
1882
1894
|
}, { priority: "later" });
|
|
1883
1895
|
}
|
|
1884
1896
|
catch {
|
|
@@ -2390,6 +2402,7 @@ task_id: ${taskId}
|
|
|
2390
2402
|
}
|
|
2391
2403
|
}
|
|
2392
2404
|
const stoppedBy = settled === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
|
|
2405
|
+
const completionIdBg = bg.registry.getCompletionId(taskId);
|
|
2393
2406
|
const observerNote = observerNoteFor(await startBoundedObserverDrain());
|
|
2394
2407
|
const residual = residualFields();
|
|
2395
2408
|
const resumableBg = bgRetain !== undefined && settled !== "killed";
|
|
@@ -2406,6 +2419,7 @@ task_id: ${taskId}
|
|
|
2406
2419
|
...residual,
|
|
2407
2420
|
resumable: resumableBg,
|
|
2408
2421
|
usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
|
|
2422
|
+
...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
|
|
2409
2423
|
});
|
|
2410
2424
|
const completionFrame = {
|
|
2411
2425
|
task_id: taskId,
|
|
@@ -2421,6 +2435,7 @@ task_id: ${taskId}
|
|
|
2421
2435
|
...residual,
|
|
2422
2436
|
resumable: resumableBg,
|
|
2423
2437
|
usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
|
|
2438
|
+
...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
|
|
2424
2439
|
};
|
|
2425
2440
|
const gateOwner = settled === "completed" && notify !== undefined ? bgRetain?.childSessionId : undefined;
|
|
2426
2441
|
const aliveBgCount = gateOwner !== undefined ? bg.registry.countRunningBackgroundForOwner(gateOwner) : 0;
|
|
@@ -2465,6 +2480,7 @@ task_id: ${taskId}
|
|
|
2465
2480
|
}
|
|
2466
2481
|
}
|
|
2467
2482
|
const stoppedBy = settled === "killed" ? bg.registry.getStopAttribution(taskId) ?? "system" : undefined;
|
|
2483
|
+
const completionIdBgReject = bg.registry.getCompletionId(taskId);
|
|
2468
2484
|
const observerNote = observerNoteFor(await startBoundedObserverDrain());
|
|
2469
2485
|
sinkEmit({
|
|
2470
2486
|
kind: "terminal",
|
|
@@ -2477,6 +2493,7 @@ task_id: ${taskId}
|
|
|
2477
2493
|
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
|
|
2478
2494
|
...(stoppedBy !== undefined ? { stoppedBy } : {}),
|
|
2479
2495
|
summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300),
|
|
2496
|
+
...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
|
|
2480
2497
|
});
|
|
2481
2498
|
try {
|
|
2482
2499
|
notify?.({
|
|
@@ -2488,6 +2505,7 @@ task_id: ${taskId}
|
|
|
2488
2505
|
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
|
|
2489
2506
|
...(stoppedBy !== undefined ? { stoppedBy } : {}),
|
|
2490
2507
|
summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300),
|
|
2508
|
+
...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
|
|
2491
2509
|
}, { priority: "later" });
|
|
2492
2510
|
}
|
|
2493
2511
|
catch {
|
|
@@ -2736,15 +2754,15 @@ export function createSendMessageTool(opts) {
|
|
|
2736
2754
|
const to = String(a.to ?? "").trim();
|
|
2737
2755
|
const message = String(a.message ?? "").trim();
|
|
2738
2756
|
if (!to)
|
|
2739
|
-
return { content: "Message not sent: 'to' was empty. Pass the agent's task_id (a…).", details: { error: "empty to" } };
|
|
2757
|
+
return { content: "Message not sent: 'to' was empty. Pass the agent's task_id (a…).", details: { error: "empty to" }, isError: true };
|
|
2740
2758
|
if (!message)
|
|
2741
|
-
return { content: "Message not sent: 'message' was empty.", details: { error: "empty message" } };
|
|
2759
|
+
return { content: "Message not sent: 'message' was empty.", details: { error: "empty message" }, isError: true };
|
|
2742
2760
|
const senderId = ctx.taskId ?? opts.owner;
|
|
2743
2761
|
if (senderId !== undefined && isObserverTaskId(senderId)) {
|
|
2744
|
-
return { content: OBSERVER_SENDMESSAGE_SENDER_REFUSAL, details: { error: "observer_sender" } };
|
|
2762
|
+
return { content: OBSERVER_SENDMESSAGE_SENDER_REFUSAL, details: { error: "observer_sender" }, isError: true };
|
|
2745
2763
|
}
|
|
2746
2764
|
if (isObserverTaskId(to)) {
|
|
2747
|
-
return { content: OBSERVER_SENDMESSAGE_TARGET_REFUSAL, details: { error: "observer_target", to } };
|
|
2765
|
+
return { content: OBSERVER_SENDMESSAGE_TARGET_REFUSAL, details: { error: "observer_target", to }, isError: true };
|
|
2748
2766
|
}
|
|
2749
2767
|
if (normalizeAgentName(to) === "main") {
|
|
2750
2768
|
if (opts.uplink && senderId !== undefined) {
|
|
@@ -2761,7 +2779,7 @@ export function createSendMessageTool(opts) {
|
|
|
2761
2779
|
}, { priority: "next" });
|
|
2762
2780
|
}
|
|
2763
2781
|
catch (e) {
|
|
2764
|
-
return { content: `Message not sent: the parent's notification lane rejected it (${e instanceof Error ? e.message : String(e)}).`, details: { error: "uplink_failed", to } };
|
|
2782
|
+
return { content: `Message not sent: the parent's notification lane rejected it (${e instanceof Error ? e.message : String(e)}).`, details: { error: "uplink_failed", to }, isError: true };
|
|
2765
2783
|
}
|
|
2766
2784
|
return {
|
|
2767
2785
|
content: `Message sent to main — it will reach the spawning conversation at its next turn boundary. Continue with your task; do not wait for a reply.`,
|
|
@@ -2772,6 +2790,7 @@ export function createSendMessageTool(opts) {
|
|
|
2772
2790
|
content: `Message not sent: "main" (the spawning conversation) is not a deliverable target here. ` +
|
|
2773
2791
|
`Your completion is reported to it automatically — finish your task and your final report will be relayed.`,
|
|
2774
2792
|
details: { error: "main_not_deliverable", to },
|
|
2793
|
+
isError: true,
|
|
2775
2794
|
};
|
|
2776
2795
|
}
|
|
2777
2796
|
const access = {
|
|
@@ -2808,12 +2827,14 @@ export function createSendMessageTool(opts) {
|
|
|
2808
2827
|
return {
|
|
2809
2828
|
content: `Message not sent: ${whoT3} was stopped${row.stoppedBy !== undefined ? ` (by ${row.stoppedBy})` : ""} — a stopped agent is not revived. Launch a new agent with the needed context instead.`,
|
|
2810
2829
|
details: { error: "killed", to, ...(row.stoppedBy !== undefined ? { stoppedBy: row.stoppedBy } : {}) },
|
|
2830
|
+
isError: true,
|
|
2811
2831
|
};
|
|
2812
2832
|
}
|
|
2813
2833
|
if (row.status === "parked") {
|
|
2814
2834
|
return {
|
|
2815
2835
|
content: `Message not sent: ${whoT3} is parked on a pending approval — it resumes when the approval is decided (durable approval inbox), not by message delivery. Send again after it resumes.`,
|
|
2816
2836
|
details: { error: "parked_pending_approval", to },
|
|
2837
|
+
isError: true,
|
|
2817
2838
|
};
|
|
2818
2839
|
}
|
|
2819
2840
|
if (row.status === "running") {
|
|
@@ -2821,17 +2842,20 @@ export function createSendMessageTool(opts) {
|
|
|
2821
2842
|
return {
|
|
2822
2843
|
content: `Message not sent: ${whoT3} is currently running in this process but its mid-run delivery channel is not reachable from here. Wait for its completion notification, then send again to continue it.`,
|
|
2823
2844
|
details: { error: "still_running", to },
|
|
2845
|
+
isError: true,
|
|
2824
2846
|
};
|
|
2825
2847
|
}
|
|
2826
2848
|
if (Date.now() - row.updatedAt > 10 * DURABLE_AGENT_HEARTBEAT_MS) {
|
|
2827
2849
|
return {
|
|
2828
2850
|
content: `Message not sent: ${whoT3} last ran on another host that has stopped renewing its record (no heartbeat). Its row will be settled by the stale-row sweep — send again after that to revive it.`,
|
|
2829
2851
|
details: { error: "host_gone", to },
|
|
2852
|
+
isError: true,
|
|
2830
2853
|
};
|
|
2831
2854
|
}
|
|
2832
2855
|
return {
|
|
2833
2856
|
content: `Message not sent: ${whoT3} is running on another host — this process has no delivery channel to it. Wait for its completion notification, then send again to continue it.`,
|
|
2834
2857
|
details: { error: "running_elsewhere", to },
|
|
2858
|
+
isError: true,
|
|
2835
2859
|
};
|
|
2836
2860
|
}
|
|
2837
2861
|
if (row.sessionId === undefined)
|
|
@@ -2840,6 +2864,7 @@ export function createSendMessageTool(opts) {
|
|
|
2840
2864
|
return {
|
|
2841
2865
|
content: `Message not sent: ${whoT3} is being revived or recycled right now — send again in a moment.`,
|
|
2842
2866
|
details: { error: "claim_contended", to },
|
|
2867
|
+
isError: true,
|
|
2843
2868
|
};
|
|
2844
2869
|
}
|
|
2845
2870
|
try {
|
|
@@ -2873,11 +2898,13 @@ export function createSendMessageTool(opts) {
|
|
|
2873
2898
|
return {
|
|
2874
2899
|
content: `Message not sent: ${whoT3} was just revived by another delivery — it is running now. Send again to reach the running agent.`,
|
|
2875
2900
|
details: { error: "claim_lost", to },
|
|
2901
|
+
isError: true,
|
|
2876
2902
|
};
|
|
2877
2903
|
}
|
|
2878
2904
|
return {
|
|
2879
2905
|
content: `Message not sent: ${whoT3}'s durable record changed underneath this delivery — send again.`,
|
|
2880
2906
|
details: { error: "claim_lost", to },
|
|
2907
|
+
isError: true,
|
|
2881
2908
|
};
|
|
2882
2909
|
}
|
|
2883
2910
|
const claimedRev = row.rev + 1;
|
|
@@ -2897,11 +2924,13 @@ export function createSendMessageTool(opts) {
|
|
|
2897
2924
|
return {
|
|
2898
2925
|
content: `Message not sent: ${whoT3}'s transcript session no longer exists (evicted or reaped) — it cannot be revived. Launch a new agent with the needed context instead.`,
|
|
2899
2926
|
details: { error: "resume.session_not_found", to },
|
|
2927
|
+
isError: true,
|
|
2900
2928
|
};
|
|
2901
2929
|
}
|
|
2902
2930
|
return {
|
|
2903
2931
|
content: `Message not sent: ${whoT3}'s transcript store did not answer (${e instanceof Error ? e.message : String(e)}) — nothing was parked; send again.`,
|
|
2904
2932
|
details: { error: "session_store_unavailable", to },
|
|
2933
|
+
isError: true,
|
|
2905
2934
|
};
|
|
2906
2935
|
}
|
|
2907
2936
|
const clipped = message.length > UPLINK_RESULT_MAX ? `${message.slice(0, UPLINK_RESULT_MAX)}\n[message truncated: ${message.length} chars total]` : message;
|
|
@@ -2915,6 +2944,7 @@ export function createSendMessageTool(opts) {
|
|
|
2915
2944
|
return {
|
|
2916
2945
|
content: `Message not sent: the durable mailbox refused the message (${e instanceof Error ? e.message : String(e)}) — nothing was parked and the agent was not revived.`,
|
|
2917
2946
|
details: { error: "mailbox_failed", to },
|
|
2947
|
+
isError: true,
|
|
2918
2948
|
};
|
|
2919
2949
|
}
|
|
2920
2950
|
const leaseOwner = `${opts.registry.writerId}:${ctx.toolCallId}`;
|
|
@@ -2954,7 +2984,7 @@ export function createSendMessageTool(opts) {
|
|
|
2954
2984
|
};
|
|
2955
2985
|
}
|
|
2956
2986
|
try {
|
|
2957
|
-
await opts.mailbox.ack(scope, handle, lease.maxSeq);
|
|
2987
|
+
await opts.mailbox.ack(scope, handle, leaseOwner, lease.maxSeq);
|
|
2958
2988
|
}
|
|
2959
2989
|
catch {
|
|
2960
2990
|
}
|
|
@@ -2996,7 +3026,7 @@ export function createSendMessageTool(opts) {
|
|
|
2996
3026
|
}
|
|
2997
3027
|
}
|
|
2998
3028
|
if (byName.status === "ambiguous") {
|
|
2999
|
-
return { content: `Message not sent: ${byName.message}`, details: { error: "ambiguous", to } };
|
|
3029
|
+
return { content: `Message not sent: ${byName.message}`, details: { error: "ambiguous", to }, isError: true };
|
|
3000
3030
|
}
|
|
3001
3031
|
if (byName.status === "found") {
|
|
3002
3032
|
const h = byName.handle;
|
|
@@ -3019,6 +3049,7 @@ export function createSendMessageTool(opts) {
|
|
|
3019
3049
|
content: `Message not sent: "${to}" is on the durable roster (agent ${rosterHit.agentId}) but not reachable in this run — its session belongs to an earlier process. ` +
|
|
3020
3050
|
`Launch a new agent with the needed context instead.`,
|
|
3021
3051
|
details: { error: "roster_only", to, agentId: rosterHit.agentId },
|
|
3052
|
+
isError: true,
|
|
3022
3053
|
};
|
|
3023
3054
|
}
|
|
3024
3055
|
const labels = opts.registry.runningBackgroundAgentLabels(access);
|
|
@@ -3028,16 +3059,17 @@ export function createSendMessageTool(opts) {
|
|
|
3028
3059
|
(labels.length > 0 ? ` Running background agents: ${labels.join(", ")}.` : "") +
|
|
3029
3060
|
` Note: a completed foreground agent is not resumable (its transcript is not retained) — spawn with run_in_background to keep an agent addressable, or launch a new agent.`,
|
|
3030
3061
|
details: { error: "not_found", to, ...(byName.suggestion !== undefined ? { suggestion: byName.suggestion } : {}) },
|
|
3062
|
+
isError: true,
|
|
3031
3063
|
};
|
|
3032
3064
|
}
|
|
3033
3065
|
}
|
|
3034
3066
|
const row = target;
|
|
3035
3067
|
if (row === undefined)
|
|
3036
|
-
return { content: `Message not sent: no agent named "${to}" is reachable.`, details: { error: "not_found", to } };
|
|
3068
|
+
return { content: `Message not sent: no agent named "${to}" is reachable.`, details: { error: "not_found", to }, isError: true };
|
|
3037
3069
|
const targetId = row.task_id;
|
|
3038
3070
|
const who = targetId === to ? `agent ${targetId}` : `agent "${to}" (${targetId})`;
|
|
3039
3071
|
if (row.type !== "background_agent") {
|
|
3040
|
-
return { content: `Message not sent: ${targetId} is a ${row.type} task, not a background agent.`, details: { error: "wrong_type", to } };
|
|
3072
|
+
return { content: `Message not sent: ${targetId} is a ${row.type} task, not a background agent.`, details: { error: "wrong_type", to }, isError: true };
|
|
3041
3073
|
}
|
|
3042
3074
|
if (row.status === "running" || row.status === "pending") {
|
|
3043
3075
|
const s2Summary = typeof a.summary === "string" && a.summary.trim() !== "" ? a.summary.trim().slice(0, 200) : undefined;
|
|
@@ -3066,11 +3098,13 @@ export function createSendMessageTool(opts) {
|
|
|
3066
3098
|
return {
|
|
3067
3099
|
content: `Message not sent: ${who} is still running and this deployment has no mid-run delivery channel for it. Wait for its completion notification, then SendMessage to continue it.`,
|
|
3068
3100
|
details: { error: "still_running", to },
|
|
3101
|
+
isError: true,
|
|
3069
3102
|
};
|
|
3070
3103
|
}
|
|
3071
3104
|
return {
|
|
3072
3105
|
content: `Message not sent: ${who} just finished (delivery raced its completion). Send again to continue it from its transcript.`,
|
|
3073
3106
|
details: { error: "settle_race", to },
|
|
3107
|
+
isError: true,
|
|
3074
3108
|
};
|
|
3075
3109
|
}
|
|
3076
3110
|
if (row.status === "killed") {
|
|
@@ -3078,6 +3112,7 @@ export function createSendMessageTool(opts) {
|
|
|
3078
3112
|
return {
|
|
3079
3113
|
content: `Message not sent: ${who} was stopped${by ? ` (by ${by})` : ""} — a stopped agent is not resumable. Launch a new agent with the needed context instead.`,
|
|
3080
3114
|
details: { error: "killed", to, ...(by !== undefined ? { stoppedBy: by } : {}) },
|
|
3115
|
+
isError: true,
|
|
3081
3116
|
};
|
|
3082
3117
|
}
|
|
3083
3118
|
const runLedger = ctx.subagentRetain ?? opts.retain;
|
|
@@ -3096,6 +3131,7 @@ export function createSendMessageTool(opts) {
|
|
|
3096
3131
|
return {
|
|
3097
3132
|
content: `Message not sent: ${who}'s session was not retained (this run did not enable retainSubagentSessions), so it cannot be continued — relaunch a new agent with the needed context instead.`,
|
|
3098
3133
|
details: { error: "not_retained", to },
|
|
3134
|
+
isError: true,
|
|
3099
3135
|
};
|
|
3100
3136
|
}
|
|
3101
3137
|
const resume = makeSubagentResume({
|
|
@@ -3144,7 +3180,7 @@ export function createSendMessageTool(opts) {
|
|
|
3144
3180
|
: code === "resume.row_gone"
|
|
3145
3181
|
? `${who}'s registry row no longer exists (terminal GC) — relaunch a new agent instead.`
|
|
3146
3182
|
: `${e instanceof Error ? e.message : String(e)}`;
|
|
3147
|
-
return { content: `Message not sent: ${text}`, details: { error: code ?? "resume_failed", to } };
|
|
3183
|
+
return { content: `Message not sent: ${text}`, details: { error: code ?? "resume_failed", to }, isError: true };
|
|
3148
3184
|
}
|
|
3149
3185
|
},
|
|
3150
3186
|
});
|
|
@@ -3169,7 +3205,7 @@ export function createAgentTranscriptTool(opts) {
|
|
|
3169
3205
|
const a = args;
|
|
3170
3206
|
const id = String(a.id ?? "").trim();
|
|
3171
3207
|
if (!id)
|
|
3172
|
-
return { content: "No transcript read: 'id' was empty. Pass the agent's task_id (a…).", details: { error: "empty id" } };
|
|
3208
|
+
return { content: "No transcript read: 'id' was empty. Pass the agent's task_id (a…).", details: { error: "empty id" }, isError: true };
|
|
3173
3209
|
const lastN = typeof a.lastN === "number" && Number.isFinite(a.lastN) ? Math.max(1, Math.min(AGENT_TRANSCRIPT_MAX_N, Math.floor(a.lastN))) : AGENT_TRANSCRIPT_DEFAULT_N;
|
|
3174
3210
|
const access = {
|
|
3175
3211
|
owner: ctx.taskId ?? opts.owner,
|
|
@@ -3214,12 +3250,13 @@ export function createAgentTranscriptTool(opts) {
|
|
|
3214
3250
|
return {
|
|
3215
3251
|
content: `No transcript: "${id}" is a WORKFLOW run id — AgentTranscript reads sub-agent transcripts only. For the run's per-agent rows (which agent failed, why, elapsed) call TaskOutput({ task_id: "${id}" }); to continue a fixed script, re-invoke the workflow with resumeFromRunId: "${id}".`,
|
|
3216
3252
|
details: { error: "workflow_id", id },
|
|
3253
|
+
isError: true,
|
|
3217
3254
|
};
|
|
3218
3255
|
}
|
|
3219
3256
|
const durable = await readDurableSteps();
|
|
3220
3257
|
if (durable)
|
|
3221
3258
|
return durable;
|
|
3222
|
-
return { content: `No transcript: no background agent task "${id}" (unknown id, not yours, or expired).`, details: { error: "not_found", id } };
|
|
3259
|
+
return { content: `No transcript: no background agent task "${id}" (unknown id, not yours, or expired).`, details: { error: "not_found", id }, isError: true };
|
|
3223
3260
|
}
|
|
3224
3261
|
const runLedger = ctx.subagentRetain ?? undefined;
|
|
3225
3262
|
const atLedgerSessionId = ctx.sessionId ?? opts.sessionId;
|
|
@@ -3232,6 +3269,7 @@ export function createAgentTranscriptTool(opts) {
|
|
|
3232
3269
|
return {
|
|
3233
3270
|
content: `No transcript: agent ${id}'s session was not retained (this run did not enable retainSubagentSessions), so its history is unavailable. Its completion notification carried the recent steps.`,
|
|
3234
3271
|
details: { error: "not_retained", id },
|
|
3272
|
+
isError: true,
|
|
3235
3273
|
};
|
|
3236
3274
|
}
|
|
3237
3275
|
let steps;
|
|
@@ -3246,6 +3284,7 @@ export function createAgentTranscriptTool(opts) {
|
|
|
3246
3284
|
return {
|
|
3247
3285
|
content: `No transcript: agent ${id}'s session could not be read (${code ?? (e instanceof Error ? e.message : String(e))}).`,
|
|
3248
3286
|
details: { error: code ?? "read_failed", id },
|
|
3287
|
+
isError: true,
|
|
3249
3288
|
};
|
|
3250
3289
|
}
|
|
3251
3290
|
if (steps.length === 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
-
import { defineTool } from "./tools.js";
|
|
2
|
+
import { defineTool, errorResult } from "./tools.js";
|
|
3
3
|
import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
|
|
4
4
|
export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
5
5
|
const NO_HUMAN = "No human is available to answer right now. Proceed with your best judgment: pick the most reasonable " +
|
|
@@ -66,23 +66,23 @@ export function createAskUserQuestionTool(onQuestion, source) {
|
|
|
66
66
|
execute: async (args, ctx) => {
|
|
67
67
|
const { questions } = args;
|
|
68
68
|
if (!Array.isArray(questions) || questions.length < 1 || questions.length > 4) {
|
|
69
|
-
return "Error (AskUserQuestion): provide between 1 and 4 questions.";
|
|
69
|
+
return errorResult("Error (AskUserQuestion): provide between 1 and 4 questions.");
|
|
70
70
|
}
|
|
71
71
|
const seenHeaders = new Set();
|
|
72
72
|
for (const q of questions) {
|
|
73
73
|
if (!Array.isArray(q.options) || q.options.length < 2 || q.options.length > 4) {
|
|
74
|
-
return `Error (AskUserQuestion): the question "${q.header || q.question}" must have between 2 and 4 options
|
|
74
|
+
return errorResult(`Error (AskUserQuestion): the question "${q.header || q.question}" must have between 2 and 4 options.`);
|
|
75
75
|
}
|
|
76
76
|
const key = typeof q.header === "string" ? q.header.trim() : "";
|
|
77
77
|
if (key === "" || seenHeaders.has(key)) {
|
|
78
|
-
return `Error (AskUserQuestion): each question needs a distinct, non-empty header (got a duplicate or empty one)
|
|
78
|
+
return errorResult(`Error (AskUserQuestion): each question needs a distinct, non-empty header (got a duplicate or empty one).`);
|
|
79
79
|
}
|
|
80
80
|
seenHeaders.add(key);
|
|
81
81
|
const seenLabels = new Set();
|
|
82
82
|
for (const o of q.options) {
|
|
83
83
|
const labelKey = typeof o?.label === "string" ? o.label.trim() : "";
|
|
84
84
|
if (seenLabels.has(labelKey)) {
|
|
85
|
-
return `Error (AskUserQuestion): the question "${q.header || q.question}" has duplicate option labels — option labels must be unique within each question
|
|
85
|
+
return errorResult(`Error (AskUserQuestion): the question "${q.header || q.question}" has duplicate option labels — option labels must be unique within each question.`);
|
|
86
86
|
}
|
|
87
87
|
seenLabels.add(labelKey);
|
|
88
88
|
}
|
|
@@ -28,6 +28,7 @@ export interface BackgroundAgentRecord {
|
|
|
28
28
|
settledAt?: number;
|
|
29
29
|
status: "running" | "parked" | "completed" | "failed" | "killed";
|
|
30
30
|
stoppedBy?: string;
|
|
31
|
+
completionId?: string;
|
|
31
32
|
seq?: number;
|
|
32
33
|
parkedCheckpointToken?: string;
|
|
33
34
|
parkedAt?: number;
|
|
@@ -66,6 +67,7 @@ export declare function canAccessAgentRecord(record: Pick<BackgroundAgentRecord,
|
|
|
66
67
|
scope?: string;
|
|
67
68
|
sessionId?: string;
|
|
68
69
|
}): boolean;
|
|
70
|
+
export declare const STALE_RUNNING_REAP_ATTRIBUTION = "the host process was interrupted while this agent was running \u2014 its in-process state was lost. Check its worktree / output file for partial work before assuming the task landed (stale running row reaped).";
|
|
69
71
|
export interface BackgroundAgentStore {
|
|
70
72
|
put(record: BackgroundAgentRecord): Promise<void>;
|
|
71
73
|
get(handle: string, scope: string): Promise<BackgroundAgentRecord | null>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { uuidv7 } from "../internal/harness.js";
|
|
1
2
|
export class BackgroundAgentStoreError extends Error {
|
|
2
3
|
code;
|
|
3
4
|
constructor(code, message) {
|
|
@@ -20,6 +21,7 @@ export function canAccessAgentRecord(record, access) {
|
|
|
20
21
|
}
|
|
21
22
|
return false;
|
|
22
23
|
}
|
|
24
|
+
export const STALE_RUNNING_REAP_ATTRIBUTION = "the host process was interrupted while this agent was running — its in-process state was lost. Check its worktree / output file for partial work before assuming the task landed (stale running row reaped).";
|
|
23
25
|
export async function reconcileParkedAgents(stores, scope, now, opts) {
|
|
24
26
|
const out = { failed: 0, rolledBack: 0 };
|
|
25
27
|
const excluded = (row) => (opts?.excludeHandles?.has(row.handle) ?? false) ||
|
|
@@ -31,6 +33,7 @@ export async function reconcileParkedAgents(stores, scope, now, opts) {
|
|
|
31
33
|
next.stoppedBy = "system";
|
|
32
34
|
next.settledAt = now;
|
|
33
35
|
next.updatedAt = now;
|
|
36
|
+
next.completionId ??= uuidv7();
|
|
34
37
|
delete next.parkedCheckpointToken;
|
|
35
38
|
delete next.parkClaimId;
|
|
36
39
|
delete next.parkedAt;
|
|
@@ -248,7 +251,8 @@ export class InMemoryBackgroundAgentStore {
|
|
|
248
251
|
const flipped = structuredClone(r);
|
|
249
252
|
flipped.status = "failed";
|
|
250
253
|
flipped.stoppedBy = "system";
|
|
251
|
-
flipped.summary = flipped.summary ??
|
|
254
|
+
flipped.summary = flipped.summary ?? STALE_RUNNING_REAP_ATTRIBUTION;
|
|
255
|
+
flipped.error = flipped.error ?? STALE_RUNNING_REAP_ATTRIBUTION;
|
|
252
256
|
flipped.settledAt = now;
|
|
253
257
|
flipped.updatedAt = now;
|
|
254
258
|
flipped.rev = r.rev + 1;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { StringDecoder } from "node:string_decoder";
|
|
1
2
|
export declare const MAX_EXEC_OUTPUT_BYTES: number;
|
|
2
3
|
export declare class RollingTailBuffer {
|
|
3
4
|
private readonly maxBytes;
|
|
4
5
|
private readonly chunks;
|
|
5
6
|
private size;
|
|
6
|
-
private
|
|
7
|
+
private headDropped;
|
|
8
|
+
private sourceSkipped;
|
|
7
9
|
constructor(maxBytes?: number);
|
|
8
10
|
push(chunk: Buffer): void;
|
|
9
11
|
recordSkippedBytes(n: number): void;
|
|
@@ -11,5 +13,20 @@ export declare class RollingTailBuffer {
|
|
|
11
13
|
text: string;
|
|
12
14
|
droppedBytes: number;
|
|
13
15
|
};
|
|
16
|
+
bytes(): Buffer;
|
|
17
|
+
headDroppedBytes(): number;
|
|
18
|
+
sourceSkippedBytes(): number;
|
|
14
19
|
}
|
|
20
|
+
export interface StreamCursorState {
|
|
21
|
+
tail: RollingTailBuffer;
|
|
22
|
+
cursorBytes: number;
|
|
23
|
+
acceptedBytes: number;
|
|
24
|
+
disclosedSkippedBytes: number;
|
|
25
|
+
decoder: StringDecoder;
|
|
26
|
+
}
|
|
27
|
+
export declare function newStreamCursorState(tail?: RollingTailBuffer): StreamCursorState;
|
|
28
|
+
export declare function sliceStreamIncrement(s: StreamCursorState, terminal: boolean): {
|
|
29
|
+
inc: string;
|
|
30
|
+
droppedBeforeCursor: number;
|
|
31
|
+
};
|
|
15
32
|
export declare function markTruncated(text: string, droppedBytes: number): string;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { StringDecoder } from "node:string_decoder";
|
|
1
2
|
export const MAX_EXEC_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
2
3
|
export class RollingTailBuffer {
|
|
3
4
|
maxBytes;
|
|
4
5
|
chunks = [];
|
|
5
6
|
size = 0;
|
|
6
|
-
|
|
7
|
+
headDropped = 0;
|
|
8
|
+
sourceSkipped = 0;
|
|
7
9
|
constructor(maxBytes = MAX_EXEC_OUTPUT_BYTES) {
|
|
8
10
|
this.maxBytes = maxBytes;
|
|
9
11
|
}
|
|
@@ -18,22 +20,53 @@ export class RollingTailBuffer {
|
|
|
18
20
|
if (over >= head.length) {
|
|
19
21
|
this.chunks.shift();
|
|
20
22
|
this.size -= head.length;
|
|
21
|
-
this.
|
|
23
|
+
this.headDropped += head.length;
|
|
22
24
|
}
|
|
23
25
|
else {
|
|
24
26
|
this.chunks[0] = head.subarray(over);
|
|
25
27
|
this.size -= over;
|
|
26
|
-
this.
|
|
28
|
+
this.headDropped += over;
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
recordSkippedBytes(n) {
|
|
31
33
|
if (n > 0)
|
|
32
|
-
this.
|
|
34
|
+
this.sourceSkipped += n;
|
|
33
35
|
}
|
|
34
36
|
result() {
|
|
35
|
-
return { text: Buffer.concat(this.chunks).toString("utf8"), droppedBytes: this.
|
|
37
|
+
return { text: Buffer.concat(this.chunks).toString("utf8"), droppedBytes: this.headDropped + this.sourceSkipped };
|
|
36
38
|
}
|
|
39
|
+
bytes() {
|
|
40
|
+
return Buffer.concat(this.chunks);
|
|
41
|
+
}
|
|
42
|
+
headDroppedBytes() {
|
|
43
|
+
return this.headDropped;
|
|
44
|
+
}
|
|
45
|
+
sourceSkippedBytes() {
|
|
46
|
+
return this.sourceSkipped;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function newStreamCursorState(tail = new RollingTailBuffer()) {
|
|
50
|
+
return { tail, cursorBytes: 0, acceptedBytes: 0, disclosedSkippedBytes: 0, decoder: new StringDecoder("utf8") };
|
|
51
|
+
}
|
|
52
|
+
export function sliceStreamIncrement(s, terminal) {
|
|
53
|
+
const headDropped = s.tail.headDroppedBytes();
|
|
54
|
+
const sourceSkipped = s.tail.sourceSkippedBytes();
|
|
55
|
+
const newSkipped = Math.max(0, sourceSkipped - s.disclosedSkippedBytes);
|
|
56
|
+
s.disclosedSkippedBytes = sourceSkipped;
|
|
57
|
+
const droppedBeforeCursor = Math.max(0, headDropped - s.cursorBytes) + newSkipped;
|
|
58
|
+
const buf = s.tail.bytes();
|
|
59
|
+
const startInTail = Math.min(Math.max(s.cursorBytes, headDropped) - headDropped, buf.length);
|
|
60
|
+
const incBuf = startInTail <= 0 ? buf : buf.subarray(startInTail);
|
|
61
|
+
if (droppedBeforeCursor > 0)
|
|
62
|
+
s.decoder = new StringDecoder("utf8");
|
|
63
|
+
let inc = s.decoder.write(incBuf);
|
|
64
|
+
if (terminal) {
|
|
65
|
+
inc += s.decoder.end();
|
|
66
|
+
s.decoder = new StringDecoder("utf8");
|
|
67
|
+
}
|
|
68
|
+
s.cursorBytes = s.acceptedBytes;
|
|
69
|
+
return { inc, droppedBeforeCursor };
|
|
37
70
|
}
|
|
38
71
|
export function markTruncated(text, droppedBytes) {
|
|
39
72
|
return droppedBytes > 0
|
|
@@ -18,6 +18,7 @@ export declare class TransportLspSession implements LspSession {
|
|
|
18
18
|
request(op: LspOperation, params: LspRequestParams, signal?: AbortSignal): Promise<LspResult>;
|
|
19
19
|
private syncOpenedFiles;
|
|
20
20
|
private syncOne;
|
|
21
|
+
private static classifyUnbranded;
|
|
21
22
|
get closed(): boolean;
|
|
22
23
|
openedFiles(): string[];
|
|
23
24
|
warmOpen(filePaths: string[], signal?: AbortSignal): Promise<void>;
|