@sema-agent/server 1.316.0 → 1.318.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/README.md +9 -0
- package/README.zh-CN.md +7 -0
- package/dist/config-types.d.ts +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.js +148 -45
- package/dist/elicitation.js +2 -2
- package/dist/http/route-ctx.d.ts +51 -2
- package/dist/http/routes/approvals-assistant.d.ts +11 -0
- package/dist/http/routes/approvals-assistant.js +530 -0
- package/dist/http/routes/attachments.js +7 -7
- package/dist/http/routes/fleet.d.ts +4 -0
- package/dist/http/routes/fleet.js +147 -0
- package/dist/http/routes/images.js +29 -29
- package/dist/http/routes/leader.d.ts +4 -0
- package/dist/http/routes/leader.js +48 -0
- package/dist/http/routes/memory-policy.js +10 -10
- package/dist/http/routes/notify-wake.d.ts +4 -0
- package/dist/http/routes/notify-wake.js +133 -0
- package/dist/http/routes/observability.js +5 -5
- package/dist/http/routes/runs.d.ts +19 -0
- package/dist/http/routes/runs.js +967 -0
- package/dist/http/routes/session-sync.js +28 -28
- package/dist/http/routes/sessions-list.js +8 -8
- package/dist/http/routes/sessions.js +47 -47
- package/dist/http/routes/side-query.d.ts +4 -0
- package/dist/http/routes/side-query.js +88 -0
- package/dist/http/routes/tasks.d.ts +4 -0
- package/dist/http/routes/tasks.js +632 -0
- package/dist/http/routes/trace-usage.d.ts +4 -0
- package/dist/http/routes/trace-usage.js +239 -0
- package/dist/http/routes/workflows.d.ts +5 -0
- package/dist/http/routes/workflows.js +337 -0
- package/dist/http/run-meta.d.ts +11 -0
- package/dist/http/run-meta.js +16 -0
- package/dist/http/send.d.ts +1 -0
- package/dist/http/send.js +15 -0
- package/dist/http/server.d.ts +6 -5
- package/dist/http/server.js +241 -3166
- package/dist/http/sse-log.js +2 -2
- package/dist/http/wire-types.d.ts +6 -0
- package/dist/leader/endpoint.js +4 -4
- package/dist/main.js +5 -5
- package/dist/plugins/posix-shell-fs.d.ts +35 -0
- package/dist/plugins/posix-shell-fs.js +122 -0
- package/dist/plugins/remote-env-adb.d.ts +2 -1
- package/dist/plugins/remote-env-adb.js +19 -108
- package/dist/plugins/remote-env-file-error.d.ts +1 -0
- package/dist/plugins/remote-env-file-error.js +16 -0
- package/dist/plugins/remote-env-host.js +4 -3
- package/dist/plugins/remote-env-k8s.d.ts +2 -2
- package/dist/plugins/remote-env-k8s.js +22 -97
- package/dist/plugins/remote-env-local-docker.d.ts +2 -2
- package/dist/plugins/remote-env-local-docker.js +32 -109
- package/dist/plugins/remote-env-ssh.d.ts +2 -1
- package/dist/plugins/remote-env-ssh.js +15 -61
- package/dist/question.js +2 -2
- package/dist/run-local.js +1 -1
- package/dist/tool-approval.js +2 -2
- package/dist/trace/ledger-sink.js +1 -1
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +3 -0
- package/package.json +1 -1
|
@@ -7,7 +7,8 @@ import WebSocket from "ws";
|
|
|
7
7
|
import { shellQuote, kindFromMode } from "./remote-shell.js";
|
|
8
8
|
import { presignS3Url } from "./s3-presign.js";
|
|
9
9
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
10
|
-
import { fileErrorFromExec } from "./remote-env-file-error.js";
|
|
10
|
+
import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
|
|
11
|
+
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
11
12
|
import { randomUUID } from "node:crypto";
|
|
12
13
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
13
14
|
const PROVIDER = "k8s";
|
|
@@ -833,7 +834,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
833
834
|
if (!r.ok)
|
|
834
835
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
835
836
|
if (r.value.exitCode !== 0)
|
|
836
|
-
return { ok: false, error:
|
|
837
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "not_found") };
|
|
837
838
|
return ok(new Uint8Array(Buffer.from(r.value.stdout.replace(/\s+/g, ""), "base64")));
|
|
838
839
|
}
|
|
839
840
|
async readTextFile(p, abortSignal) {
|
|
@@ -859,7 +860,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
859
860
|
if (!mk.ok)
|
|
860
861
|
return { ok: false, error: fileErrorFromExec(mk.error, dir) };
|
|
861
862
|
if (mk.value.exitCode !== 0)
|
|
862
|
-
return { ok: false, error:
|
|
863
|
+
return { ok: false, error: classifyFsStderr(mk.value.stderr, dir) };
|
|
863
864
|
return undefined;
|
|
864
865
|
}
|
|
865
866
|
async writeFile(p, content, abortSignal) {
|
|
@@ -885,99 +886,44 @@ export class RemoteK8sExecutionEnv {
|
|
|
885
886
|
if (!r.ok)
|
|
886
887
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
887
888
|
if (r.value.exitCode !== 0)
|
|
888
|
-
return { ok: false, error:
|
|
889
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs) };
|
|
889
890
|
}
|
|
890
891
|
return ok(undefined);
|
|
891
892
|
}
|
|
893
|
+
posixFsInst;
|
|
894
|
+
get posixFs() {
|
|
895
|
+
return (this.posixFsInst ??= createPosixShellFs({
|
|
896
|
+
run: (cmd, abortSignal) => this.exec(cmd, { abortSignal }),
|
|
897
|
+
resolve: (p) => this.resolve(p),
|
|
898
|
+
tempDir: () => this.cwd,
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
892
901
|
async fileInfo(p, abortSignal) {
|
|
893
|
-
|
|
894
|
-
const r = await this.exec(`stat -c '%s %Y %f' ${shellQuote(abs)}`, { abortSignal });
|
|
895
|
-
if (!r.ok)
|
|
896
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
897
|
-
if (r.value.exitCode !== 0)
|
|
898
|
-
return { ok: false, error: this.fileErrorFrom(r.value.stderr, abs, "not_found") };
|
|
899
|
-
const [sizeS, mtimeS, modeHex] = r.value.stdout.trim().split(/\s+/);
|
|
900
|
-
return ok({ name: path.posix.basename(abs), path: abs, kind: kindFromMode(parseInt(modeHex ?? "0", 16)), size: Number(sizeS) || 0, mtimeMs: (Number(mtimeS) || 0) * 1000 });
|
|
902
|
+
return this.posixFs.statFileInfo(p, abortSignal);
|
|
901
903
|
}
|
|
902
904
|
async listDir(p, abortSignal) {
|
|
903
|
-
|
|
904
|
-
const r = await this.exec(`ls -1A ${shellQuote(abs)}`, { abortSignal });
|
|
905
|
-
if (!r.ok)
|
|
906
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
907
|
-
if (r.value.exitCode !== 0)
|
|
908
|
-
return { ok: false, error: this.fileErrorFrom(r.value.stderr, abs) };
|
|
909
|
-
const names = r.value.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
910
|
-
const out = [];
|
|
911
|
-
for (const name of names) {
|
|
912
|
-
const info = await this.fileInfo(path.posix.join(abs, name), abortSignal);
|
|
913
|
-
out.push(info.ok ? info.value : { name, path: path.posix.join(abs, name), kind: "file", size: 0, mtimeMs: 0 });
|
|
914
|
-
}
|
|
915
|
-
return ok(out);
|
|
905
|
+
return this.posixFs.listDir(p, abortSignal);
|
|
916
906
|
}
|
|
917
907
|
async readLink(p, abortSignal) {
|
|
918
|
-
|
|
919
|
-
const r = await this.exec(`readlink ${shellQuote(abs)}`, { abortSignal });
|
|
920
|
-
if (!r.ok)
|
|
921
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
922
|
-
if (r.value.exitCode !== 0)
|
|
923
|
-
return { ok: false, error: this.fileErrorFrom(r.value.stderr, abs, "not_found") };
|
|
924
|
-
return ok(r.value.stdout.trim());
|
|
908
|
+
return this.posixFs.readLink(p, abortSignal);
|
|
925
909
|
}
|
|
926
910
|
async canonicalPath(p, abortSignal) {
|
|
927
|
-
|
|
928
|
-
const r = await this.exec(`realpath ${shellQuote(abs)}`, { abortSignal });
|
|
929
|
-
if (!r.ok)
|
|
930
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
931
|
-
if (r.value.exitCode !== 0)
|
|
932
|
-
return { ok: false, error: this.fileErrorFrom(r.value.stderr, abs, "not_found") };
|
|
933
|
-
return ok(r.value.stdout.trim());
|
|
911
|
+
return this.posixFs.canonicalPath(p, abortSignal);
|
|
934
912
|
}
|
|
935
913
|
async exists(p, abortSignal) {
|
|
936
|
-
|
|
937
|
-
const r = await this.exec(`{ [ -e ${shellQuote(abs)} ] || [ -L ${shellQuote(abs)} ]; } && printf yes || printf no`, { abortSignal });
|
|
938
|
-
if (!r.ok)
|
|
939
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
940
|
-
const out = r.value.stdout.trim();
|
|
941
|
-
if (out === "yes")
|
|
942
|
-
return ok(true);
|
|
943
|
-
if (out === "no")
|
|
944
|
-
return ok(false);
|
|
945
|
-
return { ok: false, error: this.fileErrorFrom(r.value.stderr, abs) };
|
|
914
|
+
return this.posixFs.exists(p, abortSignal);
|
|
946
915
|
}
|
|
947
916
|
async createDir(p, options) {
|
|
948
|
-
|
|
949
|
-
const r = await this.exec(`mkdir ${options?.recursive === false ? "" : "-p "}${shellQuote(abs)}`, { abortSignal: options?.abortSignal });
|
|
950
|
-
if (!r.ok)
|
|
951
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
952
|
-
if (r.value.exitCode !== 0)
|
|
953
|
-
return { ok: false, error: this.fileErrorFrom(r.value.stderr, abs) };
|
|
954
|
-
return ok(undefined);
|
|
917
|
+
return this.posixFs.createDir(p, options);
|
|
955
918
|
}
|
|
956
919
|
async remove(p, options) {
|
|
957
|
-
|
|
958
|
-
const flags = `${options?.recursive ? "r" : ""}${options?.force ? "f" : ""}`;
|
|
959
|
-
const r = await this.exec(`rm ${flags ? `-${flags}` : ""} ${shellQuote(abs)}`, { abortSignal: options?.abortSignal });
|
|
960
|
-
if (!r.ok)
|
|
961
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
962
|
-
if (r.value.exitCode !== 0) {
|
|
963
|
-
if (options?.force && /no such file/i.test(r.value.stderr))
|
|
964
|
-
return ok(undefined);
|
|
965
|
-
return { ok: false, error: this.fileErrorFrom(r.value.stderr, abs) };
|
|
966
|
-
}
|
|
967
|
-
return ok(undefined);
|
|
920
|
+
return this.posixFs.remove(p, options);
|
|
968
921
|
}
|
|
969
922
|
async createTempDir(prefix = "tmp-", abortSignal) {
|
|
970
|
-
|
|
971
|
-
return this.tempResult(r, "mktemp -d");
|
|
923
|
+
return this.posixFs.createTempDir(prefix, abortSignal);
|
|
972
924
|
}
|
|
973
925
|
async createTempFile(options) {
|
|
974
|
-
|
|
975
|
-
const sfx = options?.suffix ?? "";
|
|
976
|
-
const cmd = sfx
|
|
977
|
-
? `f=$(mktemp -p ${shellQuote(this.cwd)} ${shellQuote(`${prefix}XXXXXX`)}) && mv -- "$f" "$f"${shellQuote(sfx)} && printf '%s' "$f"${shellQuote(sfx)}`
|
|
978
|
-
: `mktemp -p ${shellQuote(this.cwd)} ${shellQuote(`${prefix}XXXXXX`)}`;
|
|
979
|
-
const r = await this.exec(cmd, { abortSignal: options?.abortSignal });
|
|
980
|
-
return this.tempResult(r, "mktemp");
|
|
926
|
+
return this.posixFs.createTempFile(options);
|
|
981
927
|
}
|
|
982
928
|
async execData(command, abortSignal) {
|
|
983
929
|
const c = await this.ensureConnected();
|
|
@@ -996,27 +942,6 @@ export class RemoteK8sExecutionEnv {
|
|
|
996
942
|
parts.push(command);
|
|
997
943
|
return parts.join(" ");
|
|
998
944
|
}
|
|
999
|
-
fileErrorFrom(stderr, p, fallback = "unknown") {
|
|
1000
|
-
const msg = stderr.trim();
|
|
1001
|
-
if (/no such file/i.test(msg))
|
|
1002
|
-
return new FileError("not_found", msg, p);
|
|
1003
|
-
if (/permission denied/i.test(msg))
|
|
1004
|
-
return new FileError("permission_denied", msg, p);
|
|
1005
|
-
if (/is a directory/i.test(msg))
|
|
1006
|
-
return new FileError("is_directory", msg, p);
|
|
1007
|
-
if (/not a directory/i.test(msg))
|
|
1008
|
-
return new FileError("not_directory", msg, p);
|
|
1009
|
-
if (/input\/output error|too many levels|file name too long|no space left/i.test(msg))
|
|
1010
|
-
return new FileError("unknown", msg, p);
|
|
1011
|
-
return new FileError(fallback, msg || "command failed", p);
|
|
1012
|
-
}
|
|
1013
|
-
tempResult(r, label) {
|
|
1014
|
-
if (!r.ok)
|
|
1015
|
-
return { ok: false, error: fileErrorFromExec(r.error, undefined) };
|
|
1016
|
-
if (r.value.exitCode !== 0)
|
|
1017
|
-
return { ok: false, error: new FileError("unknown", r.value.stderr.trim() || `${label} failed`) };
|
|
1018
|
-
return ok(r.value.stdout.trim());
|
|
1019
|
-
}
|
|
1020
945
|
}
|
|
1021
946
|
export function k8sExecutionEnvFactory(config) {
|
|
1022
947
|
return (ctx) => new RemoteK8sExecutionEnv({ ...config, sessionId: ctx.sessionId });
|
|
@@ -90,6 +90,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
90
90
|
}): Promise<Result<string[], FileError>>;
|
|
91
91
|
writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
92
92
|
appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
93
|
+
private posixFsInst?;
|
|
94
|
+
private get posixFs();
|
|
93
95
|
fileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
|
|
94
96
|
listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
|
|
95
97
|
readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
@@ -119,8 +121,6 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
119
121
|
private probeContainerShell;
|
|
120
122
|
private ensureConnectedExec;
|
|
121
123
|
private fsReady;
|
|
122
|
-
private tempResult;
|
|
123
|
-
private fsErrorFromStderr;
|
|
124
124
|
private dockerToRemoteError;
|
|
125
125
|
}
|
|
126
126
|
export declare function localDockerExecutionEnvFactory(config: LocalDockerEnvConfig, deps?: LocalDockerEnvDeps): ExecutionEnvFactory;
|
|
@@ -3,9 +3,10 @@ import os from "node:os";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
|
-
import { shellQuote,
|
|
6
|
+
import { shellQuote, armPipeDestroyGrace } from "./remote-shell.js";
|
|
7
7
|
import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
8
|
-
import { fileErrorFromExec } from "./remote-env-file-error.js";
|
|
8
|
+
import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
|
|
9
|
+
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
9
10
|
const PROVIDER = "local-docker";
|
|
10
11
|
const DEFAULT_MOUNT_PATH = "/workspace";
|
|
11
12
|
const DEFAULT_COMMAND_TIMEOUT_MS = 30 * 60_000;
|
|
@@ -301,7 +302,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
301
302
|
if (!r.ok)
|
|
302
303
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
303
304
|
if (r.value.exitCode !== 0)
|
|
304
|
-
return { ok: false, error:
|
|
305
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs) };
|
|
305
306
|
return ok(r.value.stdoutBytes ?? new Uint8Array());
|
|
306
307
|
}
|
|
307
308
|
async readTextFile(p, abortSignal) {
|
|
@@ -330,7 +331,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
330
331
|
if (!mk.ok)
|
|
331
332
|
return { ok: false, error: fileErrorFromExec(mk.error, dir) };
|
|
332
333
|
if (mk.value.exitCode !== 0)
|
|
333
|
-
return { ok: false, error:
|
|
334
|
+
return { ok: false, error: classifyFsStderr(mk.value.stderr, dir) };
|
|
334
335
|
}
|
|
335
336
|
let local;
|
|
336
337
|
try {
|
|
@@ -340,7 +341,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
340
341
|
if (!cp.ok)
|
|
341
342
|
return { ok: false, error: fileErrorFromExec(cp.error, abs) };
|
|
342
343
|
if (cp.value.exitCode !== 0)
|
|
343
|
-
return { ok: false, error:
|
|
344
|
+
return { ok: false, error: classifyFsStderr(cp.value.stderr, abs) };
|
|
344
345
|
return ok(undefined);
|
|
345
346
|
}
|
|
346
347
|
catch (e) {
|
|
@@ -367,108 +368,49 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
367
368
|
merged.set(add, base.length);
|
|
368
369
|
return this.writeFile(abs, merged, abortSignal);
|
|
369
370
|
}
|
|
371
|
+
posixFsInst;
|
|
372
|
+
get posixFs() {
|
|
373
|
+
return (this.posixFsInst ??= createPosixShellFs({
|
|
374
|
+
run: async (cmd, abortSignal) => {
|
|
375
|
+
if (this.destroyed)
|
|
376
|
+
return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
|
|
377
|
+
if (!this.containerId) {
|
|
378
|
+
const c = await this.connect();
|
|
379
|
+
if (!c.ok)
|
|
380
|
+
return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}`) };
|
|
381
|
+
}
|
|
382
|
+
return this.docker(this.execRaw(cmd), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
383
|
+
},
|
|
384
|
+
resolve: (p) => this.resolve(p),
|
|
385
|
+
tempDir: () => this.cwd,
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
370
388
|
async fileInfo(p, abortSignal) {
|
|
371
|
-
|
|
372
|
-
if (!ready.ok)
|
|
373
|
-
return ready;
|
|
374
|
-
const abs = this.resolve(p);
|
|
375
|
-
const r = await this.docker(this.execRaw(`stat -c '%s %Y %f' ${shellQuote(abs)}`), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
376
|
-
if (!r.ok)
|
|
377
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
378
|
-
if (r.value.exitCode !== 0)
|
|
379
|
-
return { ok: false, error: this.fsErrorFromStderr(r.value.stderr, abs) };
|
|
380
|
-
const [sizeS, mtimeS, modeHex] = r.value.stdout.trim().split(/\s+/);
|
|
381
|
-
return ok({ name: path.posix.basename(abs), path: abs, kind: kindFromMode(parseInt(modeHex ?? "0", 16)), size: Number(sizeS) || 0, mtimeMs: (Number(mtimeS) || 0) * 1000 });
|
|
389
|
+
return this.posixFs.statFileInfo(p, abortSignal);
|
|
382
390
|
}
|
|
383
391
|
async listDir(p, abortSignal) {
|
|
384
|
-
|
|
385
|
-
if (!ready.ok)
|
|
386
|
-
return ready;
|
|
387
|
-
const abs = this.resolve(p);
|
|
388
|
-
const r = await this.docker(this.execRaw(`ls -1A ${shellQuote(abs)}`), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
389
|
-
if (!r.ok)
|
|
390
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
391
|
-
if (r.value.exitCode !== 0)
|
|
392
|
-
return { ok: false, error: this.fsErrorFromStderr(r.value.stderr, abs) };
|
|
393
|
-
const names = r.value.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
394
|
-
const out = [];
|
|
395
|
-
for (const name of names) {
|
|
396
|
-
const info = await this.fileInfo(path.posix.join(abs, name), abortSignal);
|
|
397
|
-
if (info.ok)
|
|
398
|
-
out.push(info.value);
|
|
399
|
-
else
|
|
400
|
-
out.push({ name, path: path.posix.join(abs, name), kind: "file", size: 0, mtimeMs: 0 });
|
|
401
|
-
}
|
|
402
|
-
return ok(out);
|
|
392
|
+
return this.posixFs.listDir(p, abortSignal);
|
|
403
393
|
}
|
|
404
394
|
async readLink(p, abortSignal) {
|
|
405
|
-
|
|
406
|
-
const r = await this.docker(this.execRaw(`readlink ${shellQuote(abs)}`), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
407
|
-
if (!r.ok)
|
|
408
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
409
|
-
if (r.value.exitCode !== 0)
|
|
410
|
-
return { ok: false, error: this.fsErrorFromStderr(r.value.stderr, abs) };
|
|
411
|
-
return ok(r.value.stdout.trim());
|
|
395
|
+
return this.posixFs.readLink(p, abortSignal);
|
|
412
396
|
}
|
|
413
397
|
async canonicalPath(p, abortSignal) {
|
|
414
|
-
|
|
415
|
-
const r = await this.docker(this.execRaw(`realpath ${shellQuote(abs)}`), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
416
|
-
if (!r.ok)
|
|
417
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
418
|
-
if (r.value.exitCode !== 0)
|
|
419
|
-
return { ok: false, error: this.fsErrorFromStderr(r.value.stderr, abs) };
|
|
420
|
-
return ok(r.value.stdout.trim());
|
|
398
|
+
return this.posixFs.canonicalPath(p, abortSignal);
|
|
421
399
|
}
|
|
422
400
|
async exists(p, abortSignal) {
|
|
423
|
-
|
|
424
|
-
if (info.ok)
|
|
425
|
-
return ok(true);
|
|
426
|
-
if (info.error.code === "not_found")
|
|
427
|
-
return ok(false);
|
|
428
|
-
return { ok: false, error: info.error };
|
|
401
|
+
return this.posixFs.exists(p, abortSignal);
|
|
429
402
|
}
|
|
430
403
|
async createDir(p, options) {
|
|
431
|
-
|
|
432
|
-
if (!ready.ok)
|
|
433
|
-
return ready;
|
|
434
|
-
const abs = this.resolve(p);
|
|
435
|
-
const recursive = options?.recursive ?? true;
|
|
436
|
-
const r = await this.docker(this.execRaw(`mkdir ${recursive ? "-p " : ""}${shellQuote(abs)}`), { signal: options?.abortSignal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
437
|
-
if (!r.ok)
|
|
438
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
439
|
-
if (r.value.exitCode !== 0)
|
|
440
|
-
return { ok: false, error: this.fsErrorFromStderr(r.value.stderr, abs) };
|
|
441
|
-
return ok(undefined);
|
|
404
|
+
return this.posixFs.createDir(p, options);
|
|
442
405
|
}
|
|
443
406
|
async remove(p, options) {
|
|
444
|
-
|
|
445
|
-
if (!ready.ok)
|
|
446
|
-
return ready;
|
|
447
|
-
const abs = this.resolve(p);
|
|
448
|
-
const flags = `${options?.recursive ? "r" : ""}${options?.force ? "f" : ""}`;
|
|
449
|
-
const r = await this.docker(this.execRaw(`rm ${flags ? `-${flags} ` : ""}-- ${shellQuote(abs)}`), { signal: options?.abortSignal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
450
|
-
if (!r.ok)
|
|
451
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
452
|
-
if (r.value.exitCode !== 0) {
|
|
453
|
-
const fe = this.fsErrorFromStderr(r.value.stderr, abs);
|
|
454
|
-
if (options?.force && fe.code === "not_found")
|
|
455
|
-
return ok(undefined);
|
|
456
|
-
return { ok: false, error: fe };
|
|
457
|
-
}
|
|
458
|
-
return ok(undefined);
|
|
407
|
+
return this.posixFs.remove(p, options);
|
|
459
408
|
}
|
|
460
409
|
async createTempDir(prefix = "tmp-", abortSignal) {
|
|
461
|
-
|
|
462
|
-
return this.tempResult(r);
|
|
410
|
+
return this.posixFs.createTempDir(prefix, abortSignal);
|
|
463
411
|
}
|
|
464
412
|
async createTempFile(options) {
|
|
465
|
-
|
|
466
|
-
const sfx = options?.suffix ?? "";
|
|
467
|
-
const cmd = sfx
|
|
468
|
-
? `f=$(mktemp -p ${shellQuote(this.cwd)} ${shellQuote(`${prefix}XXXXXX`)}) && mv -- "$f" "$f"${shellQuote(sfx)} && printf '%s' "$f"${shellQuote(sfx)}`
|
|
469
|
-
: `mktemp -p ${shellQuote(this.cwd)} ${shellQuote(`${prefix}XXXXXX`)}`;
|
|
470
|
-
const r = await this.docker(this.execRaw(cmd), { signal: options?.abortSignal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
471
|
-
return this.tempResult(r);
|
|
413
|
+
return this.posixFs.createTempFile(options);
|
|
472
414
|
}
|
|
473
415
|
withHostFlag(args) {
|
|
474
416
|
return this.cfg.dockerHost ? ["-H", this.cfg.dockerHost, ...args] : args;
|
|
@@ -584,25 +526,6 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
584
526
|
return { ok: false, error: new FileError("unknown", `local-docker connect failed: ${c.error.message}`, this.resolve(p), c.error) };
|
|
585
527
|
return ok(undefined);
|
|
586
528
|
}
|
|
587
|
-
tempResult(r) {
|
|
588
|
-
if (!r.ok)
|
|
589
|
-
return { ok: false, error: fileErrorFromExec(r.error, undefined) };
|
|
590
|
-
if (r.value.exitCode !== 0)
|
|
591
|
-
return { ok: false, error: this.fsErrorFromStderr(r.value.stderr, undefined) };
|
|
592
|
-
return ok(r.value.stdout.trim());
|
|
593
|
-
}
|
|
594
|
-
fsErrorFromStderr(stderr, p) {
|
|
595
|
-
const msg = stderr.trim();
|
|
596
|
-
if (/no such file|not found/i.test(msg))
|
|
597
|
-
return new FileError("not_found", msg || "no such file", p);
|
|
598
|
-
if (/permission denied/i.test(msg))
|
|
599
|
-
return new FileError("permission_denied", msg, p);
|
|
600
|
-
if (/is a directory/i.test(msg))
|
|
601
|
-
return new FileError("is_directory", msg, p);
|
|
602
|
-
if (/not a directory/i.test(msg))
|
|
603
|
-
return new FileError("not_directory", msg, p);
|
|
604
|
-
return new FileError("unknown", msg || "command failed", p);
|
|
605
|
-
}
|
|
606
529
|
dockerToRemoteError(e, fallback) {
|
|
607
530
|
if (e.code === "timeout")
|
|
608
531
|
return new RemoteExecutionError("timeout", e.message, e);
|
|
@@ -101,6 +101,8 @@ export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
|
|
|
101
101
|
appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
102
102
|
fileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
|
|
103
103
|
listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
|
|
104
|
+
private posixFsInst?;
|
|
105
|
+
private get posixFs();
|
|
104
106
|
readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
105
107
|
canonicalPath(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
106
108
|
exists(p: string, abortSignal?: AbortSignal): Promise<Result<boolean, FileError>>;
|
|
@@ -120,7 +122,6 @@ export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
|
|
|
120
122
|
abortSignal?: AbortSignal;
|
|
121
123
|
}): Promise<Result<string, FileError>>;
|
|
122
124
|
private withCwdEnv;
|
|
123
|
-
private tempResult;
|
|
124
125
|
private connErr;
|
|
125
126
|
private fileError;
|
|
126
127
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { Client } from "ssh2";
|
|
3
3
|
import { shellQuote, kindFromMode } from "./remote-shell.js";
|
|
4
|
+
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
4
5
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
5
6
|
import { fileErrorFromExec } from "./remote-env-file-error.js";
|
|
6
7
|
const PROVIDER = "ssh";
|
|
@@ -503,29 +504,19 @@ export class RemoteSshExecutionEnv {
|
|
|
503
504
|
return { ok: false, error: this.fileError(e, abs) };
|
|
504
505
|
}
|
|
505
506
|
}
|
|
507
|
+
posixFsInst;
|
|
508
|
+
get posixFs() {
|
|
509
|
+
return (this.posixFsInst ??= createPosixShellFs({
|
|
510
|
+
run: (cmd, abortSignal) => this.exec(cmd, { abortSignal }),
|
|
511
|
+
resolve: (p) => this.resolve(p),
|
|
512
|
+
tempDir: () => null,
|
|
513
|
+
}));
|
|
514
|
+
}
|
|
506
515
|
async readLink(p, abortSignal) {
|
|
507
|
-
|
|
508
|
-
const r = await this.exec(`readlink -- ${shellQuote(abs)}`, { abortSignal });
|
|
509
|
-
if (!r.ok)
|
|
510
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
511
|
-
if (r.value.exitCode !== 0)
|
|
512
|
-
return { ok: false, error: new FileError("not_found", r.value.stderr.trim() || `readlink failed for ${abs}`, abs) };
|
|
513
|
-
return ok(r.value.stdout.trim());
|
|
516
|
+
return this.posixFs.readLink(p, abortSignal);
|
|
514
517
|
}
|
|
515
518
|
async canonicalPath(p, abortSignal) {
|
|
516
|
-
|
|
517
|
-
const r = await this.exec(`realpath -- ${shellQuote(abs)}`, { abortSignal });
|
|
518
|
-
if (!r.ok)
|
|
519
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
520
|
-
if (r.value.exitCode !== 0) {
|
|
521
|
-
const msg = r.value.stderr.trim();
|
|
522
|
-
if (/permission denied|access denied/i.test(msg))
|
|
523
|
-
return { ok: false, error: new FileError("permission_denied", msg, abs) };
|
|
524
|
-
if (/not a directory/i.test(msg))
|
|
525
|
-
return { ok: false, error: new FileError("not_directory", msg, abs) };
|
|
526
|
-
return { ok: false, error: new FileError("not_found", msg || `realpath failed for ${abs}`, abs) };
|
|
527
|
-
}
|
|
528
|
-
return ok(r.value.stdout.trim());
|
|
519
|
+
return this.posixFs.canonicalPath(p, abortSignal);
|
|
529
520
|
}
|
|
530
521
|
async exists(p, abortSignal) {
|
|
531
522
|
const info = await this.fileInfo(p, abortSignal);
|
|
@@ -536,46 +527,16 @@ export class RemoteSshExecutionEnv {
|
|
|
536
527
|
return { ok: false, error: info.error };
|
|
537
528
|
}
|
|
538
529
|
async createDir(p, options) {
|
|
539
|
-
|
|
540
|
-
const r = await this.exec(`mkdir -p -- ${shellQuote(abs)}`, { abortSignal: options?.abortSignal });
|
|
541
|
-
if (!r.ok)
|
|
542
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
543
|
-
if (r.value.exitCode !== 0) {
|
|
544
|
-
const msg = r.value.stderr.trim();
|
|
545
|
-
if (/permission denied/i.test(msg))
|
|
546
|
-
return { ok: false, error: new FileError("permission_denied", msg, abs) };
|
|
547
|
-
return { ok: false, error: new FileError("unknown", msg || `mkdir failed for ${abs}`, abs) };
|
|
548
|
-
}
|
|
549
|
-
return ok(undefined);
|
|
530
|
+
return this.posixFs.createDir(p, options);
|
|
550
531
|
}
|
|
551
532
|
async remove(p, options) {
|
|
552
|
-
|
|
553
|
-
const flags = `${options?.recursive ? "r" : ""}${options?.force ? "f" : ""}`;
|
|
554
|
-
const r = await this.exec(`rm ${flags ? `-${flags}` : ""} -- ${shellQuote(abs)}`, { abortSignal: options?.abortSignal });
|
|
555
|
-
if (!r.ok)
|
|
556
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
557
|
-
if (r.value.exitCode !== 0) {
|
|
558
|
-
const msg = r.value.stderr.trim();
|
|
559
|
-
if (options?.force && /no such file/i.test(msg))
|
|
560
|
-
return ok(undefined);
|
|
561
|
-
if (/no such file/i.test(msg))
|
|
562
|
-
return { ok: false, error: new FileError("not_found", msg, abs) };
|
|
563
|
-
if (/permission denied/i.test(msg))
|
|
564
|
-
return { ok: false, error: new FileError("permission_denied", msg, abs) };
|
|
565
|
-
return { ok: false, error: new FileError("unknown", msg || `rm failed for ${abs}`, abs) };
|
|
566
|
-
}
|
|
567
|
-
return ok(undefined);
|
|
533
|
+
return this.posixFs.remove(p, options);
|
|
568
534
|
}
|
|
569
535
|
async createTempDir(prefix = "tmp-", abortSignal) {
|
|
570
|
-
|
|
571
|
-
return this.tempResult(r, "mktemp -d");
|
|
536
|
+
return this.posixFs.createTempDir(prefix, abortSignal);
|
|
572
537
|
}
|
|
573
538
|
async createTempFile(options) {
|
|
574
|
-
|
|
575
|
-
const suffix = options?.suffix ?? "";
|
|
576
|
-
const suffixArg = suffix ? `--suffix=${shellQuote(suffix)} -t` : "-t";
|
|
577
|
-
const r = await this.exec(`mktemp ${suffixArg} ${shellQuote(`${prefix}XXXXXX`)}`, { abortSignal: options?.abortSignal });
|
|
578
|
-
return this.tempResult(r, "mktemp");
|
|
539
|
+
return this.posixFs.createTempFile(options);
|
|
579
540
|
}
|
|
580
541
|
withCwdEnv(command, cwd, env) {
|
|
581
542
|
const parts = [];
|
|
@@ -588,13 +549,6 @@ export class RemoteSshExecutionEnv {
|
|
|
588
549
|
parts.push(command);
|
|
589
550
|
return parts.join(" ");
|
|
590
551
|
}
|
|
591
|
-
tempResult(r, label) {
|
|
592
|
-
if (!r.ok)
|
|
593
|
-
return { ok: false, error: fileErrorFromExec(r.error, undefined) };
|
|
594
|
-
if (r.value.exitCode !== 0)
|
|
595
|
-
return { ok: false, error: new FileError("unknown", r.value.stderr.trim() || `${label} failed`) };
|
|
596
|
-
return ok(r.value.stdout.trim());
|
|
597
|
-
}
|
|
598
552
|
connErr(e) {
|
|
599
553
|
const msg = e.message || String(e);
|
|
600
554
|
if (e.level === "client-authentication" || /authentication|all configured authentication methods failed/i.test(msg)) {
|
package/dist/question.js
CHANGED
|
@@ -118,10 +118,10 @@ export class QuestionCoordinator {
|
|
|
118
118
|
respond(id, principal, body) {
|
|
119
119
|
const parsed = parseQuestionResponse(body);
|
|
120
120
|
if (!parsed.ok)
|
|
121
|
-
return { status: 400, body: { error: parsed.error } };
|
|
121
|
+
return { status: 400, body: { error: parsed.error, errorCode: "request.body_shape" } };
|
|
122
122
|
const entry = this.pending.get(id);
|
|
123
123
|
if (!entry || (entry.owner !== null && entry.owner !== principal)) {
|
|
124
|
-
return { status: 404, body: { error: "no pending question for this id (answered, expired, or not on this replica)" } };
|
|
124
|
+
return { status: 404, body: { error: "no pending question for this id (answered, expired, or not on this replica)", errorCode: "question.not_pending" } };
|
|
125
125
|
}
|
|
126
126
|
entry.settle(parsed.value, "answered");
|
|
127
127
|
return { status: 200, body: { questionId: id, delivery: "applied" } };
|
package/dist/run-local.js
CHANGED
|
@@ -254,7 +254,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
254
254
|
toolResultStore: fileBackend.toolResultStore,
|
|
255
255
|
...(executionEnvFactory ? { executionEnvFactory } : {}),
|
|
256
256
|
...(lspManager ? { lspManager } : {}),
|
|
257
|
-
...(
|
|
257
|
+
...(config.projectMemoryEnabled
|
|
258
258
|
? { loadProjectMemory: makeLoadProjectMemory({ logger }), probeInstructionSources: makeProbeInstructionSources() }
|
|
259
259
|
: {}),
|
|
260
260
|
});
|
package/dist/tool-approval.js
CHANGED
|
@@ -211,10 +211,10 @@ export class ToolApprovalCoordinator {
|
|
|
211
211
|
respond(id, principal, body) {
|
|
212
212
|
const parsed = parseToolApprovalResponse(body);
|
|
213
213
|
if (!parsed.ok)
|
|
214
|
-
return { status: 400, body: { error: parsed.error } };
|
|
214
|
+
return { status: 400, body: { error: parsed.error, errorCode: "request.body_shape" } };
|
|
215
215
|
const entry = this.pending.get(id);
|
|
216
216
|
if (!entry || (entry.owner !== null && entry.owner !== principal)) {
|
|
217
|
-
return { status: 404, body: { error: "no pending tool approval for this id (settled, expired, or not on this replica)" } };
|
|
217
|
+
return { status: 404, body: { error: "no pending tool approval for this id (settled, expired, or not on this replica)", errorCode: "tool_approval.not_pending" } };
|
|
218
218
|
}
|
|
219
219
|
let rememberApplied;
|
|
220
220
|
if (parsed.value === "allow_session")
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -221,6 +221,7 @@ export interface TaskSummary {
|
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
export declare function runSummary(run: RunRecord, latestEventType?: string): TaskSummary;
|
|
224
|
+
export declare function normalizeRunEventType(type: string): string;
|
|
224
225
|
export declare function mapTraceEvent(type: string, seq: number, data: Record<string, unknown>): {
|
|
225
226
|
event: string;
|
|
226
227
|
data: Record<string, unknown>;
|
package/dist/trace/project.js
CHANGED
|
@@ -299,6 +299,9 @@ export function runSummary(run, latestEventType) {
|
|
|
299
299
|
lastStep: lastStep(latestEventType, run.status),
|
|
300
300
|
};
|
|
301
301
|
}
|
|
302
|
+
export function normalizeRunEventType(type) {
|
|
303
|
+
return type === "brain_status" ? "status" : type;
|
|
304
|
+
}
|
|
302
305
|
export function mapTraceEvent(type, seq, data) {
|
|
303
306
|
switch (type) {
|
|
304
307
|
case "reasoning":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.318.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|