@sema-agent/core 2.2.0 → 2.4.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.d.ts +4 -0
- package/dist/agents/send-message-tool.js +37 -24
- package/dist/agents/subagent.js +275 -127
- package/dist/agents/teacher.js +51 -23
- package/dist/brain/errors.d.ts +1 -0
- package/dist/brain/errors.js +14 -0
- package/dist/brain/stream-engine.js +3 -3
- package/dist/core/context-edit.js +2 -1
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +12 -8
- package/dist/core/runner/prepare-task.js +32 -7
- package/dist/core/runner/runtask.js +18 -5
- package/dist/core/runner/tool-output-projection.js +2 -1
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +37 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +195 -0
- package/dist/core/store-contracts/contract-harness.d.ts +6 -0
- package/dist/core/store-contracts/contract-harness.js +16 -0
- package/dist/core/store-contracts/contract-kit-version.d.ts +1 -0
- package/dist/core/store-contracts/contract-kit-version.js +2 -0
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-snapshot-store-contract.js +126 -0
- package/dist/core/store-contracts/mailbox-store-contract.d.ts +6 -0
- package/dist/core/store-contracts/mailbox-store-contract.js +193 -0
- package/dist/core/store-contracts/session-repo-contract.d.ts +3 -0
- package/dist/core/store-contracts/session-repo-contract.js +36 -0
- package/dist/core/store-contracts/tool-result-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/tool-result-store-contract.js +35 -0
- package/dist/core/task-notification.d.ts +2 -0
- package/dist/core/task-registry-agent.d.ts +7 -1
- package/dist/core/task-registry-agent.js +31 -2
- package/dist/core/task-registry-monitor.js +71 -6
- package/dist/core/task-registry-shared.d.ts +18 -1
- package/dist/core/task-registry-shared.js +5 -2
- package/dist/core/task-registry.d.ts +9 -0
- package/dist/core/task-registry.js +52 -7
- package/dist/core/tool-result-store.d.ts +3 -2
- package/dist/core/tool-result-store.js +12 -4
- package/dist/core/tools.d.ts +2 -0
- package/dist/core/tools.js +9 -0
- package/dist/core/trace.d.ts +7 -0
- package/dist/core/workflow-journal-store.d.ts +16 -0
- package/dist/core/workflow-journal-store.js +28 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +16 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/orchestration/builtin-workflows.d.ts +1 -1
- package/dist/orchestration/builtin-workflows.js +11 -2
- package/dist/orchestration/workflow-governance.d.ts +6 -1
- package/dist/orchestration/workflow-governance.js +24 -4
- package/dist/orchestration/workflow-primitives.js +7 -1
- package/dist/orchestration/workflow.d.ts +16 -0
- package/dist/orchestration/workflow.js +94 -21
- package/dist/tools/fs/fs-bash.d.ts +7 -1
- package/dist/tools/fs/fs-bash.js +51 -20
- package/dist/tools/fs/fs-read.js +22 -11
- package/dist/tools/fs/fs-search-tools.js +3 -3
- package/dist/tools/fs/fs-shared.d.ts +20 -7
- package/dist/tools/fs/fs-shared.js +17 -3
- package/dist/tools/fs/fs-write.js +4 -4
- package/dist/tools/fs/index.d.ts +2 -0
- package/dist/tools/fs/index.js +7 -1
- package/dist/tools/fs/repo-map.js +2 -2
- package/dist/tools/fs/safety.d.ts +10 -0
- package/dist/tools/fs/safety.js +15 -1
- package/dist/tools/monitor.d.ts +2 -0
- package/dist/tools/monitor.js +20 -4
- package/dist/tools/web.js +6 -2
- package/dist/tools/worktree.js +46 -25
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
2
|
-
import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, MONITOR_STORM_BURST, MONITOR_STORM_KILL_AFTER_MS, MONITOR_LINE_BUF_CAP, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, clipMonitorLine, terminalTaskSummary, accountDroppedBytes, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
|
|
2
|
+
import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, MONITOR_STORM_BURST, MONITOR_STORM_KILL_AFTER_MS, MONITOR_LINE_BUF_CAP, MONITOR_SPILL_CAP_CHARS, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, clipMonitorLine, terminalTaskSummary, accountDroppedBytes, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
|
|
3
|
+
import { buildToolResultRef, OFFLOAD_TOOL_NAME } from "./tool-result-store.js";
|
|
3
4
|
export function registerMonitorLane(core, input) {
|
|
4
5
|
assertOwnership(input, "registerMonitor");
|
|
5
6
|
const id = core.mintTaskId("monitor");
|
|
@@ -37,17 +38,66 @@ export function registerMonitorLane(core, input) {
|
|
|
37
38
|
...(timeoutMs !== undefined ? { timeoutMs, deadlineAt: timers.now() + timeoutMs } : {}),
|
|
38
39
|
timers,
|
|
39
40
|
...(input.onEvent !== undefined ? { onEvent: input.onEvent } : {}),
|
|
41
|
+
...(input.toolResultStore !== undefined ? { toolResultStore: input.toolResultStore } : {}),
|
|
42
|
+
...(input.sessionId !== undefined ? { spillSessionId: input.sessionId } : {}),
|
|
40
43
|
};
|
|
41
44
|
core.handles.set(id, handle);
|
|
42
45
|
startMonitorWatcherLane(core, id);
|
|
43
46
|
return id;
|
|
44
47
|
}
|
|
48
|
+
function spillRolledMonitorChunk(handle, stream, dropped) {
|
|
49
|
+
const store = handle.toolResultStore;
|
|
50
|
+
if (store === undefined)
|
|
51
|
+
return;
|
|
52
|
+
const used = handle.spillCharsUsed ?? 0;
|
|
53
|
+
if (used >= MONITOR_SPILL_CAP_CHARS) {
|
|
54
|
+
handle.spillCapped = true;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const n = stream === "out" ? (handle.spillSegCount ?? 0) : (handle.spillErrSegCount ?? 0);
|
|
58
|
+
const ref = buildToolResultRef(handle.spillSessionId ?? "no-session", `${handle.id}_${stream}_seg${n}`);
|
|
59
|
+
handle.spillCharsUsed = used + dropped.length;
|
|
60
|
+
if (stream === "out")
|
|
61
|
+
handle.spillSegCount = n + 1;
|
|
62
|
+
else
|
|
63
|
+
handle.spillErrSegCount = n + 1;
|
|
64
|
+
try {
|
|
65
|
+
void Promise.resolve(store.put(ref, dropped)).catch(() => {
|
|
66
|
+
handle.spillFailed = true;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
handle.spillFailed = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function monitorSpillNote(handle) {
|
|
74
|
+
const outN = handle.spillSegCount ?? 0;
|
|
75
|
+
const errN = handle.spillErrSegCount ?? 0;
|
|
76
|
+
if (outN === 0 && errN === 0)
|
|
77
|
+
return "";
|
|
78
|
+
const sid = handle.spillSessionId ?? "no-session";
|
|
79
|
+
const segLabel = (stream, n) => {
|
|
80
|
+
const first = buildToolResultRef(sid, `${handle.id}_${stream}_seg0`);
|
|
81
|
+
return n <= 1 ? `ref "${first}"` : `refs "${first}" .. "${buildToolResultRef(sid, `${handle.id}_${stream}_seg${n - 1}`)}"`;
|
|
82
|
+
};
|
|
83
|
+
const clauses = [];
|
|
84
|
+
if (outN > 0)
|
|
85
|
+
clauses.push(`stdout ${segLabel("out", outN)}`);
|
|
86
|
+
if (errN > 0)
|
|
87
|
+
clauses.push(`stderr ${segLabel("err", errN)}`);
|
|
88
|
+
const coverage = handle.spillFailed === true
|
|
89
|
+
? " — a write failed partway through; the ref chain may be INCOMPLETE, read what is there"
|
|
90
|
+
: handle.spillCapped === true
|
|
91
|
+
? ` — spill cap (${MONITOR_SPILL_CAP_CHARS} chars) reached; earlier segments retained, later rolls were not spilled`
|
|
92
|
+
: "";
|
|
93
|
+
return `; spilled to ${clauses.join(", ")} (read back via ${OFFLOAD_TOOL_NAME})${coverage}`;
|
|
94
|
+
}
|
|
45
95
|
export function absorbMonitorPollLane(handle, v) {
|
|
46
96
|
const spool = handle.spool;
|
|
47
97
|
accountDroppedBytes(spool, v);
|
|
48
98
|
const ROLL_CAP = 2 * TASK_OUTPUT_MAX_CHARS;
|
|
49
|
-
spool.stdout = rollSpoolText(spool, spool.stdout + v.stdout, ROLL_CAP);
|
|
50
|
-
spool.stderr = rollSpoolText(spool, spool.stderr + v.stderr, ROLL_CAP);
|
|
99
|
+
spool.stdout = rollSpoolText(spool, spool.stdout + v.stdout, ROLL_CAP, (dropped) => spillRolledMonitorChunk(handle, "out", dropped));
|
|
100
|
+
spool.stderr = rollSpoolText(spool, spool.stderr + v.stderr, ROLL_CAP, (dropped) => spillRolledMonitorChunk(handle, "err", dropped));
|
|
51
101
|
handle.lineBuf += v.stdout;
|
|
52
102
|
if (handle.lineBuf.length > MONITOR_LINE_BUF_CAP)
|
|
53
103
|
handle.lineBuf = handle.lineBuf.slice(-MONITOR_LINE_BUF_CAP);
|
|
@@ -264,7 +314,7 @@ export async function pollMonitorLane(handle, filter, deadline, signal) {
|
|
|
264
314
|
const retrieval = running && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success";
|
|
265
315
|
const timedOutClause = spool.timedOut === true ? ` (timed out after ${spool.timeoutSec !== undefined ? `${spool.timeoutSec}s` : "its background time budget"})` : "";
|
|
266
316
|
const displayStatus = (spool.exitCode !== undefined ? `exited(code ${spool.exitCode})` : running ? "running" : handle.status) + timedOutClause;
|
|
267
|
-
const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)\n` : "";
|
|
317
|
+
const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)${monitorSpillNote(handle)}\n` : "";
|
|
268
318
|
const droppedNote = (spool.droppedBytes ?? 0) > 0
|
|
269
319
|
? `[!] ${spool.droppedBytes} earlier byte(s) permanently dropped before this window (8MB buffer evicted)\n`
|
|
270
320
|
: spool.dropUnknown === true
|
|
@@ -299,6 +349,21 @@ function consumePendingSuppressionNote(handle) {
|
|
|
299
349
|
handle.suppressedBatches = 0;
|
|
300
350
|
return ` [${suppressed} event batch(es) suppressed before the watch ended — output rate was too high; the suppressed output is still readable via TaskOutput.]`;
|
|
301
351
|
}
|
|
352
|
+
function explicitStopTerminalNote(handle, alreadyGone) {
|
|
353
|
+
const label = (handle.description ?? "monitor").slice(0, 200);
|
|
354
|
+
const detail = alreadyGone
|
|
355
|
+
? "killed before completion (stopped via TaskStop; the process was already gone); watch ended."
|
|
356
|
+
: "killed before completion (stopped via TaskStop); watch ended.";
|
|
357
|
+
emitMonitorEventLane(handle, {
|
|
358
|
+
task_id: handle.id,
|
|
359
|
+
task_type: "monitor",
|
|
360
|
+
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
361
|
+
status: "killed",
|
|
362
|
+
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
363
|
+
summary: `${terminalTaskSummary("monitor", label, "killed")} — ${detail}${droppedGapNote(handle.spool)}`,
|
|
364
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
302
367
|
export async function stopMonitorLane(core, handle) {
|
|
303
368
|
const wasRunning = handle.status === "running";
|
|
304
369
|
if (handle.watcher !== undefined) {
|
|
@@ -358,7 +423,7 @@ export async function stopMonitorLane(core, handle) {
|
|
|
358
423
|
handle.status = "killed";
|
|
359
424
|
handle.updatedAt = Date.now();
|
|
360
425
|
mintCompletionId(handle);
|
|
361
|
-
core.
|
|
426
|
+
core.notifyTerminalOnce(handle, () => explicitStopTerminalNote(handle, true));
|
|
362
427
|
return {
|
|
363
428
|
content: `Terminated ${handle.id} (the process was already gone).${drainNote}${consumePendingSuppressionNote(handle)}`,
|
|
364
429
|
details: {
|
|
@@ -386,7 +451,7 @@ export async function stopMonitorLane(core, handle) {
|
|
|
386
451
|
handle.status = "killed";
|
|
387
452
|
handle.updatedAt = Date.now();
|
|
388
453
|
mintCompletionId(handle);
|
|
389
|
-
core.
|
|
454
|
+
core.notifyTerminalOnce(handle, () => explicitStopTerminalNote(handle, false));
|
|
390
455
|
}
|
|
391
456
|
return {
|
|
392
457
|
content: `Terminated ${handle.id}.${drainNote}${consumePendingSuppressionNote(handle)}`,
|
|
@@ -4,6 +4,7 @@ import { type WorkflowRunStore } from "./workflow-run-store.js";
|
|
|
4
4
|
import type { BackgroundAgentRecord, BackgroundAgentStore } from "./background-agent-store.js";
|
|
5
5
|
import type { WorkflowHandle, WorkflowRun } from "../orchestration/workflow.js";
|
|
6
6
|
import type { TaskNotificationPayload } from "./task-notification.js";
|
|
7
|
+
import type { ToolResultStore } from "./tool-result-store.js";
|
|
7
8
|
export type SemaTaskType = "background_bash" | "workflow" | "background_agent" | "monitor";
|
|
8
9
|
export type StopSource = "user" | "parent" | "system" | (string & {});
|
|
9
10
|
export type SemaTaskStatus = "pending" | "running" | "parked" | "completed" | "failed" | "killed" | "cancelled";
|
|
@@ -15,6 +16,8 @@ export interface UnifiedTaskOutput {
|
|
|
15
16
|
retrieval_status: TaskRetrievalStatus;
|
|
16
17
|
content?: string;
|
|
17
18
|
error?: string;
|
|
19
|
+
errorCode?: string;
|
|
20
|
+
retryable?: boolean;
|
|
18
21
|
stoppedBy?: StopSource;
|
|
19
22
|
seq?: number;
|
|
20
23
|
partial_result?: boolean;
|
|
@@ -132,10 +135,15 @@ export interface BackgroundAgentTaskHandle extends SemaTaskHandle {
|
|
|
132
135
|
sessionScoped?: true;
|
|
133
136
|
result?: string;
|
|
134
137
|
resultFull?: string;
|
|
138
|
+
spillRef?: string;
|
|
135
139
|
error?: string;
|
|
140
|
+
errorCode?: string;
|
|
141
|
+
errorRetryable?: boolean;
|
|
136
142
|
resultIsPartial?: boolean;
|
|
137
143
|
stopSource?: StopSource;
|
|
138
144
|
stoppedBy?: StopSource;
|
|
145
|
+
onReapTerminal?: () => void;
|
|
146
|
+
terminalNotified?: true;
|
|
139
147
|
}
|
|
140
148
|
export interface MonitorTimers {
|
|
141
149
|
setInterval: (fn: () => unknown, ms: number) => unknown;
|
|
@@ -182,6 +190,13 @@ export interface MonitorTaskHandle extends SemaTaskHandle {
|
|
|
182
190
|
stopSource?: StopSource;
|
|
183
191
|
stoppedBy?: StopSource;
|
|
184
192
|
terminalNotified?: true;
|
|
193
|
+
toolResultStore?: ToolResultStore;
|
|
194
|
+
spillSessionId?: string;
|
|
195
|
+
spillSegCount?: number;
|
|
196
|
+
spillErrSegCount?: number;
|
|
197
|
+
spillCharsUsed?: number;
|
|
198
|
+
spillCapped?: true;
|
|
199
|
+
spillFailed?: true;
|
|
185
200
|
}
|
|
186
201
|
export type RegisteredTaskHandle = BackgroundBashTaskHandle | WorkflowTaskHandle | BackgroundAgentTaskHandle | MonitorTaskHandle;
|
|
187
202
|
export interface RegisterMonitorInput extends TaskAccess {
|
|
@@ -198,6 +213,7 @@ export interface RegisterMonitorInput extends TaskAccess {
|
|
|
198
213
|
maxBatchesPerMinute?: number;
|
|
199
214
|
stormBurst?: number;
|
|
200
215
|
now?: number;
|
|
216
|
+
toolResultStore?: ToolResultStore;
|
|
201
217
|
}
|
|
202
218
|
export interface RegisterWorkflowInput extends TaskAccess {
|
|
203
219
|
runId: string;
|
|
@@ -226,7 +242,8 @@ export declare function clipTaskOutput(s: string, fullOutputPath?: string): stri
|
|
|
226
242
|
export declare function statusFromBackground(status: string, exitCode?: number): SemaTaskStatus;
|
|
227
243
|
export declare function rollSpoolText(spool: {
|
|
228
244
|
rolledChars: number;
|
|
229
|
-
}, s: string, cap: number): string;
|
|
245
|
+
}, s: string, cap: number, onDrop?: (dropped: string) => void): string;
|
|
246
|
+
export declare const MONITOR_SPILL_CAP_CHARS: number;
|
|
230
247
|
export declare function accountDroppedBytes(spool: {
|
|
231
248
|
droppedBytes?: number;
|
|
232
249
|
dropUnknown?: true;
|
|
@@ -85,13 +85,16 @@ export function statusFromBackground(status, exitCode) {
|
|
|
85
85
|
return exitCode === 0 ? "completed" : "failed";
|
|
86
86
|
return "failed";
|
|
87
87
|
}
|
|
88
|
-
export function rollSpoolText(spool, s, cap) {
|
|
88
|
+
export function rollSpoolText(spool, s, cap, onDrop) {
|
|
89
89
|
if (s.length <= cap)
|
|
90
90
|
return s;
|
|
91
91
|
const half = Math.floor(cap / 2);
|
|
92
|
-
|
|
92
|
+
const dropped = s.slice(half, s.length - half);
|
|
93
|
+
spool.rolledChars += dropped.length;
|
|
94
|
+
onDrop?.(dropped);
|
|
93
95
|
return s.slice(0, half) + s.slice(s.length - half);
|
|
94
96
|
}
|
|
97
|
+
export const MONITOR_SPILL_CAP_CHARS = 64 * 1024 * 1024;
|
|
95
98
|
export function accountDroppedBytes(spool, poll) {
|
|
96
99
|
const d = poll.bytesDroppedBeforeCursor ?? 0;
|
|
97
100
|
if (d > 0)
|
|
@@ -4,6 +4,7 @@ import { type BackgroundShellCapability, type BackgroundShellId } from "./backgr
|
|
|
4
4
|
import type { WorkflowRunStore } from "./workflow-run-store.js";
|
|
5
5
|
import { type BackgroundAgentStore } from "./background-agent-store.js";
|
|
6
6
|
import type { TaskNotificationPayload } from "./task-notification.js";
|
|
7
|
+
import type { ToolResultStore } from "./tool-result-store.js";
|
|
7
8
|
import { type SemaTaskType, type StopSource, type SemaTaskStatus, type TaskRetrievalStatus, type UnifiedTaskOutput, type UnifiedTaskResult, type SemaTaskHandle, type TaskAccess, type BackgroundAgentTaskHandle, type MonitorTimers, type RegisterMonitorInput, type RegisterWorkflowInput, canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE } from "./task-registry-shared.js";
|
|
8
9
|
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
9
10
|
export type { ParkedClaimTicket, RegisterBackgroundAgentInput } from "./task-registry-shared.js";
|
|
@@ -33,6 +34,7 @@ export interface TaskPollOptions {
|
|
|
33
34
|
timeoutMs?: number;
|
|
34
35
|
signal?: AbortSignal;
|
|
35
36
|
oneShot?: boolean;
|
|
37
|
+
toolResultStore?: ToolResultStore;
|
|
36
38
|
}
|
|
37
39
|
export interface TaskStopOptions {
|
|
38
40
|
workflowStore?: WorkflowRunStore;
|
|
@@ -45,6 +47,7 @@ export interface TaskToolOptions extends TaskAccess {
|
|
|
45
47
|
deadlineMs?: () => number | undefined;
|
|
46
48
|
notificationWired?: boolean;
|
|
47
49
|
oneShot?: boolean;
|
|
50
|
+
toolResultStore?: ToolResultStore;
|
|
48
51
|
}
|
|
49
52
|
export interface AccessibleTaskRow {
|
|
50
53
|
task_id: string;
|
|
@@ -130,6 +133,8 @@ export declare class TaskRegistry {
|
|
|
130
133
|
result?: string;
|
|
131
134
|
resultFull?: string;
|
|
132
135
|
error?: string;
|
|
136
|
+
errorCode?: string;
|
|
137
|
+
retryable?: boolean;
|
|
133
138
|
stoppedBy?: StopSource;
|
|
134
139
|
seq?: number;
|
|
135
140
|
}): "completed" | "failed" | "killed" | undefined;
|
|
@@ -163,9 +168,13 @@ export declare class TaskRegistry {
|
|
|
163
168
|
result?: string;
|
|
164
169
|
resultFull?: string;
|
|
165
170
|
error?: string;
|
|
171
|
+
errorCode?: string;
|
|
172
|
+
retryable?: boolean;
|
|
166
173
|
}): "completed" | "failed" | "killed" | undefined;
|
|
167
174
|
unmarkRetainedContinuation(id: string): void;
|
|
168
175
|
attachAgentNotify(id: string, notify: NonNullable<BackgroundAgentTaskHandle["notify"]>, cycle?: number): void;
|
|
176
|
+
attachAgentTerminalNotifier(id: string, fn: () => void): void;
|
|
177
|
+
claimAgentTerminalNotify(id: string): boolean;
|
|
169
178
|
deliverToRunningAgent(id: string, access: TaskAccess, notification: import("./task-notification.js").TaskNotificationPayload, opts?: {
|
|
170
179
|
priority?: import("./task-notification.js").SystemInjectionPriority;
|
|
171
180
|
}): Promise<{
|
|
@@ -52,6 +52,23 @@ function bashMirrorGapNote(handle) {
|
|
|
52
52
|
? " [!] The output file is INCOMPLETE (one or more writes to it failed) — trust the result text / TaskOutput over the file."
|
|
53
53
|
: "";
|
|
54
54
|
}
|
|
55
|
+
function explicitStopBashTerminalNote(handle, alreadyGone) {
|
|
56
|
+
const resultText = handle.spool !== undefined ? bashTerminalResult(handle.spool) : "";
|
|
57
|
+
const detail = alreadyGone
|
|
58
|
+
? "killed before completion (stopped via TaskStop; the process was already gone)"
|
|
59
|
+
: "killed before completion (stopped via TaskStop)";
|
|
60
|
+
handle.onTerminal?.({
|
|
61
|
+
task_id: handle.id,
|
|
62
|
+
task_type: "background_bash",
|
|
63
|
+
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
64
|
+
...(handle.outputFile !== undefined ? { output_file: handle.outputFile } : {}),
|
|
65
|
+
status: "killed",
|
|
66
|
+
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
67
|
+
summary: `${terminalTaskSummary("bash", (handle.description ?? "background command").slice(0, 200), "killed")} — ${detail}${bashMirrorGapNote(handle)}${handle.spool !== undefined ? droppedGapNote(handle.spool) : ""}`,
|
|
68
|
+
...(resultText.length > 0 ? { result: resultText, partial: true } : {}),
|
|
69
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
55
72
|
function monitorInTimeoutWindow(handle) {
|
|
56
73
|
return handle.status === "running" && handle.deadlineAt !== undefined && handle.timers.now() < handle.deadlineAt;
|
|
57
74
|
}
|
|
@@ -145,14 +162,31 @@ export class TaskRegistry {
|
|
|
145
162
|
attachAgentNotify(id, notify, cycle) {
|
|
146
163
|
return attachAgentNotifyLane(this.core, id, notify, cycle);
|
|
147
164
|
}
|
|
165
|
+
attachAgentTerminalNotifier(id, fn) {
|
|
166
|
+
const handle = this.handles.get(id);
|
|
167
|
+
if (!handle || handle.type !== "background_agent")
|
|
168
|
+
return;
|
|
169
|
+
if (handle.status !== "running" && handle.status !== "parked" && handle.status !== "pending")
|
|
170
|
+
return;
|
|
171
|
+
handle.onReapTerminal = fn;
|
|
172
|
+
}
|
|
173
|
+
claimAgentTerminalNotify(id) {
|
|
174
|
+
const handle = this.handles.get(id);
|
|
175
|
+
if (!handle || handle.type !== "background_agent")
|
|
176
|
+
return true;
|
|
177
|
+
if (handle.terminalNotified === true)
|
|
178
|
+
return false;
|
|
179
|
+
handle.terminalNotified = true;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
148
182
|
async deliverToRunningAgent(id, access, notification, opts) {
|
|
149
183
|
return deliverToRunningAgentLane(this.core, id, access, notification, opts);
|
|
150
184
|
}
|
|
151
185
|
runningBackgroundAgentLabels(access) {
|
|
152
186
|
return runningBackgroundAgentLabelsLane(this.core, access);
|
|
153
187
|
}
|
|
154
|
-
async pollBackgroundAgent(handle, deadline, signal, oneShot) {
|
|
155
|
-
return pollBackgroundAgentLane(handle, deadline, signal, oneShot);
|
|
188
|
+
async pollBackgroundAgent(handle, deadline, signal, oneShot, toolResultStore, sessionId) {
|
|
189
|
+
return pollBackgroundAgentLane(handle, deadline, signal, oneShot, toolResultStore, sessionId);
|
|
156
190
|
}
|
|
157
191
|
async stopBackgroundAgent(handle) {
|
|
158
192
|
return stopBackgroundAgentLane(this.core, handle);
|
|
@@ -471,8 +505,17 @@ export class TaskRegistry {
|
|
|
471
505
|
if (!canAccess(handle, access))
|
|
472
506
|
continue;
|
|
473
507
|
this.markStopSource(handle.id, "system");
|
|
508
|
+
const reapTerminalNote = handle.onReapTerminal;
|
|
474
509
|
handle.abort.abort();
|
|
475
510
|
this.settleBackgroundAgent(handle.id, { status: "killed", error: "session released" });
|
|
511
|
+
if (reapTerminalNote !== undefined && handle.terminalNotified !== true) {
|
|
512
|
+
handle.terminalNotified = true;
|
|
513
|
+
try {
|
|
514
|
+
reapTerminalNote();
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
476
519
|
reaped++;
|
|
477
520
|
}
|
|
478
521
|
this.pokeBgQuiescence(sessionId);
|
|
@@ -748,7 +791,7 @@ export class TaskRegistry {
|
|
|
748
791
|
if (handle.type === "background_bash")
|
|
749
792
|
return this.pollBackgroundBash(handle, taskId, access, opts.filter, deadline, opts.signal);
|
|
750
793
|
if (handle.type === "background_agent")
|
|
751
|
-
return this.pollBackgroundAgent(handle, deadline, opts.signal, opts.oneShot);
|
|
794
|
+
return this.pollBackgroundAgent(handle, deadline, opts.signal, opts.oneShot, opts.toolResultStore, access.sessionId);
|
|
752
795
|
if (handle.type === "monitor")
|
|
753
796
|
return this.pollMonitor(handle, opts.filter, deadline, opts.signal);
|
|
754
797
|
return this.pollWorkflow(handle, access, deadline, opts.signal);
|
|
@@ -1122,7 +1165,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1122
1165
|
handle.status = "killed";
|
|
1123
1166
|
handle.updatedAt = Date.now();
|
|
1124
1167
|
mintCompletionId(handle);
|
|
1125
|
-
this.
|
|
1168
|
+
this.notifyTerminalOnce(handle, () => explicitStopBashTerminalNote(handle, true));
|
|
1126
1169
|
return {
|
|
1127
1170
|
content: `Terminated ${handle.id} (the process was already gone).`,
|
|
1128
1171
|
details: {
|
|
@@ -1151,7 +1194,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1151
1194
|
handle.status = "killed";
|
|
1152
1195
|
handle.updatedAt = Date.now();
|
|
1153
1196
|
mintCompletionId(handle);
|
|
1154
|
-
this.
|
|
1197
|
+
this.notifyTerminalOnce(handle, () => explicitStopBashTerminalNote(handle, false));
|
|
1155
1198
|
return {
|
|
1156
1199
|
content: `Terminated ${handle.id}.`,
|
|
1157
1200
|
details: {
|
|
@@ -1193,9 +1236,10 @@ export function createTaskOutputTool(opts) {
|
|
|
1193
1236
|
let effectiveTimeoutMs = requestedMs;
|
|
1194
1237
|
let waitClampNote = "";
|
|
1195
1238
|
const wall = opts.deadlineMs?.();
|
|
1239
|
+
const requestedWaitMs = Math.max(1, Math.floor(requestedMs ?? BLOCK_DEFAULT_TIMEOUT_MS));
|
|
1196
1240
|
if (args.block !== false && wall !== undefined) {
|
|
1197
1241
|
const remainMs = wall - Date.now() - BLOCK_WAIT_WRITEOUT_RESERVE_MS;
|
|
1198
|
-
const wantMs = Math.min(BLOCK_MAX_TIMEOUT_MS,
|
|
1242
|
+
const wantMs = Math.min(BLOCK_MAX_TIMEOUT_MS, requestedWaitMs);
|
|
1199
1243
|
if (remainMs <= 0) {
|
|
1200
1244
|
effectiveTimeoutMs = 1;
|
|
1201
1245
|
waitClampNote =
|
|
@@ -1203,7 +1247,7 @@ export function createTaskOutputTool(opts) {
|
|
|
1203
1247
|
}
|
|
1204
1248
|
else if (remainMs < wantMs) {
|
|
1205
1249
|
effectiveTimeoutMs = remainMs;
|
|
1206
|
-
waitClampNote = `\n\nNOTE: wait clamped to ${Math.round(remainMs / 1000)}s (requested ${Math.round(
|
|
1250
|
+
waitClampNote = `\n\nNOTE: wait clamped to ${Math.round(remainMs / 1000)}s (requested ${Math.round(requestedWaitMs / 1000)}s) — the task's wall-clock deadline is near. If the task is still running after this wait, do NOT re-wait: proceed with other work or write out your results now.`;
|
|
1207
1251
|
}
|
|
1208
1252
|
}
|
|
1209
1253
|
const r = await opts.registry.pollTask(id, { owner: ctx.taskId ?? opts.owner, scope: ctx.principal ?? opts.scope, ...((ctx.sessionId ?? opts.sessionId) !== undefined ? { sessionId: ctx.sessionId ?? opts.sessionId } : {}) }, {
|
|
@@ -1214,6 +1258,7 @@ export function createTaskOutputTool(opts) {
|
|
|
1214
1258
|
timeoutMs: effectiveTimeoutMs,
|
|
1215
1259
|
signal: ctx.signal,
|
|
1216
1260
|
oneShot: opts.oneShot,
|
|
1261
|
+
toolResultStore: opts.toolResultStore,
|
|
1217
1262
|
});
|
|
1218
1263
|
const { type: taskType, ...rest } = r.details;
|
|
1219
1264
|
return {
|
|
@@ -45,6 +45,7 @@ export declare class ScopedToolResultStore implements ToolResultStore {
|
|
|
45
45
|
}
|
|
46
46
|
export declare function isVolatileOffloadStore(store: ToolResultStore): boolean;
|
|
47
47
|
export declare const OFFLOAD_TOOL_NAME = "ReadToolResult";
|
|
48
|
+
export declare function offloadPagebackHint(ref: string, form: "preview" | "cleared", reachableTools?: ReadonlySet<string>): string;
|
|
48
49
|
export declare const PERSISTED_OUTPUT_PREFIX = "<persisted-output ref=";
|
|
49
50
|
export declare const DEFAULT_TOOL_RESULT_THRESHOLD_CHARS = 20000;
|
|
50
51
|
export declare function firstPartyOffloadPolicy(toolName: string): {
|
|
@@ -54,6 +55,6 @@ export declare function firstPartyOffloadPolicy(toolName: string): {
|
|
|
54
55
|
export declare function buildPreview(full: string, ref: string, sizes?: {
|
|
55
56
|
head: number;
|
|
56
57
|
tail: number;
|
|
57
|
-
}): string;
|
|
58
|
-
export declare function withToolResultOffload(tool: AgentTool, store: ToolResultStore, thresholdChars: number, sessionId: string): AgentTool;
|
|
58
|
+
}, reachableTools?: ReadonlySet<string>): string;
|
|
59
|
+
export declare function withToolResultOffload(tool: AgentTool, store: ToolResultStore, thresholdChars: number, sessionId: string, reachableTools?: () => ReadonlySet<string> | undefined): AgentTool;
|
|
59
60
|
export declare function createReadToolResultTool(store: ToolResultStore): AgentTool;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import { defineTool, errorResult } from "./tools.js";
|
|
4
|
+
import { TOOL_SEARCH_NAME } from "./runner/tool-disclosure.js";
|
|
4
5
|
export function assertSafeToolResultRef(ref) {
|
|
5
6
|
const bad = ref === "" ||
|
|
6
7
|
ref === "." ||
|
|
@@ -92,6 +93,13 @@ export function isVolatileOffloadStore(store) {
|
|
|
92
93
|
return store instanceof InMemoryToolResultStore;
|
|
93
94
|
}
|
|
94
95
|
export const OFFLOAD_TOOL_NAME = "ReadToolResult";
|
|
96
|
+
export function offloadPagebackHint(ref, form, reachableTools) {
|
|
97
|
+
const activate = reachableTools !== undefined && !reachableTools.has(OFFLOAD_TOOL_NAME)
|
|
98
|
+
? `${OFFLOAD_TOOL_NAME} is not active yet — call ${TOOL_SEARCH_NAME} with {"query":"select:${OFFLOAD_TOOL_NAME}"} to activate it, then `
|
|
99
|
+
: "";
|
|
100
|
+
const core = `${activate === "" && form === "preview" ? "Call" : `${activate}call`} ${OFFLOAD_TOOL_NAME} with ref`;
|
|
101
|
+
return form === "preview" ? `${core}="${ref}" (offset, limit) to read more.` : `full text persisted; ${core} "${ref}" to read it back`;
|
|
102
|
+
}
|
|
95
103
|
export const PERSISTED_OUTPUT_PREFIX = "<persisted-output ref=";
|
|
96
104
|
export const DEFAULT_TOOL_RESULT_THRESHOLD_CHARS = 20_000;
|
|
97
105
|
export function firstPartyOffloadPolicy(toolName) {
|
|
@@ -122,7 +130,7 @@ function totalTextChars(content) {
|
|
|
122
130
|
n += b.text.length;
|
|
123
131
|
return n;
|
|
124
132
|
}
|
|
125
|
-
export function buildPreview(full, ref, sizes) {
|
|
133
|
+
export function buildPreview(full, ref, sizes, reachableTools) {
|
|
126
134
|
const total = full.length;
|
|
127
135
|
const headChars = sizes?.head ?? PREVIEW_HEAD_CHARS;
|
|
128
136
|
const tailChars = sizes?.tail ?? PREVIEW_TAIL_CHARS;
|
|
@@ -131,11 +139,11 @@ export function buildPreview(full, ref, sizes) {
|
|
|
131
139
|
const tail = full.slice(tailStart);
|
|
132
140
|
return (`${PERSISTED_OUTPUT_PREFIX}"${ref}" chars="${total}">\n` +
|
|
133
141
|
`${head}\n` +
|
|
134
|
-
`…[truncated — ${total} chars total.
|
|
142
|
+
`…[truncated — ${total} chars total. ${offloadPagebackHint(ref, "preview", reachableTools)}]\n` +
|
|
135
143
|
`${tail}\n` +
|
|
136
144
|
`</persisted-output>`);
|
|
137
145
|
}
|
|
138
|
-
export function withToolResultOffload(tool, store, thresholdChars, sessionId) {
|
|
146
|
+
export function withToolResultOffload(tool, store, thresholdChars, sessionId, reachableTools) {
|
|
139
147
|
const wrappedExecute = async (toolCallId, params, signal, onUpdate) => {
|
|
140
148
|
const res = await tool.execute(toolCallId, params, signal, onUpdate);
|
|
141
149
|
if (totalTextChars(res.content) <= thresholdChars)
|
|
@@ -149,7 +157,7 @@ export function withToolResultOffload(tool, store, thresholdChars, sessionId) {
|
|
|
149
157
|
const ref = buildToolResultRef(sessionId, toolCallId);
|
|
150
158
|
await store.put(ref, full);
|
|
151
159
|
const images = res.content.filter((b) => b.type !== "text");
|
|
152
|
-
return { ...res, content: [{ type: "text", text: buildPreview(full, ref) }, ...images] };
|
|
160
|
+
return { ...res, content: [{ type: "text", text: buildPreview(full, ref, undefined, reachableTools?.()) }, ...images] };
|
|
153
161
|
};
|
|
154
162
|
return { ...tool, execute: wrappedExecute };
|
|
155
163
|
}
|
package/dist/core/tools.d.ts
CHANGED
|
@@ -6,4 +6,6 @@ export declare function errorResult(text: string, details?: unknown): {
|
|
|
6
6
|
isError: true;
|
|
7
7
|
details?: unknown;
|
|
8
8
|
};
|
|
9
|
+
export declare function isDefineToolProduct(x: unknown): x is AgentTool;
|
|
10
|
+
export declare function stampDefineToolBrand<T extends object>(tool: T): T;
|
|
9
11
|
export declare function defineTool<TParams extends TSchema = TSchema>(spec: ToolSpec<TParams>): AgentTool<TParams>;
|
package/dist/core/tools.js
CHANGED
|
@@ -25,6 +25,14 @@ function isEmptyToolContent(content) {
|
|
|
25
25
|
export function errorResult(text, details) {
|
|
26
26
|
return details === undefined ? { content: text, isError: true } : { content: text, isError: true, details };
|
|
27
27
|
}
|
|
28
|
+
const DEFINE_TOOL_BRAND = Symbol("sema.core.defineTool.product");
|
|
29
|
+
export function isDefineToolProduct(x) {
|
|
30
|
+
return typeof x === "object" && x !== null && x[DEFINE_TOOL_BRAND] === true;
|
|
31
|
+
}
|
|
32
|
+
export function stampDefineToolBrand(tool) {
|
|
33
|
+
Object.defineProperty(tool, DEFINE_TOOL_BRAND, { value: true, enumerable: false });
|
|
34
|
+
return tool;
|
|
35
|
+
}
|
|
28
36
|
export function defineTool(spec) {
|
|
29
37
|
const executionMode = spec.executionMode ?? (spec.effect === "read" ? "parallel" : "sequential");
|
|
30
38
|
const tool = {
|
|
@@ -72,5 +80,6 @@ export function defineTool(spec) {
|
|
|
72
80
|
...(spec.aliases && spec.aliases.length > 0 ? { aliases: spec.aliases } : {}),
|
|
73
81
|
});
|
|
74
82
|
}
|
|
83
|
+
stampDefineToolBrand(tool);
|
|
75
84
|
return tool;
|
|
76
85
|
}
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -104,6 +104,13 @@ export type TraceEvent = {
|
|
|
104
104
|
}>;
|
|
105
105
|
overrideReasons?: Record<string, string>;
|
|
106
106
|
ts: number;
|
|
107
|
+
} | {
|
|
108
|
+
kind: "config.additional_directory_skipped";
|
|
109
|
+
version: 1;
|
|
110
|
+
taskId: string;
|
|
111
|
+
entry: string;
|
|
112
|
+
reason: string;
|
|
113
|
+
ts: number;
|
|
107
114
|
} | {
|
|
108
115
|
kind: "task.end";
|
|
109
116
|
version: 1;
|
|
@@ -26,8 +26,24 @@ export declare function oversizeJournalResult(serialized: string): boolean;
|
|
|
26
26
|
export declare function callKeyOrdinal(callKey: string): number;
|
|
27
27
|
export declare const JOURNAL_OVERSIZE_ERROR_CODE = "workflow.journal_oversize";
|
|
28
28
|
export declare function journalOversizeTombstone(result: TaskResult, bytes: number): TaskResult;
|
|
29
|
+
export declare const IN_MEMORY_RESUME_CLAIM_TTL_MS: number;
|
|
29
30
|
export declare class InMemoryWorkflowJournalStore implements WorkflowJournalStore {
|
|
30
31
|
private readonly runs;
|
|
32
|
+
private readonly resumeClaims;
|
|
33
|
+
private claimsForScope;
|
|
34
|
+
resumeClaim(input: {
|
|
35
|
+
sourceRunId: string;
|
|
36
|
+
newRunId: string;
|
|
37
|
+
scope: string;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
granted: boolean;
|
|
40
|
+
holder?: string;
|
|
41
|
+
}>;
|
|
42
|
+
releaseResumeClaim(input: {
|
|
43
|
+
sourceRunId: string;
|
|
44
|
+
newRunId: string;
|
|
45
|
+
scope: string;
|
|
46
|
+
}): Promise<void>;
|
|
31
47
|
load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
|
|
32
48
|
append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
|
|
33
49
|
}
|
|
@@ -29,8 +29,36 @@ function snapshot(entry) {
|
|
|
29
29
|
return JSON.parse(JSON.stringify(entry));
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
export const IN_MEMORY_RESUME_CLAIM_TTL_MS = 60 * 60 * 1000;
|
|
32
33
|
export class InMemoryWorkflowJournalStore {
|
|
33
34
|
runs = new Map();
|
|
35
|
+
resumeClaims = new Map();
|
|
36
|
+
claimsForScope(scope) {
|
|
37
|
+
let m = this.resumeClaims.get(scope);
|
|
38
|
+
if (m === undefined) {
|
|
39
|
+
m = new Map();
|
|
40
|
+
this.resumeClaims.set(scope, m);
|
|
41
|
+
}
|
|
42
|
+
return m;
|
|
43
|
+
}
|
|
44
|
+
async resumeClaim(input) {
|
|
45
|
+
const table = this.claimsForScope(input.scope);
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const existing = table.get(input.sourceRunId);
|
|
48
|
+
if (existing !== undefined && existing.expiresAt > now && existing.holder !== input.newRunId) {
|
|
49
|
+
return { granted: false, holder: existing.holder };
|
|
50
|
+
}
|
|
51
|
+
table.set(input.sourceRunId, { holder: input.newRunId, expiresAt: now + IN_MEMORY_RESUME_CLAIM_TTL_MS });
|
|
52
|
+
return { granted: true };
|
|
53
|
+
}
|
|
54
|
+
async releaseResumeClaim(input) {
|
|
55
|
+
const table = this.resumeClaims.get(input.scope);
|
|
56
|
+
if (table === undefined)
|
|
57
|
+
return;
|
|
58
|
+
const existing = table.get(input.sourceRunId);
|
|
59
|
+
if (existing !== undefined && existing.holder === input.newRunId)
|
|
60
|
+
table.delete(input.sourceRunId);
|
|
61
|
+
}
|
|
34
62
|
async load(runId, scope) {
|
|
35
63
|
const rec = this.runs.get(runId);
|
|
36
64
|
if (!rec || rec.scope !== scope)
|
|
@@ -28,6 +28,7 @@ export declare class NodeLspManager implements LspServerManager {
|
|
|
28
28
|
private sweepTimer?;
|
|
29
29
|
private readonly cache;
|
|
30
30
|
private readonly opening;
|
|
31
|
+
private readonly failed;
|
|
31
32
|
private disposed;
|
|
32
33
|
constructor(opts?: NodeLspManagerOptions);
|
|
33
34
|
private sweepIdle;
|
|
@@ -35,6 +36,7 @@ export declare class NodeLspManager implements LspServerManager {
|
|
|
35
36
|
private open;
|
|
36
37
|
private evictOverCap;
|
|
37
38
|
evict(root: string): Promise<void>;
|
|
39
|
+
clearFailed(root?: string): void;
|
|
38
40
|
private openSession;
|
|
39
41
|
private serverCmd;
|
|
40
42
|
dispose(): Promise<void>;
|
|
@@ -59,6 +59,7 @@ export class NodeLspManager {
|
|
|
59
59
|
sweepTimer;
|
|
60
60
|
cache = new Map();
|
|
61
61
|
opening = new Map();
|
|
62
|
+
failed = new Map();
|
|
62
63
|
disposed = false;
|
|
63
64
|
constructor(opts = {}) {
|
|
64
65
|
this.servers = { ...DEFAULT_LSP_SERVERS, ...opts.servers };
|
|
@@ -96,6 +97,11 @@ export class NodeLspManager {
|
|
|
96
97
|
cached.lastUsed = Date.now();
|
|
97
98
|
return cached.session;
|
|
98
99
|
}
|
|
100
|
+
const neg = this.failed.get(key);
|
|
101
|
+
if (neg !== undefined) {
|
|
102
|
+
neg.attempts += 1;
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
99
105
|
if (cached) {
|
|
100
106
|
this.cache.delete(key);
|
|
101
107
|
const carry = cached.session.openedFiles();
|
|
@@ -163,6 +169,14 @@ export class NodeLspManager {
|
|
|
163
169
|
}
|
|
164
170
|
await Promise.all(closing);
|
|
165
171
|
}
|
|
172
|
+
clearFailed(root) {
|
|
173
|
+
for (const [key, e] of [...this.failed]) {
|
|
174
|
+
if (root !== undefined && e.root !== root)
|
|
175
|
+
continue;
|
|
176
|
+
this.failed.delete(key);
|
|
177
|
+
this.log("lsp_negative_cache_cleared", { key, attempts: e.attempts });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
166
180
|
async openSession(language, root, signal, _env) {
|
|
167
181
|
const cmd = this.serverCmd(language);
|
|
168
182
|
if (!cmd)
|
|
@@ -171,6 +185,8 @@ export class NodeLspManager {
|
|
|
171
185
|
const [command, ...args] = tokens.length > 0 ? tokens : [""];
|
|
172
186
|
const child = await this.spawnFn(command, args, root, signal).catch(() => undefined);
|
|
173
187
|
if (!child) {
|
|
188
|
+
if (signal?.aborted !== true)
|
|
189
|
+
this.failed.set(`${language} ${root}`, { root, attempts: 1 });
|
|
174
190
|
this.log("lsp_spawn_degraded", { language, root, command });
|
|
175
191
|
return undefined;
|
|
176
192
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core
|
|
|
66
66
|
export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
|
|
67
67
|
export type { InvariantKind, FunctionContract, Invariant, InvariantViolation, CheckResult, } from "./core/property-harness.js";
|
|
68
68
|
export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
|
|
69
|
+
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
69
70
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, type ToolResultStore, type ToolResultSlice, } from "./core/tool-result-store.js";
|
|
70
71
|
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, WALLTIME_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingWalltimeMs, winnerFromOutcome, validatePendingSteer, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type SafetyAxis, type ResourceLedger, type ResourceLimitReason, } from "./core/checkpoint-store.js";
|
|
71
72
|
export { InMemoryFileSnapshotStore, DEFAULT_SNAPSHOT_BOUNDS } from "./core/file-snapshot-store.js";
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,7 @@ export { runExecGate } from "./core/exec-gate.js";
|
|
|
57
57
|
export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core/untrusted-text.js";
|
|
58
58
|
export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
|
|
59
59
|
export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
|
|
60
|
+
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
60
61
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, } from "./core/tool-result-store.js";
|
|
61
62
|
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, WALLTIME_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingWalltimeMs, winnerFromOutcome, validatePendingSteer, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, } from "./core/checkpoint-store.js";
|
|
62
63
|
export { InMemoryFileSnapshotStore, DEFAULT_SNAPSHOT_BOUNDS } from "./core/file-snapshot-store.js";
|