@sema-agent/core 1.450.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 +11 -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 +2 -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 +2 -0
- package/dist/orchestration/run-workflow-tool.js +35 -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
|
@@ -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
|
export class InMemoryToolResultStore {
|
|
4
4
|
opts;
|
|
5
5
|
map = new Map();
|
|
@@ -146,7 +146,7 @@ export function makeReadToolResultTool(store) {
|
|
|
146
146
|
const lim = Math.min(READ_MAX_LIMIT, Math.max(1, intOr(limit, READ_DEFAULT_LIMIT)));
|
|
147
147
|
const slice = await store.get(ref, { offset: off, limit: lim });
|
|
148
148
|
if (!slice) {
|
|
149
|
-
return `No persisted output for ref "${ref}" — it may have expired, or this process has no durable tool-result store (the preview above is all that remains)
|
|
149
|
+
return errorResult(`No persisted output for ref "${ref}" — it may have expired, or this process has no durable tool-result store (the preview above is all that remains).`);
|
|
150
150
|
}
|
|
151
151
|
const end = slice.offset + slice.content.length;
|
|
152
152
|
const more = end < slice.totalChars ? `\n…[${slice.totalChars - end} more chars — call again with offset=${end}.]` : "";
|
package/dist/core/tools.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { TSchema } from "typebox";
|
|
2
2
|
import type { AgentTool, Skill } from "../internal/harness.js";
|
|
3
3
|
import type { SkillSpec, ToolSpec } from "./types.js";
|
|
4
|
+
export declare function errorResult(text: string, details?: unknown): {
|
|
5
|
+
content: string;
|
|
6
|
+
isError: true;
|
|
7
|
+
details?: unknown;
|
|
8
|
+
};
|
|
4
9
|
export declare function defineTool<TParams extends TSchema = TSchema>(spec: ToolSpec<TParams>): AgentTool<TParams>;
|
|
5
10
|
export declare function toSkill(spec: SkillSpec): Skill;
|
package/dist/core/tools.js
CHANGED
|
@@ -22,6 +22,9 @@ function isEmptyToolContent(content) {
|
|
|
22
22
|
return true;
|
|
23
23
|
return content.every((b) => b.type === "text" && (typeof b.text !== "string" || b.text.trim() === ""));
|
|
24
24
|
}
|
|
25
|
+
export function errorResult(text, details) {
|
|
26
|
+
return details === undefined ? { content: text, isError: true } : { content: text, isError: true, details };
|
|
27
|
+
}
|
|
25
28
|
export function defineTool(spec) {
|
|
26
29
|
const executionMode = spec.executionMode ?? (spec.effect === "read" ? "parallel" : "sequential");
|
|
27
30
|
const tool = {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -231,6 +231,7 @@ export interface TaskSpec {
|
|
|
231
231
|
images?: ImageInput[];
|
|
232
232
|
sessionId?: string;
|
|
233
233
|
requireExistingSession?: boolean;
|
|
234
|
+
oneShot?: boolean;
|
|
234
235
|
clientContext?: {
|
|
235
236
|
timeZone?: string;
|
|
236
237
|
userEmail?: string;
|
|
@@ -663,6 +664,7 @@ export interface BackgroundChildEvent {
|
|
|
663
664
|
resumable?: boolean;
|
|
664
665
|
status?: "completed" | "killed" | "failed";
|
|
665
666
|
seq?: number;
|
|
667
|
+
completionId?: string;
|
|
666
668
|
stoppedBy?: "user" | "parent" | "system" | (string & {});
|
|
667
669
|
summary?: string;
|
|
668
670
|
usage?: {
|
|
@@ -11,6 +11,8 @@ export interface WorkflowJournalStore {
|
|
|
11
11
|
export declare const MAX_JOURNAL_RESULT_BYTES: number;
|
|
12
12
|
export declare function oversizeJournalResult(serialized: string): boolean;
|
|
13
13
|
export declare function callKeyOrdinal(callKey: string): number;
|
|
14
|
+
export declare const JOURNAL_OVERSIZE_ERROR_CODE = "workflow.journal_oversize";
|
|
15
|
+
export declare function journalOversizeTombstone(result: TaskResult, bytes: number): TaskResult;
|
|
14
16
|
export declare class InMemoryWorkflowJournalStore implements WorkflowJournalStore {
|
|
15
17
|
private readonly runs;
|
|
16
18
|
load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
|
|
@@ -7,6 +7,20 @@ export function callKeyOrdinal(callKey) {
|
|
|
7
7
|
const n = Number.parseInt(i >= 0 ? callKey.slice(0, i) : callKey, 10);
|
|
8
8
|
return Number.isFinite(n) ? n : 0;
|
|
9
9
|
}
|
|
10
|
+
export const JOURNAL_OVERSIZE_ERROR_CODE = "workflow.journal_oversize";
|
|
11
|
+
export function journalOversizeTombstone(result, bytes) {
|
|
12
|
+
const message = `[journal] result omitted: ${bytes} bytes exceeds the ${MAX_JOURNAL_RESULT_BYTES}-byte per-entry journal cap; ` +
|
|
13
|
+
`the agent's real terminal status was "${result.status}". A resume re-runs this agent (and everything after it) live.`;
|
|
14
|
+
return {
|
|
15
|
+
taskId: result.taskId,
|
|
16
|
+
sessionId: result.sessionId,
|
|
17
|
+
status: "failed",
|
|
18
|
+
result: message,
|
|
19
|
+
errorCode: JOURNAL_OVERSIZE_ERROR_CODE,
|
|
20
|
+
errorMessage: message,
|
|
21
|
+
stats: result.stats,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
10
24
|
function snapshot(entry) {
|
|
11
25
|
try {
|
|
12
26
|
return structuredClone(entry);
|
|
@@ -71,6 +71,7 @@ export declare class NodeExecutionEnv implements ExecutionEnv, BackgroundShellCa
|
|
|
71
71
|
}): Promise<Result<string[], FileError>>;
|
|
72
72
|
readBinaryFile(path: string, abortSignal?: AbortSignal): Promise<Result<Uint8Array, FileError>>;
|
|
73
73
|
writeFile(path: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
74
|
+
private writeFileInPlace;
|
|
74
75
|
writeFileExclusive(path: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
75
76
|
appendFile(path: string, content: string | Uint8Array): Promise<Result<void, FileError>>;
|
|
76
77
|
fileInfo(path: string): Promise<Result<FileInfo, FileError>>;
|
|
@@ -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, 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);
|
|
@@ -744,10 +749,116 @@ export class NodeExecutionEnv {
|
|
|
744
749
|
}
|
|
745
750
|
try {
|
|
746
751
|
await mkdir(resolve(resolved, ".."), { recursive: true });
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
752
|
+
}
|
|
753
|
+
catch (error) {
|
|
754
|
+
return err(toFileError(error, resolved));
|
|
755
|
+
}
|
|
756
|
+
const afterMkdirAbort = abortResult(abortSignal, resolved);
|
|
757
|
+
if (afterMkdirAbort) {
|
|
758
|
+
return afterMkdirAbort;
|
|
759
|
+
}
|
|
760
|
+
let target = resolved;
|
|
761
|
+
let existingMode;
|
|
762
|
+
let st;
|
|
763
|
+
try {
|
|
764
|
+
st = await lstat(resolved);
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
st = undefined;
|
|
768
|
+
}
|
|
769
|
+
if (st !== undefined) {
|
|
770
|
+
if (st.isSymbolicLink()) {
|
|
771
|
+
let hop;
|
|
772
|
+
try {
|
|
773
|
+
const link = await readlink(resolved);
|
|
774
|
+
const parentReal = await realpath(resolve(resolved, "..")).catch(() => resolve(resolved, ".."));
|
|
775
|
+
hop = isAbsolute(link) ? link : resolve(parentReal, link);
|
|
776
|
+
}
|
|
777
|
+
catch {
|
|
778
|
+
hop = undefined;
|
|
779
|
+
}
|
|
780
|
+
if (hop === undefined)
|
|
781
|
+
return await this.writeFileInPlace(resolved, content, abortSignal);
|
|
782
|
+
let hopStat;
|
|
783
|
+
try {
|
|
784
|
+
hopStat = await lstat(hop);
|
|
785
|
+
}
|
|
786
|
+
catch {
|
|
787
|
+
hopStat = undefined;
|
|
788
|
+
}
|
|
789
|
+
if (hopStat !== undefined && !hopStat.isFile()) {
|
|
790
|
+
return await this.writeFileInPlace(resolved, content, abortSignal);
|
|
791
|
+
}
|
|
792
|
+
target = hop;
|
|
793
|
+
existingMode = hopStat !== undefined ? hopStat.mode & 0o7777 : undefined;
|
|
794
|
+
}
|
|
795
|
+
else if (!st.isFile()) {
|
|
796
|
+
return await this.writeFileInPlace(resolved, content, abortSignal);
|
|
750
797
|
}
|
|
798
|
+
else {
|
|
799
|
+
existingMode = st.mode & 0o7777;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
const tmp = `${target}.tmp.${process.pid}.${randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
803
|
+
let tmpDurable = false;
|
|
804
|
+
try {
|
|
805
|
+
const handle = await open(tmp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0));
|
|
806
|
+
try {
|
|
807
|
+
await handle.writeFile(content, { signal: abortSignal });
|
|
808
|
+
if (existingMode !== undefined) {
|
|
809
|
+
try {
|
|
810
|
+
await handle.chmod(existingMode);
|
|
811
|
+
}
|
|
812
|
+
catch (error) {
|
|
813
|
+
if (!isFsSupportError(error))
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
await handle.sync();
|
|
819
|
+
}
|
|
820
|
+
catch (error) {
|
|
821
|
+
if (!isFsSupportError(error))
|
|
822
|
+
throw error;
|
|
823
|
+
}
|
|
824
|
+
tmpDurable = true;
|
|
825
|
+
}
|
|
826
|
+
finally {
|
|
827
|
+
await handle.close().catch(() => { });
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
catch (error) {
|
|
831
|
+
await unlink(tmp).catch(() => { });
|
|
832
|
+
if (isNodeError(error) && error.code === "EACCES" && existingMode !== undefined) {
|
|
833
|
+
return await this.writeFileInPlace(resolved, content, abortSignal);
|
|
834
|
+
}
|
|
835
|
+
return err(toFileError(error, resolved));
|
|
836
|
+
}
|
|
837
|
+
const preRenameAbort = abortResult(abortSignal, resolved);
|
|
838
|
+
if (preRenameAbort) {
|
|
839
|
+
await unlink(tmp).catch(() => { });
|
|
840
|
+
return preRenameAbort;
|
|
841
|
+
}
|
|
842
|
+
try {
|
|
843
|
+
await rename(tmp, target);
|
|
844
|
+
return ok(undefined);
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
const code = isNodeError(error) ? error.code : undefined;
|
|
848
|
+
if (tmpDurable && code !== undefined && RENAME_FALLBACK_CODES.has(code)) {
|
|
849
|
+
const inPlace = await this.writeFileInPlace(resolved, content, abortSignal);
|
|
850
|
+
if (inPlace.ok) {
|
|
851
|
+
await unlink(tmp).catch(() => { });
|
|
852
|
+
return inPlace;
|
|
853
|
+
}
|
|
854
|
+
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));
|
|
855
|
+
}
|
|
856
|
+
await unlink(tmp).catch(() => { });
|
|
857
|
+
return err(toFileError(error, resolved));
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
async writeFileInPlace(resolved, content, abortSignal) {
|
|
861
|
+
try {
|
|
751
862
|
await writeFile(resolved, content, { signal: abortSignal });
|
|
752
863
|
return ok(undefined);
|
|
753
864
|
}
|
|
@@ -913,8 +1024,8 @@ export class NodeExecutionEnv {
|
|
|
913
1024
|
const shellId = `bg_${++this.bgCounter}_${randomUUID()}`;
|
|
914
1025
|
const entry = {
|
|
915
1026
|
child,
|
|
916
|
-
stdout: {
|
|
917
|
-
stderr: {
|
|
1027
|
+
stdout: { ...newStreamCursorState(), totalBytes: 0 },
|
|
1028
|
+
stderr: { ...newStreamCursorState(), totalBytes: 0 },
|
|
918
1029
|
status: "running",
|
|
919
1030
|
...(handover !== undefined
|
|
920
1031
|
? { spool: { outPath: handover.spool.outPath, errPath: handover.spool.errPath, fileCursor: { ...handover.cursors }, ephemeral: true } }
|
|
@@ -928,15 +1039,19 @@ export class NodeExecutionEnv {
|
|
|
928
1039
|
entry.stderr.tail.push(seedErr);
|
|
929
1040
|
entry.stdout.totalBytes += stdoutSoFar.droppedBytes + seedOut.length;
|
|
930
1041
|
entry.stderr.totalBytes += stderrSoFar.droppedBytes + seedErr.length;
|
|
1042
|
+
entry.stdout.acceptedBytes += seedOut.length;
|
|
1043
|
+
entry.stderr.acceptedBytes += seedErr.length;
|
|
931
1044
|
child.stdout?.on("data", (chunk) => {
|
|
932
1045
|
const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
933
1046
|
entry.stdout.tail.push(buf);
|
|
934
1047
|
entry.stdout.totalBytes += buf.length;
|
|
1048
|
+
entry.stdout.acceptedBytes += buf.length;
|
|
935
1049
|
});
|
|
936
1050
|
child.stderr?.on("data", (chunk) => {
|
|
937
1051
|
const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
938
1052
|
entry.stderr.tail.push(buf);
|
|
939
1053
|
entry.stderr.totalBytes += buf.length;
|
|
1054
|
+
entry.stderr.acceptedBytes += buf.length;
|
|
940
1055
|
});
|
|
941
1056
|
child.on("error", () => {
|
|
942
1057
|
if (entry.timer)
|
|
@@ -1040,8 +1155,8 @@ export class NodeExecutionEnv {
|
|
|
1040
1155
|
const shellId = `bg_${++this.bgCounter}_${randomUUID()}`;
|
|
1041
1156
|
const entry = {
|
|
1042
1157
|
child,
|
|
1043
|
-
stdout: {
|
|
1044
|
-
stderr: {
|
|
1158
|
+
stdout: { ...newStreamCursorState(), totalBytes: 0 },
|
|
1159
|
+
stderr: { ...newStreamCursorState(), totalBytes: 0 },
|
|
1045
1160
|
status: "running",
|
|
1046
1161
|
...(spool ? { spool } : {}),
|
|
1047
1162
|
};
|
|
@@ -1051,11 +1166,13 @@ export class NodeExecutionEnv {
|
|
|
1051
1166
|
const buf = Buffer.from(chunk, "utf8");
|
|
1052
1167
|
entry.stdout.tail.push(buf);
|
|
1053
1168
|
entry.stdout.totalBytes += buf.length;
|
|
1169
|
+
entry.stdout.acceptedBytes += buf.length;
|
|
1054
1170
|
});
|
|
1055
1171
|
child.stderr?.on("data", (chunk) => {
|
|
1056
1172
|
const buf = Buffer.from(chunk, "utf8");
|
|
1057
1173
|
entry.stderr.tail.push(buf);
|
|
1058
1174
|
entry.stderr.totalBytes += buf.length;
|
|
1175
|
+
entry.stderr.acceptedBytes += buf.length;
|
|
1059
1176
|
});
|
|
1060
1177
|
child.stdout?.on("error", () => { });
|
|
1061
1178
|
child.stderr?.on("error", () => { });
|
|
@@ -1098,17 +1215,9 @@ export class NodeExecutionEnv {
|
|
|
1098
1215
|
return err(new BackgroundShellError("not_found", `Unknown background shell: ${shellId}`));
|
|
1099
1216
|
}
|
|
1100
1217
|
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);
|
|
1218
|
+
const terminal = entry.status !== "running";
|
|
1219
|
+
const out = sliceStreamIncrement(entry.stdout, terminal);
|
|
1220
|
+
const er = sliceStreamIncrement(entry.stderr, terminal);
|
|
1112
1221
|
const droppedBeforeCursor = out.droppedBeforeCursor + er.droppedBeforeCursor;
|
|
1113
1222
|
return ok({
|
|
1114
1223
|
stdout: out.inc,
|
|
@@ -1165,6 +1274,7 @@ export class NodeExecutionEnv {
|
|
|
1165
1274
|
if (got > 0) {
|
|
1166
1275
|
stream.tail.push(buf.subarray(0, got));
|
|
1167
1276
|
stream.totalBytes += got;
|
|
1277
|
+
stream.acceptedBytes += got;
|
|
1168
1278
|
spool.fileCursor[lane] += got;
|
|
1169
1279
|
}
|
|
1170
1280
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SessionWarmup } from "../../core/lsp-session.js";
|
|
2
2
|
import { type LspChildProcess } from "./stdio-lsp-transport.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type LspServerManager, type LspSession, type LspReadText } from "../../core/lsp.js";
|
|
4
4
|
import type { ExecutionEnv } from "../../internal/harness.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>;
|
|
@@ -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,5 +1,6 @@
|
|
|
1
1
|
import { encodeFrame, makeFrameDecoder } 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;
|
|
@@ -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;
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { assembleCodeTools, type CodeToolsConfig, CODE_ROLE, assembleFullBodyToo
|
|
|
13
13
|
export { capAggregateToolResults, AGGREGATE_TOOL_RESULT_BUDGET_CHARS, DEFAULT_BUDGET_EXEMPT_TOOLS, type AggregateBudgetOptions, } from "./core/tool-result-budget.js";
|
|
14
14
|
export { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES, type MediaStripInfo } from "./core/media-byte-cap.js";
|
|
15
15
|
export { createAskUserQuestionTool, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, type OnQuestion, type AskQuestion, type AskQuestionOption, type AskQuestionRequest, type QuestionAnswer, type QuestionAnswerItem, } from "./core/ask-question.js";
|
|
16
|
-
export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, type LspOperation, type LspServerManager, type LspSession, type LspResult, type LspLocation, type LspSymbolInfo, type LspRequestParams, type LspToolOptions, type LspTransport, type LspReadText, } from "./core/lsp.js";
|
|
16
|
+
export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, type LspNoneReason, type LspOperation, type LspServerManager, type LspSession, type LspResult, type LspLocation, type LspSymbolInfo, type LspRequestParams, type LspToolOptions, type LspTransport, type LspReadText, } from "./core/lsp.js";
|
|
17
17
|
export { buildRequest, parseResult, callHierarchyMethod, pathToUri } from "./core/lsp-protocol.js";
|
|
18
18
|
export { TransportLspSession, type SessionWarmup } from "./core/lsp-session.js";
|
|
19
19
|
export { LspDiagnosticsRegistry, formatDiagnosticsBlock, formatDiagnosticsSummary, type LspDiagnostic, type LspFileDiagnostics, } from "./core/lsp-diagnostics.js";
|
|
@@ -153,7 +153,7 @@ export { FileWorkflowRunStore, type FileWorkflowRunStoreOptions } from "./stores
|
|
|
153
153
|
export { workflowRunStoreContract } from "./core/workflow-run-store-contract.js";
|
|
154
154
|
export { type BackgroundAgentStore, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentUsage, canAccessAgentRecord, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, } from "./core/background-agent-store.js";
|
|
155
155
|
export { FileBackgroundAgentStore, type FileBackgroundAgentStoreOptions } from "./stores/file/background-agent-store.js";
|
|
156
|
-
export { type WorkflowJournalStore, type WorkflowJournalEntry, InMemoryWorkflowJournalStore, callKeyOrdinal, } from "./core/workflow-journal-store.js";
|
|
156
|
+
export { type WorkflowJournalStore, type WorkflowJournalEntry, InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, } from "./core/workflow-journal-store.js";
|
|
157
157
|
export { untrustedEgressForHuman, redactHostLeaks, redactSecrets, boundedRedactedSummary } from "./core/untrusted-egress.js";
|
|
158
158
|
export { MemoryRosterStore, FileRosterStore, type RosterStore, type RosterEntry, type RosterAccess, type RosterGcOptions } from "./agents/roster-store.js";
|
|
159
159
|
export { CONFIG_CATALOG_VERSION, describeConfigCatalog, resolveEffectiveConfig, type ConfigKnob, type ConfigOverrideDeclaration, type ConfigProvenance, type EffectiveConfigField, } from "./config/catalog.js";
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export { assembleCodeTools, CODE_ROLE, assembleFullBodyTools, FULL_BODY_ROLE } f
|
|
|
11
11
|
export { capAggregateToolResults, AGGREGATE_TOOL_RESULT_BUDGET_CHARS, DEFAULT_BUDGET_EXEMPT_TOOLS, } from "./core/tool-result-budget.js";
|
|
12
12
|
export { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES } from "./core/media-byte-cap.js";
|
|
13
13
|
export { createAskUserQuestionTool, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, } from "./core/ask-question.js";
|
|
14
|
-
export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, } from "./core/lsp.js";
|
|
14
|
+
export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, } from "./core/lsp.js";
|
|
15
15
|
export { buildRequest, parseResult, callHierarchyMethod, pathToUri } from "./core/lsp-protocol.js";
|
|
16
16
|
export { TransportLspSession } from "./core/lsp-session.js";
|
|
17
17
|
export { LspDiagnosticsRegistry, formatDiagnosticsBlock, formatDiagnosticsSummary, } from "./core/lsp-diagnostics.js";
|
|
@@ -140,7 +140,7 @@ export { FileWorkflowRunStore } from "./stores/file/workflow-run-store.js";
|
|
|
140
140
|
export { workflowRunStoreContract } from "./core/workflow-run-store-contract.js";
|
|
141
141
|
export { canAccessAgentRecord, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, } from "./core/background-agent-store.js";
|
|
142
142
|
export { FileBackgroundAgentStore } from "./stores/file/background-agent-store.js";
|
|
143
|
-
export { InMemoryWorkflowJournalStore, callKeyOrdinal, } from "./core/workflow-journal-store.js";
|
|
143
|
+
export { InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, } from "./core/workflow-journal-store.js";
|
|
144
144
|
export { untrustedEgressForHuman, redactHostLeaks, redactSecrets, boundedRedactedSummary } from "./core/untrusted-egress.js";
|
|
145
145
|
export { MemoryRosterStore, FileRosterStore } from "./agents/roster-store.js";
|
|
146
146
|
export { CONFIG_CATALOG_VERSION, describeConfigCatalog, resolveEffectiveConfig, } from "./config/catalog.js";
|
|
@@ -25,6 +25,7 @@ export interface WorkflowCompletionNotifier {
|
|
|
25
25
|
result?: string;
|
|
26
26
|
usage?: unknown;
|
|
27
27
|
diagnostics?: string;
|
|
28
|
+
completionId?: string;
|
|
28
29
|
}): Promise<void> | void;
|
|
29
30
|
ackServed?(input: {
|
|
30
31
|
runId: string;
|
|
@@ -78,6 +79,7 @@ export interface RunWorkflowToolDeps {
|
|
|
78
79
|
sizeGuideline?: WorkflowSizeGuideline;
|
|
79
80
|
sourceTaskId?: string;
|
|
80
81
|
principal?: string;
|
|
82
|
+
oneShot?: boolean;
|
|
81
83
|
workflowDepth?: number;
|
|
82
84
|
parentCwd?: string;
|
|
83
85
|
parentThinking?: () => import("../core/types.js").TaskSpec["thinking"];
|