@sema-agent/core 1.451.0 → 2.0.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/agent-transcript-tool.d.ts +15 -0
- package/dist/agents/agent-transcript-tool.js +122 -0
- package/dist/agents/cascade.js +5 -8
- package/dist/agents/observer.js +3 -0
- package/dist/agents/repair-loop.d.ts +1 -1
- package/dist/agents/repair-loop.js +1 -1
- package/dist/agents/retain-ledger.d.ts +60 -0
- package/dist/agents/retain-ledger.js +225 -0
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/roster-store.js +9 -0
- package/dist/agents/send-message-tool.d.ts +41 -0
- package/dist/agents/send-message-tool.js +469 -0
- package/dist/agents/subagent.d.ts +33 -108
- package/dist/agents/subagent.js +40 -813
- package/dist/agents/teacher.d.ts +0 -1
- package/dist/agents/teacher.js +0 -9
- package/dist/bin/sema-tb.js +3 -3
- package/dist/brain/anthropic.js +15 -40
- package/dist/brain/openai.js +15 -40
- package/dist/brain/reasoning.d.ts +1 -1
- package/dist/brain/stream-engine.js +2 -2
- package/dist/brain/stream-shared.d.ts +17 -0
- package/dist/brain/stream-shared.js +50 -0
- package/dist/brain/timeout.d.ts +1 -1
- package/dist/brain/timeout.js +1 -1
- package/dist/config/catalog.js +1 -3
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.js +6 -0
- package/dist/core/ask-question.d.ts +1 -1
- 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/background-shell.d.ts +1 -1
- package/dist/core/checkpoint-store.d.ts +0 -1
- package/dist/core/exec-gate.d.ts +1 -1
- package/dist/core/exec-output-tail.d.ts +19 -1
- package/dist/core/exec-output-tail.js +44 -5
- package/dist/core/file-snapshot-store.d.ts +1 -1
- package/dist/core/fs-write-gate-policy.d.ts +1 -1
- package/dist/core/git-worktree-env.d.ts +1 -1
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/image-downsample.d.ts +44 -0
- package/dist/core/image-downsample.js +97 -0
- package/dist/core/lsp-session.d.ts +1 -0
- package/dist/core/lsp-session.js +24 -6
- package/dist/core/lsp.d.ts +11 -1
- 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/mcp.d.ts +3 -45
- package/dist/core/mcp.js +2 -97
- package/dist/core/media-byte-cap.d.ts +1 -1
- package/dist/core/memory-recall.d.ts +0 -12
- package/dist/core/memory-recall.js +0 -222
- package/dist/core/memory.d.ts +0 -6
- package/dist/core/memory.js +0 -88
- package/dist/core/message-utils.d.ts +1 -1
- package/dist/core/oracle-isolation.d.ts +1 -1
- package/dist/core/remote-env.d.ts +1 -1
- package/dist/core/roles.d.ts +1 -1
- package/dist/core/runner/active-skill-scope.d.ts +1 -1
- package/dist/core/runner/assemble-result.d.ts +0 -2
- package/dist/core/runner/memory-consolidation.d.ts +1 -1
- package/dist/core/runner/prepare-memory.d.ts +16 -0
- package/dist/core/runner/prepare-memory.js +191 -0
- package/dist/core/runner/prepare-task.d.ts +4 -28
- package/dist/core/runner/prepare-task.js +29 -208
- package/dist/core/runner/prompt-suggestions.d.ts +1 -1
- package/dist/core/runner/runtask.d.ts +2 -3
- package/dist/core/runner/runtask.js +337 -529
- package/dist/core/runner/session-rule-policy.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.d.ts +5 -5
- package/dist/core/runner/synthetic-tools.js +4 -4
- package/dist/core/runner/tool-disclosure.d.ts +3 -3
- package/dist/core/runner/tool-disclosure.js +2 -2
- package/dist/core/runner/tool-output-projection.d.ts +6 -0
- package/dist/core/runner/tool-output-projection.js +81 -0
- package/dist/core/runtime.d.ts +1 -1
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/session-reconcile.d.ts +1 -1
- package/dist/core/session-reconcile.js +5 -2
- package/dist/core/session-store.d.ts +2 -1
- package/dist/core/session-store.js +2 -1
- package/dist/core/session.d.ts +0 -1
- package/dist/core/task-notification.d.ts +1 -0
- package/dist/core/task-registry-agent.d.ts +115 -0
- package/dist/core/task-registry-agent.js +1163 -0
- package/dist/core/task-registry-monitor.d.ts +13 -0
- package/dist/core/task-registry-monitor.js +347 -0
- package/dist/core/task-registry-shared.d.ts +294 -0
- package/dist/core/task-registry-shared.js +271 -0
- package/dist/core/task-registry-workflow.d.ts +5 -0
- package/dist/core/task-registry-workflow.js +143 -0
- package/dist/core/task-registry.d.ts +54 -213
- package/dist/core/task-registry.js +190 -1766
- package/dist/core/task-tool-shape.d.ts +32 -0
- package/dist/core/task-tool-shape.js +83 -0
- package/dist/core/tool-policy.d.ts +1 -2
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +2 -2
- package/dist/core/tool-result-store.js +3 -3
- package/dist/core/tools.d.ts +7 -3
- package/dist/core/tools.js +3 -8
- package/dist/core/trace.d.ts +2 -2
- package/dist/core/types.d.ts +1 -29
- package/dist/core/workflow-journal-store.d.ts +2 -0
- package/dist/core/workflow-journal-store.js +14 -0
- package/dist/core/workflow-run-store-contract.d.ts +1 -1
- package/dist/core/workflow-run-store-contract.js +19 -19
- package/dist/engine/execution-env/node-execution-env.d.ts +14 -0
- package/dist/engine/execution-env/node-execution-env.js +184 -44
- package/dist/engine/lsp/frame-decoder.d.ts +1 -1
- package/dist/engine/lsp/frame-decoder.js +1 -1
- package/dist/engine/lsp/node-lsp-manager.d.ts +4 -2
- 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 +19 -8
- package/dist/index.d.ts +18 -16
- package/dist/index.js +15 -13
- package/dist/internal/harness-types.d.ts +7 -0
- package/dist/internal/harness-types.js +1 -0
- package/dist/internal/harness.d.ts +1 -7
- package/dist/internal/harness.js +1 -0
- package/dist/orchestration/goal.js +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +2 -1
- package/dist/orchestration/run-workflow-tool.js +29 -6
- package/dist/orchestration/workflow-types.d.ts +193 -0
- package/dist/orchestration/workflow-types.js +54 -0
- package/dist/orchestration/workflow.d.ts +3 -184
- package/dist/orchestration/workflow.js +87 -64
- package/dist/prompt-assembly/assemble.d.ts +3 -3
- package/dist/prompt-assembly/assemble.js +0 -16
- package/dist/prompt-assembly/explain.d.ts +1 -1
- package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
- package/dist/prompts/default.d.ts +1 -6
- package/dist/prompts/default.js +3 -11
- package/dist/scenarios/env.d.ts +1 -1
- package/dist/scenarios/full-body.d.ts +0 -3
- package/dist/scenarios/full-body.js +0 -2
- package/dist/stores/cc/mailbox-store.js +6 -1
- package/dist/stores/file/background-agent-store.d.ts +2 -15
- package/dist/stores/file/background-agent-store.js +33 -102
- package/dist/stores/file/checkpoint-store.d.ts +1 -14
- package/dist/stores/file/checkpoint-store.js +47 -119
- package/dist/stores/file/file-snapshot-store.d.ts +1 -1
- package/dist/stores/file/mailbox-store.d.ts +1 -1
- package/dist/stores/file/mailbox-store.js +9 -7
- package/dist/stores/file/shared-ledger.d.ts +35 -0
- package/dist/stores/file/shared-ledger.js +94 -0
- package/dist/stores/file/workflow-run-store.d.ts +1 -14
- package/dist/stores/file/workflow-run-store.js +24 -93
- package/dist/tools/fs/bash-readonly-classifier.d.ts +8 -0
- package/dist/tools/fs/bash-readonly-classifier.js +166 -0
- package/dist/tools/fs/encoding.d.ts +5 -0
- package/dist/tools/fs/encoding.js +6 -0
- package/dist/tools/fs/fs-bash.d.ts +31 -0
- package/dist/tools/fs/fs-bash.js +672 -0
- package/dist/tools/fs/fs-pdf.d.ts +22 -0
- package/dist/tools/fs/fs-pdf.js +236 -0
- package/dist/tools/fs/fs-read.d.ts +8 -0
- package/dist/tools/fs/fs-read.js +286 -0
- package/dist/tools/fs/fs-search-tools.d.ts +5 -0
- package/dist/tools/fs/fs-search-tools.js +173 -0
- package/dist/tools/fs/fs-shared.d.ts +55 -0
- package/dist/tools/fs/fs-shared.js +144 -0
- package/dist/tools/fs/fs-write.d.ts +8 -0
- package/dist/tools/fs/fs-write.js +423 -0
- package/dist/tools/fs/index.d.ts +12 -65
- package/dist/tools/fs/index.js +26 -2026
- package/dist/tools/fs/notebook.d.ts +43 -0
- package/dist/tools/fs/notebook.js +141 -0
- package/dist/tools/fs/pdf.d.ts +1 -1
- package/dist/tools/fs/repo-map.d.ts +3 -3
- package/dist/tools/fs/repo-map.js +3 -3
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/fs/search.d.ts +1 -1
- package/dist/tools/fs/search.js +141 -12
- package/dist/tools/gitea-issue.js +4 -2
- package/dist/tools/monitor.d.ts +1 -1
- package/dist/tools/monitor.js +12 -8
- package/dist/tools/scheduler-tools.d.ts +1 -1
- package/dist/tools/scheduler-tools.js +16 -16
- package/dist/tools/task-list.js +34 -12
- package/dist/tools/web.d.ts +4 -2
- package/dist/tools/web.js +106 -20
- package/dist/tools/worktree.d.ts +1 -1
- package/dist/tools/worktree.js +14 -14
- package/package.json +1 -1
- package/dist/core/dynamic-recall.d.ts +0 -32
- package/dist/core/dynamic-recall.js +0 -76
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { closeSync, constants, createReadStream, mkdtempSync, openSync, readSync, statSync, truncateSync, unlinkSync } from "node:fs";
|
|
4
|
-
import { access, appendFile, lstat, mkdir, mkdtemp, readdir, readFile, readlink, realpath, rm, writeFile, } from "node:fs/promises";
|
|
4
|
+
import { access, appendFile, lstat, mkdir, mkdtemp, open, readdir, readFile, readlink, realpath, rename, rm, unlink, writeFile, } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { isAbsolute, join, resolve } from "node:path";
|
|
7
7
|
import { createInterface } from "node:readline";
|
|
8
8
|
import { ExecutionError, err, FileError, ok, toError, } from "../harness/types.js";
|
|
9
9
|
import { killProcessTree, shutdownDebug } from "./kill-tree.js";
|
|
10
10
|
import { scrubSecretEnv } from "../../core/secret-env.js";
|
|
11
|
-
import { RollingTailBuffer, markTruncated } from "../../core/exec-output-tail.js";
|
|
11
|
+
import { RollingTailBuffer, markLiveSpoolRotations, markTruncated, newStreamCursorState, sliceStreamIncrement, } from "../../core/exec-output-tail.js";
|
|
12
12
|
import { BackgroundShellError } from "../../core/background-shell.js";
|
|
13
13
|
import { SchedulerError } from "../../core/scheduler.js";
|
|
14
14
|
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
|
|
@@ -88,6 +88,11 @@ function toFileError(error, path) {
|
|
|
88
88
|
function abortResult(signal, path) {
|
|
89
89
|
return signal?.aborted ? err(new FileError("aborted", "aborted", path)) : undefined;
|
|
90
90
|
}
|
|
91
|
+
function isFsSupportError(error) {
|
|
92
|
+
const code = isNodeError(error) ? error.code : undefined;
|
|
93
|
+
return code === "EINVAL" || code === "ENOTSUP" || code === "EPERM" || code === "ENOSYS";
|
|
94
|
+
}
|
|
95
|
+
const RENAME_FALLBACK_CODES = new Set(["EXDEV", "EPERM", "EEXIST", "EBUSY"]);
|
|
91
96
|
async function pathExists(path) {
|
|
92
97
|
try {
|
|
93
98
|
await access(path, constants.F_OK);
|
|
@@ -208,6 +213,23 @@ const EXEC_SPOOL_PUMP_MS = 200;
|
|
|
208
213
|
const EXEC_SPOOL_ROTATE_BYTES = 8 * 1024 * 1024;
|
|
209
214
|
const EXEC_SPOOL_FINAL_TAIL_BYTES = 8 * 1024 * 1024;
|
|
210
215
|
const EXEC_SPOOL_HARD_CAP_BYTES = 16 * 1024 * 1024;
|
|
216
|
+
export function decideSpoolReclaim(args) {
|
|
217
|
+
const rotate = args.rotateBytes ?? EXEC_SPOOL_ROTATE_BYTES;
|
|
218
|
+
const hardCap = args.hardCapBytes ?? EXEC_SPOOL_HARD_CAP_BYTES;
|
|
219
|
+
if (args.cursor < rotate)
|
|
220
|
+
return { reclaim: false };
|
|
221
|
+
if (args.size === args.cursor) {
|
|
222
|
+
if (!args.writerLive)
|
|
223
|
+
return { reclaim: true, live: false, skippedBytes: 0 };
|
|
224
|
+
if (args.size >= hardCap)
|
|
225
|
+
return { reclaim: true, live: true, skippedBytes: 0 };
|
|
226
|
+
return { reclaim: false };
|
|
227
|
+
}
|
|
228
|
+
if (args.size > args.cursor && args.size >= hardCap) {
|
|
229
|
+
return { reclaim: true, live: args.writerLive, skippedBytes: args.size - args.cursor };
|
|
230
|
+
}
|
|
231
|
+
return { reclaim: false };
|
|
232
|
+
}
|
|
211
233
|
const SIGNUM = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGABRT: 6, SIGKILL: 9, SIGSEGV: 11, SIGPIPE: 13, SIGTERM: 15 };
|
|
212
234
|
export function openSpoolPair(base) {
|
|
213
235
|
const outPath = `${base}.out.log`;
|
|
@@ -308,6 +330,9 @@ export class NodeExecutionEnv {
|
|
|
308
330
|
const adoptOnTimeoutRef = {};
|
|
309
331
|
const pumpSpoolRef = {};
|
|
310
332
|
const drainSpoolRef = {};
|
|
333
|
+
const liveRotations = { out: 0, err: 0 };
|
|
334
|
+
const liveRotationsTotal = () => liveRotations.out + liveRotations.err;
|
|
335
|
+
const writerLive = () => !settled && child !== undefined && child.exitCode === null && child.signalCode === null;
|
|
311
336
|
const armForceSettle = (result) => {
|
|
312
337
|
if (forceSettleRef.current || settled)
|
|
313
338
|
return;
|
|
@@ -326,7 +351,7 @@ export class NodeExecutionEnv {
|
|
|
326
351
|
drainSpoolRef.current?.();
|
|
327
352
|
const so = stdoutTail.result();
|
|
328
353
|
const se = stderrTail.result();
|
|
329
|
-
return { stdout: markTruncated(so.text, so.droppedBytes), stderr: markTruncated(se.text, se.droppedBytes) };
|
|
354
|
+
return { stdout: markTruncated(so.text, so.droppedBytes), stderr: markLiveSpoolRotations(markTruncated(se.text, se.droppedBytes), liveRotationsTotal()) };
|
|
330
355
|
};
|
|
331
356
|
const onAbort = () => {
|
|
332
357
|
abortKillInFlight = true;
|
|
@@ -515,16 +540,15 @@ export class NodeExecutionEnv {
|
|
|
515
540
|
if (spoolCursor[lane.cursor] < EXEC_SPOOL_ROTATE_BYTES)
|
|
516
541
|
return;
|
|
517
542
|
try {
|
|
518
|
-
const
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
543
|
+
const d = decideSpoolReclaim({ size: statSync(lane.path).size, cursor: spoolCursor[lane.cursor], writerLive: writerLive() });
|
|
544
|
+
if (!d.reclaim)
|
|
545
|
+
return;
|
|
546
|
+
if (d.skippedBytes > 0)
|
|
547
|
+
lane.tail.recordSkippedBytes(d.skippedBytes);
|
|
548
|
+
if (d.live)
|
|
549
|
+
liveRotations[lane.cursor]++;
|
|
550
|
+
truncateSync(lane.path, 0);
|
|
551
|
+
spoolCursor[lane.cursor] = 0;
|
|
528
552
|
}
|
|
529
553
|
catch {
|
|
530
554
|
}
|
|
@@ -559,7 +583,9 @@ export class NodeExecutionEnv {
|
|
|
559
583
|
if (settled || !child)
|
|
560
584
|
return false;
|
|
561
585
|
pumpSpoolRef.current?.();
|
|
562
|
-
const shellId = this.adoptRunningChild(child, stdoutTail.result(), stderrTail.result(), execSpoolRef.current !== undefined
|
|
586
|
+
const shellId = this.adoptRunningChild(child, stdoutTail.result(), stderrTail.result(), execSpoolRef.current !== undefined
|
|
587
|
+
? { spool: execSpoolRef.current, cursors: { out: spoolCursor.out, err: spoolCursor.err }, liveRotations: liveRotationsTotal() }
|
|
588
|
+
: undefined);
|
|
563
589
|
if (shellId === undefined)
|
|
564
590
|
return false;
|
|
565
591
|
if (spoolPumpRef.current)
|
|
@@ -652,12 +678,12 @@ export class NodeExecutionEnv {
|
|
|
652
678
|
const signum = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGABRT: 6, SIGKILL: 9, SIGSEGV: 11, SIGPIPE: 13, SIGTERM: 15 };
|
|
653
679
|
settle(ok({
|
|
654
680
|
stdout: markTruncated(so.text, so.droppedBytes),
|
|
655
|
-
stderr: `${markTruncated(se.text, se.droppedBytes)}${se.text.endsWith("\n") || se.text === "" ? "" : "\n"}[process terminated by ${signal}]`,
|
|
681
|
+
stderr: markLiveSpoolRotations(`${markTruncated(se.text, se.droppedBytes)}${se.text.endsWith("\n") || se.text === "" ? "" : "\n"}[process terminated by ${signal}]`, liveRotationsTotal()),
|
|
656
682
|
exitCode: 128 + (signum[signal] ?? 9),
|
|
657
683
|
}));
|
|
658
684
|
return;
|
|
659
685
|
}
|
|
660
|
-
settle(ok({ stdout: markTruncated(so.text, so.droppedBytes), stderr: markTruncated(se.text, se.droppedBytes), exitCode: code ?? 0 }));
|
|
686
|
+
settle(ok({ stdout: markTruncated(so.text, so.droppedBytes), stderr: markLiveSpoolRotations(markTruncated(se.text, se.droppedBytes), liveRotationsTotal()), exitCode: code ?? 0 }));
|
|
661
687
|
};
|
|
662
688
|
child.on("close", (code, signal) => settleFromTermination(code, signal));
|
|
663
689
|
child.on("exit", (code, signal) => {
|
|
@@ -744,10 +770,116 @@ export class NodeExecutionEnv {
|
|
|
744
770
|
}
|
|
745
771
|
try {
|
|
746
772
|
await mkdir(resolve(resolved, ".."), { recursive: true });
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
773
|
+
}
|
|
774
|
+
catch (error) {
|
|
775
|
+
return err(toFileError(error, resolved));
|
|
776
|
+
}
|
|
777
|
+
const afterMkdirAbort = abortResult(abortSignal, resolved);
|
|
778
|
+
if (afterMkdirAbort) {
|
|
779
|
+
return afterMkdirAbort;
|
|
780
|
+
}
|
|
781
|
+
let target = resolved;
|
|
782
|
+
let existingMode;
|
|
783
|
+
let st;
|
|
784
|
+
try {
|
|
785
|
+
st = await lstat(resolved);
|
|
786
|
+
}
|
|
787
|
+
catch {
|
|
788
|
+
st = undefined;
|
|
789
|
+
}
|
|
790
|
+
if (st !== undefined) {
|
|
791
|
+
if (st.isSymbolicLink()) {
|
|
792
|
+
let hop;
|
|
793
|
+
try {
|
|
794
|
+
const link = await readlink(resolved);
|
|
795
|
+
const parentReal = await realpath(resolve(resolved, "..")).catch(() => resolve(resolved, ".."));
|
|
796
|
+
hop = isAbsolute(link) ? link : resolve(parentReal, link);
|
|
797
|
+
}
|
|
798
|
+
catch {
|
|
799
|
+
hop = undefined;
|
|
800
|
+
}
|
|
801
|
+
if (hop === undefined)
|
|
802
|
+
return await this.writeFileInPlace(resolved, content, abortSignal);
|
|
803
|
+
let hopStat;
|
|
804
|
+
try {
|
|
805
|
+
hopStat = await lstat(hop);
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
hopStat = undefined;
|
|
809
|
+
}
|
|
810
|
+
if (hopStat !== undefined && !hopStat.isFile()) {
|
|
811
|
+
return await this.writeFileInPlace(resolved, content, abortSignal);
|
|
812
|
+
}
|
|
813
|
+
target = hop;
|
|
814
|
+
existingMode = hopStat !== undefined ? hopStat.mode & 0o7777 : undefined;
|
|
815
|
+
}
|
|
816
|
+
else if (!st.isFile()) {
|
|
817
|
+
return await this.writeFileInPlace(resolved, content, abortSignal);
|
|
750
818
|
}
|
|
819
|
+
else {
|
|
820
|
+
existingMode = st.mode & 0o7777;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const tmp = `${target}.tmp.${process.pid}.${randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
824
|
+
let tmpDurable = false;
|
|
825
|
+
try {
|
|
826
|
+
const handle = await open(tmp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0));
|
|
827
|
+
try {
|
|
828
|
+
await handle.writeFile(content, { signal: abortSignal });
|
|
829
|
+
if (existingMode !== undefined) {
|
|
830
|
+
try {
|
|
831
|
+
await handle.chmod(existingMode);
|
|
832
|
+
}
|
|
833
|
+
catch (error) {
|
|
834
|
+
if (!isFsSupportError(error))
|
|
835
|
+
throw error;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
await handle.sync();
|
|
840
|
+
}
|
|
841
|
+
catch (error) {
|
|
842
|
+
if (!isFsSupportError(error))
|
|
843
|
+
throw error;
|
|
844
|
+
}
|
|
845
|
+
tmpDurable = true;
|
|
846
|
+
}
|
|
847
|
+
finally {
|
|
848
|
+
await handle.close().catch(() => { });
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
catch (error) {
|
|
852
|
+
await unlink(tmp).catch(() => { });
|
|
853
|
+
if (isNodeError(error) && error.code === "EACCES" && existingMode !== undefined) {
|
|
854
|
+
return await this.writeFileInPlace(resolved, content, abortSignal);
|
|
855
|
+
}
|
|
856
|
+
return err(toFileError(error, resolved));
|
|
857
|
+
}
|
|
858
|
+
const preRenameAbort = abortResult(abortSignal, resolved);
|
|
859
|
+
if (preRenameAbort) {
|
|
860
|
+
await unlink(tmp).catch(() => { });
|
|
861
|
+
return preRenameAbort;
|
|
862
|
+
}
|
|
863
|
+
try {
|
|
864
|
+
await rename(tmp, target);
|
|
865
|
+
return ok(undefined);
|
|
866
|
+
}
|
|
867
|
+
catch (error) {
|
|
868
|
+
const code = isNodeError(error) ? error.code : undefined;
|
|
869
|
+
if (tmpDurable && code !== undefined && RENAME_FALLBACK_CODES.has(code)) {
|
|
870
|
+
const inPlace = await this.writeFileInPlace(resolved, content, abortSignal);
|
|
871
|
+
if (inPlace.ok) {
|
|
872
|
+
await unlink(tmp).catch(() => { });
|
|
873
|
+
return inPlace;
|
|
874
|
+
}
|
|
875
|
+
return err(new FileError("unknown", `write to ${resolved} failed after the atomic rename was refused (${code}): ${inPlace.error.message}. The new content was preserved at ${tmp}`, resolved, inPlace.error));
|
|
876
|
+
}
|
|
877
|
+
await unlink(tmp).catch(() => { });
|
|
878
|
+
return err(toFileError(error, resolved));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async writeFileInPlace(resolved, content, abortSignal) {
|
|
882
|
+
try {
|
|
751
883
|
await writeFile(resolved, content, { signal: abortSignal });
|
|
752
884
|
return ok(undefined);
|
|
753
885
|
}
|
|
@@ -913,12 +1045,13 @@ export class NodeExecutionEnv {
|
|
|
913
1045
|
const shellId = `bg_${++this.bgCounter}_${randomUUID()}`;
|
|
914
1046
|
const entry = {
|
|
915
1047
|
child,
|
|
916
|
-
stdout: {
|
|
917
|
-
stderr: {
|
|
1048
|
+
stdout: { ...newStreamCursorState(), totalBytes: 0 },
|
|
1049
|
+
stderr: { ...newStreamCursorState(), totalBytes: 0 },
|
|
918
1050
|
status: "running",
|
|
919
1051
|
...(handover !== undefined
|
|
920
1052
|
? { spool: { outPath: handover.spool.outPath, errPath: handover.spool.errPath, fileCursor: { ...handover.cursors }, ephemeral: true } }
|
|
921
1053
|
: {}),
|
|
1054
|
+
...(handover !== undefined && (handover.liveRotations ?? 0) > 0 ? { liveRotations: handover.liveRotations } : {}),
|
|
922
1055
|
};
|
|
923
1056
|
const seedOut = Buffer.from(stdoutSoFar.text, "utf8");
|
|
924
1057
|
const seedErr = Buffer.from(stderrSoFar.text, "utf8");
|
|
@@ -928,15 +1061,19 @@ export class NodeExecutionEnv {
|
|
|
928
1061
|
entry.stderr.tail.push(seedErr);
|
|
929
1062
|
entry.stdout.totalBytes += stdoutSoFar.droppedBytes + seedOut.length;
|
|
930
1063
|
entry.stderr.totalBytes += stderrSoFar.droppedBytes + seedErr.length;
|
|
1064
|
+
entry.stdout.acceptedBytes += seedOut.length;
|
|
1065
|
+
entry.stderr.acceptedBytes += seedErr.length;
|
|
931
1066
|
child.stdout?.on("data", (chunk) => {
|
|
932
1067
|
const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
933
1068
|
entry.stdout.tail.push(buf);
|
|
934
1069
|
entry.stdout.totalBytes += buf.length;
|
|
1070
|
+
entry.stdout.acceptedBytes += buf.length;
|
|
935
1071
|
});
|
|
936
1072
|
child.stderr?.on("data", (chunk) => {
|
|
937
1073
|
const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
938
1074
|
entry.stderr.tail.push(buf);
|
|
939
1075
|
entry.stderr.totalBytes += buf.length;
|
|
1076
|
+
entry.stderr.acceptedBytes += buf.length;
|
|
940
1077
|
});
|
|
941
1078
|
child.on("error", () => {
|
|
942
1079
|
if (entry.timer)
|
|
@@ -1040,8 +1177,8 @@ export class NodeExecutionEnv {
|
|
|
1040
1177
|
const shellId = `bg_${++this.bgCounter}_${randomUUID()}`;
|
|
1041
1178
|
const entry = {
|
|
1042
1179
|
child,
|
|
1043
|
-
stdout: {
|
|
1044
|
-
stderr: {
|
|
1180
|
+
stdout: { ...newStreamCursorState(), totalBytes: 0 },
|
|
1181
|
+
stderr: { ...newStreamCursorState(), totalBytes: 0 },
|
|
1045
1182
|
status: "running",
|
|
1046
1183
|
...(spool ? { spool } : {}),
|
|
1047
1184
|
};
|
|
@@ -1051,11 +1188,13 @@ export class NodeExecutionEnv {
|
|
|
1051
1188
|
const buf = Buffer.from(chunk, "utf8");
|
|
1052
1189
|
entry.stdout.tail.push(buf);
|
|
1053
1190
|
entry.stdout.totalBytes += buf.length;
|
|
1191
|
+
entry.stdout.acceptedBytes += buf.length;
|
|
1054
1192
|
});
|
|
1055
1193
|
child.stderr?.on("data", (chunk) => {
|
|
1056
1194
|
const buf = Buffer.from(chunk, "utf8");
|
|
1057
1195
|
entry.stderr.tail.push(buf);
|
|
1058
1196
|
entry.stderr.totalBytes += buf.length;
|
|
1197
|
+
entry.stderr.acceptedBytes += buf.length;
|
|
1059
1198
|
});
|
|
1060
1199
|
child.stdout?.on("error", () => { });
|
|
1061
1200
|
child.stderr?.on("error", () => { });
|
|
@@ -1098,21 +1237,18 @@ export class NodeExecutionEnv {
|
|
|
1098
1237
|
return err(new BackgroundShellError("not_found", `Unknown background shell: ${shellId}`));
|
|
1099
1238
|
}
|
|
1100
1239
|
this.syncSpool(entry);
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
const startByte = Math.max(s.cursorBytes, droppedBytes);
|
|
1105
|
-
const startInTail = startByte - droppedBytes;
|
|
1106
|
-
const inc = startInTail <= 0 ? text : Buffer.from(text, "utf8").subarray(startInTail).toString("utf8");
|
|
1107
|
-
s.cursorBytes = s.totalBytes;
|
|
1108
|
-
return { inc, droppedBeforeCursor };
|
|
1109
|
-
};
|
|
1110
|
-
const out = slice(entry.stdout);
|
|
1111
|
-
const er = slice(entry.stderr);
|
|
1240
|
+
const terminal = entry.status !== "running";
|
|
1241
|
+
const out = sliceStreamIncrement(entry.stdout, terminal);
|
|
1242
|
+
const er = sliceStreamIncrement(entry.stderr, terminal);
|
|
1112
1243
|
const droppedBeforeCursor = out.droppedBeforeCursor + er.droppedBeforeCursor;
|
|
1244
|
+
let stderrInc = er.inc;
|
|
1245
|
+
if (terminal && (entry.liveRotations ?? 0) > 0 && entry.liveRotationsDisclosed !== true) {
|
|
1246
|
+
entry.liveRotationsDisclosed = true;
|
|
1247
|
+
stderrInc = markLiveSpoolRotations(er.inc, entry.liveRotations ?? 0);
|
|
1248
|
+
}
|
|
1113
1249
|
return ok({
|
|
1114
1250
|
stdout: out.inc,
|
|
1115
|
-
stderr:
|
|
1251
|
+
stderr: stderrInc,
|
|
1116
1252
|
status: entry.status,
|
|
1117
1253
|
...(entry.exitCode !== undefined ? { exitCode: entry.exitCode } : {}),
|
|
1118
1254
|
...(droppedBeforeCursor > 0 ? { truncated: true, bytesDroppedBeforeCursor: droppedBeforeCursor } : {}),
|
|
@@ -1165,6 +1301,7 @@ export class NodeExecutionEnv {
|
|
|
1165
1301
|
if (got > 0) {
|
|
1166
1302
|
stream.tail.push(buf.subarray(0, got));
|
|
1167
1303
|
stream.totalBytes += got;
|
|
1304
|
+
stream.acceptedBytes += got;
|
|
1168
1305
|
spool.fileCursor[lane] += got;
|
|
1169
1306
|
}
|
|
1170
1307
|
}
|
|
@@ -1173,15 +1310,18 @@ export class NodeExecutionEnv {
|
|
|
1173
1310
|
}
|
|
1174
1311
|
}
|
|
1175
1312
|
if (spool.ephemeral && spool.fileCursor[lane] >= EXEC_SPOOL_ROTATE_BYTES) {
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1313
|
+
const d = decideSpoolReclaim({
|
|
1314
|
+
size: statSync(path).size,
|
|
1315
|
+
cursor: spool.fileCursor[lane],
|
|
1316
|
+
writerLive: entry.status === "running" || (entry.child.exitCode === null && entry.child.signalCode === null),
|
|
1317
|
+
});
|
|
1318
|
+
if (d.reclaim) {
|
|
1319
|
+
if (d.skippedBytes > 0) {
|
|
1320
|
+
stream.tail.recordSkippedBytes(d.skippedBytes);
|
|
1321
|
+
stream.totalBytes += d.skippedBytes;
|
|
1322
|
+
}
|
|
1323
|
+
if (d.live)
|
|
1324
|
+
entry.liveRotations = (entry.liveRotations ?? 0) + 1;
|
|
1185
1325
|
truncateSync(path, 0);
|
|
1186
1326
|
spool.fileCursor[lane] = 0;
|
|
1187
1327
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare function encodeFrame(json: string): Buffer;
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function createFrameDecoder(maxBytes?: number): (chunk: Buffer) => string[];
|
|
@@ -2,7 +2,7 @@ export function encodeFrame(json) {
|
|
|
2
2
|
const payload = Buffer.from(json, "utf8");
|
|
3
3
|
return Buffer.concat([Buffer.from(`Content-Length: ${payload.length}\r\n\r\n`, "ascii"), payload]);
|
|
4
4
|
}
|
|
5
|
-
export function
|
|
5
|
+
export function createFrameDecoder(maxBytes = 50 * 1024 * 1024) {
|
|
6
6
|
const HEADER_SLACK = 128;
|
|
7
7
|
let buf = Buffer.alloc(0);
|
|
8
8
|
return function decode(chunk) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type SessionWarmup } from "../../core/lsp-session.js";
|
|
2
2
|
import { type LspChildProcess } from "./stdio-lsp-transport.js";
|
|
3
|
-
import type
|
|
4
|
-
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
3
|
+
import { type LspServerManager, type LspSession, type LspReadText } from "../../core/lsp.js";
|
|
4
|
+
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
5
5
|
export declare const DEFAULT_LSP_SERVERS: Readonly<Record<string, string>>;
|
|
6
6
|
export type LspSpawn = (command: string, args: string[], cwd: string, signal?: AbortSignal) => Promise<LspChildProcess | undefined>;
|
|
7
7
|
export interface NodeLspManagerOptions {
|
|
@@ -40,6 +40,8 @@ export declare class NodeLspManager implements LspServerManager {
|
|
|
40
40
|
dispose(): Promise<void>;
|
|
41
41
|
}
|
|
42
42
|
export declare function languageFor(filePath: string): string | undefined;
|
|
43
|
+
export { MAX_LSP_FILE_BYTES } from "../../core/lsp.js";
|
|
44
|
+
export declare const defaultLspReadText: LspReadText;
|
|
43
45
|
export declare function lspSpawnOptions(cwd: string): {
|
|
44
46
|
cwd: string;
|
|
45
47
|
stdio: ["pipe", "pipe", "ignore"];
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { killProcessTree } from "../execution-env/kill-tree.js";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
import { dirname, extname, resolve as resolvePath } from "node:path";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, extname, resolve as resolvePath } from "node:path";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { pathToUri } from "../../core/lsp-protocol.js";
|
|
7
7
|
import { LspDiagnosticsRegistry } from "../../core/lsp-diagnostics.js";
|
|
8
8
|
import { TransportLspSession } from "../../core/lsp-session.js";
|
|
9
9
|
import { StdioLspTransport } from "./stdio-lsp-transport.js";
|
|
10
|
+
import { MAX_LSP_FILE_BYTES } from "../../core/lsp.js";
|
|
10
11
|
export const DEFAULT_LSP_SERVERS = {
|
|
11
12
|
typescript: "typescript-language-server --stdio",
|
|
12
13
|
javascript: "typescript-language-server --stdio",
|
|
@@ -43,7 +44,7 @@ const CLIENT_CAPABILITIES = {
|
|
|
43
44
|
definition: {}, references: {}, hover: {}, documentSymbol: {}, implementation: {}, callHierarchy: {},
|
|
44
45
|
publishDiagnostics: { versionSupport: true },
|
|
45
46
|
},
|
|
46
|
-
workspace: { symbol: {} },
|
|
47
|
+
workspace: { symbol: {}, workspaceFolders: false },
|
|
47
48
|
};
|
|
48
49
|
export class NodeLspManager {
|
|
49
50
|
servers;
|
|
@@ -62,7 +63,7 @@ export class NodeLspManager {
|
|
|
62
63
|
constructor(opts = {}) {
|
|
63
64
|
this.servers = { ...DEFAULT_LSP_SERVERS, ...opts.servers };
|
|
64
65
|
this.spawnFn = opts.spawn ?? defaultLspSpawn;
|
|
65
|
-
this.readTextFn = opts.readText ??
|
|
66
|
+
this.readTextFn = opts.readText ?? defaultLspReadText;
|
|
66
67
|
this.resolveRootFn = opts.resolveRoot ?? defaultResolveRoot;
|
|
67
68
|
this.log = opts.log ?? (() => { });
|
|
68
69
|
this.warmup = opts.warmup;
|
|
@@ -175,7 +176,14 @@ export class NodeLspManager {
|
|
|
175
176
|
}
|
|
176
177
|
const transport = new StdioLspTransport(child);
|
|
177
178
|
try {
|
|
178
|
-
|
|
179
|
+
const rootUri = pathToUri(root);
|
|
180
|
+
await transport.request("initialize", {
|
|
181
|
+
processId: null,
|
|
182
|
+
rootPath: root,
|
|
183
|
+
rootUri,
|
|
184
|
+
workspaceFolders: [{ uri: rootUri, name: basename(root) || root }],
|
|
185
|
+
capabilities: CLIENT_CAPABILITIES,
|
|
186
|
+
}, signal);
|
|
179
187
|
transport.notify("initialized", {});
|
|
180
188
|
}
|
|
181
189
|
catch (e) {
|
|
@@ -218,6 +226,15 @@ function defaultResolveRoot(filePath, env) {
|
|
|
218
226
|
}
|
|
219
227
|
return dirname(resolvePath(filePath));
|
|
220
228
|
}
|
|
229
|
+
export { MAX_LSP_FILE_BYTES } from "../../core/lsp.js";
|
|
230
|
+
export const defaultLspReadText = async (fp, signal) => {
|
|
231
|
+
const st = await stat(fp);
|
|
232
|
+
if (!st.isFile())
|
|
233
|
+
throw new Error(`not a regular file: ${fp}`);
|
|
234
|
+
if (st.size > MAX_LSP_FILE_BYTES)
|
|
235
|
+
throw new Error(`file too large for LSP analysis (${st.size} bytes exceeds ${MAX_LSP_FILE_BYTES})`);
|
|
236
|
+
return readFile(fp, { encoding: "utf8", signal });
|
|
237
|
+
};
|
|
221
238
|
export function lspSpawnOptions(cwd) {
|
|
222
239
|
return process.platform === "win32"
|
|
223
240
|
? { cwd, stdio: ["pipe", "pipe", "ignore"], shell: true }
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { encodeFrame,
|
|
1
|
+
import { encodeFrame, createFrameDecoder } from "./frame-decoder.js";
|
|
2
2
|
import { killProcessTree } from "../execution-env/kill-tree.js";
|
|
3
|
+
import { brandLspFailure } from "../../core/lsp.js";
|
|
3
4
|
export class StdioLspTransport {
|
|
4
5
|
child;
|
|
5
6
|
requestTimeoutMs;
|
|
6
7
|
nextId = 0;
|
|
7
8
|
pending = new Map();
|
|
8
9
|
isClosed = false;
|
|
9
|
-
decode =
|
|
10
|
+
decode = createFrameDecoder();
|
|
10
11
|
notificationHandlers = new Set();
|
|
11
12
|
constructor(child, requestTimeoutMs = 30_000) {
|
|
12
13
|
this.child = child;
|
|
@@ -44,8 +45,10 @@ export class StdioLspTransport {
|
|
|
44
45
|
const p = this.pending.get(msg.id);
|
|
45
46
|
if (p) {
|
|
46
47
|
this.pending.delete(msg.id);
|
|
47
|
-
if (msg.error)
|
|
48
|
-
|
|
48
|
+
if (msg.error) {
|
|
49
|
+
const message = msg.error.message ?? "LSP error";
|
|
50
|
+
p.reject(brandLspFailure(new Error(message), jsonRpcReason(msg.error.code), message));
|
|
51
|
+
}
|
|
49
52
|
else
|
|
50
53
|
p.resolve(msg.result);
|
|
51
54
|
}
|
|
@@ -68,6 +71,7 @@ export class StdioLspTransport {
|
|
|
68
71
|
}
|
|
69
72
|
failAll(err) {
|
|
70
73
|
this.isClosed = true;
|
|
74
|
+
brandLspFailure(err, "server_terminated");
|
|
71
75
|
for (const p of this.pending.values())
|
|
72
76
|
p.reject(err);
|
|
73
77
|
this.pending.clear();
|
|
@@ -77,13 +81,13 @@ export class StdioLspTransport {
|
|
|
77
81
|
}
|
|
78
82
|
request(method, params, signal) {
|
|
79
83
|
if (this.isClosed)
|
|
80
|
-
return Promise.reject(new Error("LSP transport closed"));
|
|
84
|
+
return Promise.reject(brandLspFailure(new Error("LSP transport closed"), "server_terminated"));
|
|
81
85
|
const id = ++this.nextId;
|
|
82
86
|
return new Promise((resolve, reject) => {
|
|
83
87
|
const timer = setTimeout(() => {
|
|
84
88
|
if (this.pending.delete(id)) {
|
|
85
89
|
signal?.removeEventListener("abort", onAbort);
|
|
86
|
-
reject(new Error(`LSP ${method} timed out after ${this.requestTimeoutMs}ms`));
|
|
90
|
+
reject(brandLspFailure(new Error(`LSP ${method} timed out after ${this.requestTimeoutMs}ms`), "timeout"));
|
|
87
91
|
}
|
|
88
92
|
}, this.requestTimeoutMs);
|
|
89
93
|
timer.unref?.();
|
|
@@ -95,7 +99,7 @@ export class StdioLspTransport {
|
|
|
95
99
|
if (this.pending.delete(id)) {
|
|
96
100
|
cleanup();
|
|
97
101
|
this.notify("$/cancelRequest", { id });
|
|
98
|
-
reject(new Error(`LSP ${method} aborted`));
|
|
102
|
+
reject(brandLspFailure(new Error(`LSP ${method} aborted`), "cancelled"));
|
|
99
103
|
}
|
|
100
104
|
};
|
|
101
105
|
this.pending.set(id, {
|
|
@@ -119,7 +123,7 @@ export class StdioLspTransport {
|
|
|
119
123
|
catch (e) {
|
|
120
124
|
if (this.pending.delete(id)) {
|
|
121
125
|
cleanup();
|
|
122
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
126
|
+
reject(brandLspFailure(e instanceof Error ? e : new Error(String(e)), "server_terminated"));
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
});
|
|
@@ -145,6 +149,13 @@ export class StdioLspTransport {
|
|
|
145
149
|
}
|
|
146
150
|
}
|
|
147
151
|
}
|
|
152
|
+
function jsonRpcReason(code) {
|
|
153
|
+
if (code === -32601)
|
|
154
|
+
return "unsupported_operation";
|
|
155
|
+
if (code === -32800 || code === -32802)
|
|
156
|
+
return "cancelled";
|
|
157
|
+
return "server_error";
|
|
158
|
+
}
|
|
148
159
|
function toError(e) {
|
|
149
160
|
if (e instanceof Error)
|
|
150
161
|
return e;
|