@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.
Files changed (57) hide show
  1. package/dist/agents/observer.js +3 -0
  2. package/dist/agents/subagent.js +51 -12
  3. package/dist/core/ask-question.js +5 -5
  4. package/dist/core/background-agent-store.d.ts +2 -0
  5. package/dist/core/background-agent-store.js +5 -1
  6. package/dist/core/exec-output-tail.d.ts +18 -1
  7. package/dist/core/exec-output-tail.js +38 -5
  8. package/dist/core/lsp-session.d.ts +1 -0
  9. package/dist/core/lsp-session.js +24 -6
  10. package/dist/core/lsp.d.ts +10 -0
  11. package/dist/core/lsp.js +63 -6
  12. package/dist/core/mailbox-store.d.ts +3 -2
  13. package/dist/core/mailbox-store.js +19 -4
  14. package/dist/core/memory.js +1 -1
  15. package/dist/core/runner/prepare-task.js +11 -1
  16. package/dist/core/runner/runtask.js +1 -1
  17. package/dist/core/session-reconcile.js +5 -2
  18. package/dist/core/task-notification.d.ts +1 -0
  19. package/dist/core/task-registry.d.ts +15 -9
  20. package/dist/core/task-registry.js +290 -53
  21. package/dist/core/tool-result-store.js +2 -2
  22. package/dist/core/tools.d.ts +5 -0
  23. package/dist/core/tools.js +3 -0
  24. package/dist/core/types.d.ts +2 -0
  25. package/dist/core/workflow-journal-store.d.ts +2 -0
  26. package/dist/core/workflow-journal-store.js +14 -0
  27. package/dist/engine/execution-env/node-execution-env.d.ts +1 -0
  28. package/dist/engine/execution-env/node-execution-env.js +130 -20
  29. package/dist/engine/lsp/node-lsp-manager.d.ts +3 -1
  30. package/dist/engine/lsp/node-lsp-manager.js +22 -5
  31. package/dist/engine/lsp/stdio-lsp-transport.d.ts +1 -1
  32. package/dist/engine/lsp/stdio-lsp-transport.js +17 -6
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +2 -2
  35. package/dist/orchestration/run-workflow-tool.d.ts +2 -0
  36. package/dist/orchestration/run-workflow-tool.js +35 -6
  37. package/dist/orchestration/workflow.d.ts +9 -0
  38. package/dist/orchestration/workflow.js +80 -5
  39. package/dist/stores/cc/mailbox-store.js +6 -1
  40. package/dist/stores/file/background-agent-store.js +3 -2
  41. package/dist/stores/file/mailbox-store.d.ts +1 -1
  42. package/dist/stores/file/mailbox-store.js +9 -7
  43. package/dist/tools/fs/encoding.d.ts +5 -0
  44. package/dist/tools/fs/encoding.js +6 -0
  45. package/dist/tools/fs/index.js +184 -120
  46. package/dist/tools/fs/notebook.d.ts +43 -0
  47. package/dist/tools/fs/notebook.js +141 -0
  48. package/dist/tools/fs/repo-map.js +2 -2
  49. package/dist/tools/fs/search.js +141 -12
  50. package/dist/tools/gitea-issue.js +4 -2
  51. package/dist/tools/monitor.js +12 -8
  52. package/dist/tools/scheduler-tools.js +16 -16
  53. package/dist/tools/task-list.js +34 -12
  54. package/dist/tools/web.d.ts +2 -0
  55. package/dist/tools/web.js +105 -19
  56. package/dist/tools/worktree.js +14 -14
  57. 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}.]` : "";
@@ -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;
@@ -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 = {
@@ -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
- const afterMkdirAbort = abortResult(abortSignal, resolved);
748
- if (afterMkdirAbort) {
749
- return afterMkdirAbort;
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: { tail: new RollingTailBuffer(), totalBytes: 0, cursorBytes: 0 },
917
- stderr: { tail: new RollingTailBuffer(), totalBytes: 0, cursorBytes: 0 },
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: { tail: new RollingTailBuffer(), totalBytes: 0, cursorBytes: 0 },
1044
- stderr: { tail: new RollingTailBuffer(), totalBytes: 0, cursorBytes: 0 },
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 slice = (s) => {
1102
- const { text, droppedBytes } = s.tail.result();
1103
- const droppedBeforeCursor = Math.max(0, droppedBytes - s.cursorBytes);
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 { LspServerManager, LspSession, LspReadText } from "../../core/lsp.js";
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 ?? ((fp) => readFile(fp, "utf8"));
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
- await transport.request("initialize", { processId: null, rootUri: pathToUri(root), capabilities: CLIENT_CAPABILITIES }, signal);
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,4 +1,4 @@
1
- import type { LspTransport } from "../../core/lsp.js";
1
+ import { type LspTransport } from "../../core/lsp.js";
2
2
  export interface LspChildProcess {
3
3
  stdin: {
4
4
  write(data: Buffer): void;
@@ -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
- p.reject(new Error(msg.error.message ?? "LSP error"));
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"];