@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
package/dist/http/sse-log.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { sendJson, sseHeaders } from "./send.js";
|
|
1
|
+
import { sendJson, sendError, sseHeaders } from "./send.js";
|
|
2
2
|
export async function streamSseLog(req, res, provider, id, staleMs) {
|
|
3
3
|
const lastId = Number(req.headers["last-event-id"] ?? new URL(req.url ?? "", "http://x").searchParams.get("from") ?? 0);
|
|
4
4
|
let from = Number.isFinite(lastId) ? lastId : 0;
|
|
5
5
|
if (from > 0) {
|
|
6
6
|
const retainedFrom = await provider.retainedFrom(id);
|
|
7
7
|
if (retainedFrom > from + 1) {
|
|
8
|
-
|
|
8
|
+
sendError(res, 416, "limit.retention_evicted", "resume point evicted past retention", { retainedFrom });
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -81,4 +81,10 @@ export interface TaskRequestBody {
|
|
|
81
81
|
enableFork?: boolean;
|
|
82
82
|
[k: string]: unknown;
|
|
83
83
|
}
|
|
84
|
+
export type DecideBinding = {
|
|
85
|
+
checkpointToken?: string;
|
|
86
|
+
boundCallId?: string;
|
|
87
|
+
boundInputHash?: string;
|
|
88
|
+
updatedInput?: unknown;
|
|
89
|
+
};
|
|
84
90
|
//# sourceMappingURL=wire-types.d.ts.map
|
package/dist/leader/endpoint.js
CHANGED
|
@@ -27,12 +27,12 @@ export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
|
27
27
|
if (method === "POST" && url === "/v1/leader") {
|
|
28
28
|
const b = body;
|
|
29
29
|
if (!b || typeof b.objective !== "string" || b.objective.trim() === "") {
|
|
30
|
-
return { status: 400, body: { error: "missing 'objective' string" } };
|
|
30
|
+
return { status: 400, body: { error: "missing 'objective' string", errorCode: "request.body_shape" } };
|
|
31
31
|
}
|
|
32
32
|
if (opts.requiredFields) {
|
|
33
33
|
const missing = opts.requiredFields.filter((f) => typeof b[f] !== "string" || b[f].trim() === "");
|
|
34
34
|
if (missing.length > 0) {
|
|
35
|
-
return { status: 400, body: { error: `missing required field(s): ${missing.join(", ")}
|
|
35
|
+
return { status: 400, body: { error: `missing required field(s): ${missing.join(", ")}`, errorCode: "request.body_shape" } };
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
reap();
|
|
@@ -61,9 +61,9 @@ export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
|
61
61
|
if (m) {
|
|
62
62
|
const run = runs.get(m[1]);
|
|
63
63
|
if (!run)
|
|
64
|
-
return { status: 404, body: { error: "leader run not found" } };
|
|
64
|
+
return { status: 404, body: { error: "leader run not found", errorCode: "not_found.leader_run" } };
|
|
65
65
|
if (run.owner != null && run.owner !== (requester ?? null)) {
|
|
66
|
-
return { status: 404, body: { error: "leader run not found" } };
|
|
66
|
+
return { status: 404, body: { error: "leader run not found", errorCode: "not_found.leader_run" } };
|
|
67
67
|
}
|
|
68
68
|
return {
|
|
69
69
|
status: 200,
|
package/dist/main.js
CHANGED
|
@@ -24,7 +24,7 @@ import { stat as fsStat, readFile as fsReadFile } from "node:fs/promises";
|
|
|
24
24
|
import { acceptShellScratchpadDir, buildEnvFacts, egressForRemoteExec, ensureScratchpadDir, purgeScratchpadDir, sweepStaleScratchpads, resumeFactsForLane } from "./env-facts.js";
|
|
25
25
|
import { normalizeSuggestNextPrompts, normalizeResilience, normalizeAttachments, normalizeResumeAtMode, resolveTaskLimits, taskAgentsSpecFragment, retainBackgroundProcessesFromBody, toolNameListFromBody, promptProfileFromBody } from "./spec-fields.js";
|
|
26
26
|
import { createBrain, brainSummary } from "./brain.js";
|
|
27
|
-
import { loadConfig, logConfigDiagnostics, resolveBindHost } from "./config.js";
|
|
27
|
+
import { configLkgEnabled, loadConfig, logConfigDiagnostics, resolveBindHost } from "./config.js";
|
|
28
28
|
import { resourceSuspendOptIn } from "./resource-suspend.js";
|
|
29
29
|
import { createSessionStore, ensureChildSessionDurableWithPromotion } from "./plugins/session-store.js";
|
|
30
30
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
@@ -247,7 +247,7 @@ async function main() {
|
|
|
247
247
|
}
|
|
248
248
|
};
|
|
249
249
|
let bootConfigPending;
|
|
250
|
-
const lkgEnabled = configProvider?.kind === "remote" &&
|
|
250
|
+
const lkgEnabled = configProvider?.kind === "remote" && configLkgEnabled();
|
|
251
251
|
const lkgPath = process.env.CONFIG_LKG_PATH ?? defaultLkgPath(cc?.worker);
|
|
252
252
|
const lkgSurvivesRestart = lkgEnabled && process.env.CONFIG_LKG_DURABLE === "true";
|
|
253
253
|
let lkgBooted = false;
|
|
@@ -1089,8 +1089,8 @@ async function main() {
|
|
|
1089
1089
|
executionEnvFactory: executionEnvFactory ? executionEnvFactory : undefined,
|
|
1090
1090
|
lspManager: lspManager ? lspManager : undefined,
|
|
1091
1091
|
onBackgroundChildEvent: fleetBackgroundChildPublisher(fleetBus, (msg, fields) => logger.info(msg, fields)),
|
|
1092
|
-
loadProjectMemory: config.requirePrincipal !== true &&
|
|
1093
|
-
probeInstructionSources: config.remoteExec?.provider === "host" && config.requirePrincipal !== true &&
|
|
1092
|
+
loadProjectMemory: config.requirePrincipal !== true && config.projectMemoryEnabled ? makeLoadProjectMemory({ logger }) : undefined,
|
|
1093
|
+
probeInstructionSources: config.remoteExec?.provider === "host" && config.requirePrincipal !== true && config.projectMemoryEnabled
|
|
1094
1094
|
? makeProbeInstructionSources()
|
|
1095
1095
|
: undefined,
|
|
1096
1096
|
hooks: deploymentHooks,
|
|
@@ -1266,7 +1266,7 @@ async function main() {
|
|
|
1266
1266
|
: undefined;
|
|
1267
1267
|
let skills = loadSkills(config.skillsDir);
|
|
1268
1268
|
if (effective?.skills && config.configCenter) {
|
|
1269
|
-
skills = await applyCenterSkills(skills, effective.skills, config.configCenter.baseUrl, config.configCenter.token, logger, undefined,
|
|
1269
|
+
skills = await applyCenterSkills(skills, effective.skills, config.configCenter.baseUrl, config.configCenter.token, logger, undefined, configLkgEnabled() ? defaultSkillCacheDir() : undefined);
|
|
1270
1270
|
}
|
|
1271
1271
|
if (effective?.plugins && config.configCenter) {
|
|
1272
1272
|
const pluginOut = await applyCenterPlugins(skills, effective, {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FileError, type ExecutionError, type RemoteExecutionError, type FileInfo, type Result } from "@sema-agent/core";
|
|
2
|
+
export interface ShellRunResult {
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
exitCode: number;
|
|
6
|
+
}
|
|
7
|
+
export interface PosixShellFsDeps {
|
|
8
|
+
run: (cmd: string, abortSignal?: AbortSignal) => Promise<Result<ShellRunResult, ExecutionError | RemoteExecutionError>>;
|
|
9
|
+
resolve: (p: string) => string;
|
|
10
|
+
tempDir: () => string | null;
|
|
11
|
+
}
|
|
12
|
+
export interface PosixShellFs {
|
|
13
|
+
readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
14
|
+
canonicalPath(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
15
|
+
statFileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
|
|
16
|
+
listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
|
|
17
|
+
exists(p: string, abortSignal?: AbortSignal): Promise<Result<boolean, FileError>>;
|
|
18
|
+
createDir(p: string, options?: {
|
|
19
|
+
recursive?: boolean;
|
|
20
|
+
abortSignal?: AbortSignal;
|
|
21
|
+
}): Promise<Result<void, FileError>>;
|
|
22
|
+
remove(p: string, options?: {
|
|
23
|
+
recursive?: boolean;
|
|
24
|
+
force?: boolean;
|
|
25
|
+
abortSignal?: AbortSignal;
|
|
26
|
+
}): Promise<Result<void, FileError>>;
|
|
27
|
+
createTempDir(prefix?: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
28
|
+
createTempFile(options?: {
|
|
29
|
+
prefix?: string;
|
|
30
|
+
suffix?: string;
|
|
31
|
+
abortSignal?: AbortSignal;
|
|
32
|
+
}): Promise<Result<string, FileError>>;
|
|
33
|
+
}
|
|
34
|
+
export declare function createPosixShellFs(deps: PosixShellFsDeps): PosixShellFs;
|
|
35
|
+
//# sourceMappingURL=posix-shell-fs.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { FileError, } from "@sema-agent/core";
|
|
3
|
+
import { shellQuote, kindFromMode } from "./remote-shell.js";
|
|
4
|
+
import { classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
|
|
5
|
+
const ok = (value) => ({ ok: true, value });
|
|
6
|
+
export function createPosixShellFs(deps) {
|
|
7
|
+
const { run, resolve } = deps;
|
|
8
|
+
const tempResult = (r) => {
|
|
9
|
+
if (!r.ok)
|
|
10
|
+
return { ok: false, error: fileErrorFromExec(r.error, undefined) };
|
|
11
|
+
if (r.value.exitCode !== 0)
|
|
12
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, undefined, "unknown") };
|
|
13
|
+
return ok(r.value.stdout.trim());
|
|
14
|
+
};
|
|
15
|
+
const fs = {
|
|
16
|
+
async readLink(p, abortSignal) {
|
|
17
|
+
const abs = resolve(p);
|
|
18
|
+
const r = await run(`readlink -- ${shellQuote(abs)}`, abortSignal);
|
|
19
|
+
if (!r.ok)
|
|
20
|
+
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
21
|
+
if (r.value.exitCode !== 0)
|
|
22
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "not_found") };
|
|
23
|
+
return ok(r.value.stdout.trim());
|
|
24
|
+
},
|
|
25
|
+
async canonicalPath(p, abortSignal) {
|
|
26
|
+
const abs = resolve(p);
|
|
27
|
+
const r = await run(`realpath -- ${shellQuote(abs)}`, abortSignal);
|
|
28
|
+
if (!r.ok)
|
|
29
|
+
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
30
|
+
if (r.value.exitCode !== 0)
|
|
31
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "not_found") };
|
|
32
|
+
return ok(r.value.stdout.trim());
|
|
33
|
+
},
|
|
34
|
+
async statFileInfo(p, abortSignal) {
|
|
35
|
+
const abs = resolve(p);
|
|
36
|
+
const r = await run(`stat -c '%s %Y %f' -- ${shellQuote(abs)}`, abortSignal);
|
|
37
|
+
if (!r.ok)
|
|
38
|
+
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
39
|
+
if (r.value.exitCode !== 0)
|
|
40
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "not_found") };
|
|
41
|
+
const [sizeS, mtimeS, modeHex] = r.value.stdout.trim().split(/\s+/);
|
|
42
|
+
return ok({
|
|
43
|
+
name: path.posix.basename(abs),
|
|
44
|
+
path: abs,
|
|
45
|
+
kind: kindFromMode(parseInt(modeHex ?? "0", 16)),
|
|
46
|
+
size: Number(sizeS) || 0,
|
|
47
|
+
mtimeMs: (Number(mtimeS) || 0) * 1000,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
async listDir(p, abortSignal) {
|
|
51
|
+
const abs = resolve(p);
|
|
52
|
+
const r = await run(`ls -1A -- ${shellQuote(abs)}`, abortSignal);
|
|
53
|
+
if (!r.ok)
|
|
54
|
+
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
55
|
+
if (r.value.exitCode !== 0)
|
|
56
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "not_found") };
|
|
57
|
+
const names = r.value.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const name of names) {
|
|
60
|
+
const info = await fs.statFileInfo(path.posix.join(abs, name), abortSignal);
|
|
61
|
+
if (info.ok)
|
|
62
|
+
out.push(info.value);
|
|
63
|
+
else
|
|
64
|
+
out.push({ name, path: path.posix.join(abs, name), kind: "file", size: 0, mtimeMs: 0 });
|
|
65
|
+
}
|
|
66
|
+
return ok(out);
|
|
67
|
+
},
|
|
68
|
+
async exists(p, abortSignal) {
|
|
69
|
+
const abs = resolve(p);
|
|
70
|
+
const r = await run(`{ [ -e ${shellQuote(abs)} ] || [ -L ${shellQuote(abs)} ]; } && printf yes || printf no`, abortSignal);
|
|
71
|
+
if (!r.ok)
|
|
72
|
+
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
73
|
+
const out = r.value.stdout.trim();
|
|
74
|
+
if (out === "yes")
|
|
75
|
+
return ok(true);
|
|
76
|
+
if (out === "no")
|
|
77
|
+
return ok(false);
|
|
78
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "unknown") };
|
|
79
|
+
},
|
|
80
|
+
async createDir(p, options) {
|
|
81
|
+
const abs = resolve(p);
|
|
82
|
+
const r = await run(`mkdir -p -- ${shellQuote(abs)}`, options?.abortSignal);
|
|
83
|
+
if (!r.ok)
|
|
84
|
+
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
85
|
+
if (r.value.exitCode !== 0)
|
|
86
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "unknown") };
|
|
87
|
+
return ok(undefined);
|
|
88
|
+
},
|
|
89
|
+
async remove(p, options) {
|
|
90
|
+
const abs = resolve(p);
|
|
91
|
+
const flags = `${options?.recursive ? "r" : ""}${options?.force ? "f" : ""}`;
|
|
92
|
+
const r = await run(`rm ${flags ? `-${flags} ` : ""}-- ${shellQuote(abs)}`, options?.abortSignal);
|
|
93
|
+
if (!r.ok)
|
|
94
|
+
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
95
|
+
if (r.value.exitCode !== 0) {
|
|
96
|
+
if (options?.force && /no such file/i.test(r.value.stderr))
|
|
97
|
+
return ok(undefined);
|
|
98
|
+
return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "unknown") };
|
|
99
|
+
}
|
|
100
|
+
return ok(undefined);
|
|
101
|
+
},
|
|
102
|
+
async createTempDir(prefix = "tmp-", abortSignal) {
|
|
103
|
+
const dir = deps.tempDir();
|
|
104
|
+
const where = dir === null ? "-t" : `-p ${shellQuote(dir)}`;
|
|
105
|
+
const r = await run(`mktemp -d ${where} ${shellQuote(`${prefix}XXXXXX`)}`, abortSignal);
|
|
106
|
+
return tempResult(r);
|
|
107
|
+
},
|
|
108
|
+
async createTempFile(options) {
|
|
109
|
+
const prefix = options?.prefix ?? "";
|
|
110
|
+
const sfx = options?.suffix ?? "";
|
|
111
|
+
const dir = deps.tempDir();
|
|
112
|
+
const where = dir === null ? "-t" : `-p ${shellQuote(dir)}`;
|
|
113
|
+
const cmd = sfx
|
|
114
|
+
? `f=$(mktemp ${where} ${shellQuote(`${prefix}XXXXXX`)}) && mv -- "$f" "$f"${shellQuote(sfx)} && printf '%s' "$f"${shellQuote(sfx)}`
|
|
115
|
+
: `mktemp ${where} ${shellQuote(`${prefix}XXXXXX`)}`;
|
|
116
|
+
const r = await run(cmd, options?.abortSignal);
|
|
117
|
+
return tempResult(r);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
return fs;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=posix-shell-fs.js.map
|
|
@@ -93,6 +93,8 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
|
|
|
93
93
|
}): Promise<Result<string[], FileError>>;
|
|
94
94
|
writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
95
95
|
appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
96
|
+
private posixFsInst?;
|
|
97
|
+
private get posixFs();
|
|
96
98
|
fileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
|
|
97
99
|
listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
|
|
98
100
|
readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
@@ -115,7 +117,6 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
|
|
|
115
117
|
}): Promise<Result<string, FileError>>;
|
|
116
118
|
private adb;
|
|
117
119
|
private withCwdEnv;
|
|
118
|
-
private tempResult;
|
|
119
120
|
}
|
|
120
121
|
export declare function adbExecutionEnvFactory(config: AdbEnvConfig): ExecutionEnvFactory;
|
|
121
122
|
export {};
|
|
@@ -2,9 +2,10 @@ import path from "node:path";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { shellQuote,
|
|
5
|
+
import { shellQuote, armPipeDestroyGrace } from "./remote-shell.js";
|
|
6
6
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
7
7
|
import { fileErrorFromExec } from "./remote-env-file-error.js";
|
|
8
|
+
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
8
9
|
const PROVIDER = "adb";
|
|
9
10
|
const ok = (value) => ({ ok: true, value });
|
|
10
11
|
const unsupported = (op) => ({
|
|
@@ -320,123 +321,40 @@ export class RemoteAdbExecutionEnv {
|
|
|
320
321
|
merged.set(add, base.length);
|
|
321
322
|
return this.writeFile(abs, merged, abortSignal);
|
|
322
323
|
}
|
|
324
|
+
posixFsInst;
|
|
325
|
+
get posixFs() {
|
|
326
|
+
return (this.posixFsInst ??= createPosixShellFs({
|
|
327
|
+
run: (cmd, abortSignal) => this.exec(cmd, { abortSignal }),
|
|
328
|
+
resolve: (p) => this.resolve(p),
|
|
329
|
+
tempDir: () => this.cwd,
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
323
332
|
async fileInfo(p, abortSignal) {
|
|
324
|
-
|
|
325
|
-
const r = await this.exec(`stat -c '%s %Y %f' ${shellQuote(abs)}`, { abortSignal });
|
|
326
|
-
if (!r.ok)
|
|
327
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
328
|
-
if (r.value.exitCode !== 0) {
|
|
329
|
-
const msg = r.value.stderr.trim();
|
|
330
|
-
if (/no such file/i.test(msg))
|
|
331
|
-
return { ok: false, error: new FileError("not_found", msg, abs) };
|
|
332
|
-
if (/permission denied/i.test(msg))
|
|
333
|
-
return { ok: false, error: new FileError("permission_denied", msg, abs) };
|
|
334
|
-
return { ok: false, error: new FileError("unknown", msg || `stat failed for ${abs}`, abs) };
|
|
335
|
-
}
|
|
336
|
-
const [sizeS, mtimeS, modeHex] = r.value.stdout.trim().split(/\s+/);
|
|
337
|
-
return ok({ name: path.posix.basename(abs), path: abs, kind: kindFromMode(parseInt(modeHex ?? "0", 16)), size: Number(sizeS) || 0, mtimeMs: (Number(mtimeS) || 0) * 1000 });
|
|
333
|
+
return this.posixFs.statFileInfo(p, abortSignal);
|
|
338
334
|
}
|
|
339
335
|
async listDir(p, abortSignal) {
|
|
340
|
-
|
|
341
|
-
const r = await this.exec(`ls -1 ${shellQuote(abs)}`, { abortSignal });
|
|
342
|
-
if (!r.ok)
|
|
343
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
344
|
-
if (r.value.exitCode !== 0) {
|
|
345
|
-
const msg = r.value.stderr.trim();
|
|
346
|
-
if (/no such file/i.test(msg))
|
|
347
|
-
return { ok: false, error: new FileError("not_found", msg, abs) };
|
|
348
|
-
if (/not a directory/i.test(msg))
|
|
349
|
-
return { ok: false, error: new FileError("not_directory", msg, abs) };
|
|
350
|
-
if (/permission denied/i.test(msg))
|
|
351
|
-
return { ok: false, error: new FileError("permission_denied", msg, abs) };
|
|
352
|
-
return { ok: false, error: new FileError("unknown", msg || `ls failed for ${abs}`, abs) };
|
|
353
|
-
}
|
|
354
|
-
const names = r.value.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
355
|
-
const out = [];
|
|
356
|
-
for (const name of names) {
|
|
357
|
-
const info = await this.fileInfo(path.posix.join(abs, name), abortSignal);
|
|
358
|
-
if (info.ok)
|
|
359
|
-
out.push(info.value);
|
|
360
|
-
else
|
|
361
|
-
out.push({ name, path: path.posix.join(abs, name), kind: "file", size: 0, mtimeMs: 0 });
|
|
362
|
-
}
|
|
363
|
-
return ok(out);
|
|
336
|
+
return this.posixFs.listDir(p, abortSignal);
|
|
364
337
|
}
|
|
365
338
|
async readLink(p, abortSignal) {
|
|
366
|
-
|
|
367
|
-
const r = await this.exec(`readlink ${shellQuote(abs)}`, { abortSignal });
|
|
368
|
-
if (!r.ok)
|
|
369
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
370
|
-
if (r.value.exitCode !== 0)
|
|
371
|
-
return { ok: false, error: new FileError("not_found", r.value.stderr.trim() || `readlink failed for ${abs}`, abs) };
|
|
372
|
-
return ok(r.value.stdout.trim());
|
|
339
|
+
return this.posixFs.readLink(p, abortSignal);
|
|
373
340
|
}
|
|
374
341
|
async canonicalPath(p, abortSignal) {
|
|
375
|
-
|
|
376
|
-
const r = await this.exec(`realpath ${shellQuote(abs)}`, { abortSignal });
|
|
377
|
-
if (!r.ok)
|
|
378
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
379
|
-
if (r.value.exitCode !== 0) {
|
|
380
|
-
const msg = r.value.stderr.trim();
|
|
381
|
-
if (/permission denied/i.test(msg))
|
|
382
|
-
return { ok: false, error: new FileError("permission_denied", msg, abs) };
|
|
383
|
-
if (/not a directory/i.test(msg))
|
|
384
|
-
return { ok: false, error: new FileError("not_directory", msg, abs) };
|
|
385
|
-
return { ok: false, error: new FileError("not_found", msg || `realpath failed for ${abs}`, abs) };
|
|
386
|
-
}
|
|
387
|
-
return ok(r.value.stdout.trim());
|
|
342
|
+
return this.posixFs.canonicalPath(p, abortSignal);
|
|
388
343
|
}
|
|
389
344
|
async exists(p, abortSignal) {
|
|
390
|
-
|
|
391
|
-
if (info.ok)
|
|
392
|
-
return ok(true);
|
|
393
|
-
if (info.error.code === "not_found")
|
|
394
|
-
return ok(false);
|
|
395
|
-
return { ok: false, error: info.error };
|
|
345
|
+
return this.posixFs.exists(p, abortSignal);
|
|
396
346
|
}
|
|
397
347
|
async createDir(p, options) {
|
|
398
|
-
|
|
399
|
-
const r = await this.exec(`mkdir -p ${shellQuote(abs)}`, { abortSignal: options?.abortSignal });
|
|
400
|
-
if (!r.ok)
|
|
401
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
402
|
-
if (r.value.exitCode !== 0) {
|
|
403
|
-
const msg = r.value.stderr.trim();
|
|
404
|
-
if (/permission denied/i.test(msg))
|
|
405
|
-
return { ok: false, error: new FileError("permission_denied", msg, abs) };
|
|
406
|
-
return { ok: false, error: new FileError("unknown", msg || `mkdir failed for ${abs}`, abs) };
|
|
407
|
-
}
|
|
408
|
-
return ok(undefined);
|
|
348
|
+
return this.posixFs.createDir(p, options);
|
|
409
349
|
}
|
|
410
350
|
async remove(p, options) {
|
|
411
|
-
|
|
412
|
-
const flags = `${options?.recursive ? "r" : ""}${options?.force ? "f" : ""}`;
|
|
413
|
-
const r = await this.exec(`rm ${flags ? `-${flags}` : ""} ${shellQuote(abs)}`, { abortSignal: options?.abortSignal });
|
|
414
|
-
if (!r.ok)
|
|
415
|
-
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
416
|
-
if (r.value.exitCode !== 0) {
|
|
417
|
-
const msg = r.value.stderr.trim();
|
|
418
|
-
if (options?.force && /no such file/i.test(msg))
|
|
419
|
-
return ok(undefined);
|
|
420
|
-
if (/no such file/i.test(msg))
|
|
421
|
-
return { ok: false, error: new FileError("not_found", msg, abs) };
|
|
422
|
-
if (/permission denied/i.test(msg))
|
|
423
|
-
return { ok: false, error: new FileError("permission_denied", msg, abs) };
|
|
424
|
-
return { ok: false, error: new FileError("unknown", msg || `rm failed for ${abs}`, abs) };
|
|
425
|
-
}
|
|
426
|
-
return ok(undefined);
|
|
351
|
+
return this.posixFs.remove(p, options);
|
|
427
352
|
}
|
|
428
353
|
async createTempDir(prefix = "tmp-", abortSignal) {
|
|
429
|
-
|
|
430
|
-
return this.tempResult(r, "mktemp -d");
|
|
354
|
+
return this.posixFs.createTempDir(prefix, abortSignal);
|
|
431
355
|
}
|
|
432
356
|
async createTempFile(options) {
|
|
433
|
-
|
|
434
|
-
const sfx = options?.suffix ?? "";
|
|
435
|
-
const cmd = sfx
|
|
436
|
-
? `f=$(mktemp -p ${shellQuote(this.cwd)} ${shellQuote(`${prefix}XXXXXX`)}) && mv -- "$f" "$f"${shellQuote(sfx)} && printf '%s' "$f"${shellQuote(sfx)}`
|
|
437
|
-
: `mktemp -p ${shellQuote(this.cwd)} ${shellQuote(`${prefix}XXXXXX`)}`;
|
|
438
|
-
const r = await this.exec(cmd, { abortSignal: options?.abortSignal });
|
|
439
|
-
return this.tempResult(r, "mktemp");
|
|
357
|
+
return this.posixFs.createTempFile(options);
|
|
440
358
|
}
|
|
441
359
|
adb(args, opts) {
|
|
442
360
|
return new Promise((resolve) => {
|
|
@@ -515,13 +433,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
515
433
|
parts.push(command);
|
|
516
434
|
return parts.join(" ");
|
|
517
435
|
}
|
|
518
|
-
tempResult(r, label) {
|
|
519
|
-
if (!r.ok)
|
|
520
|
-
return { ok: false, error: fileErrorFromExec(r.error, undefined) };
|
|
521
|
-
if (r.value.exitCode !== 0)
|
|
522
|
-
return { ok: false, error: new FileError("unknown", r.value.stderr.trim() || `${label} failed`) };
|
|
523
|
-
return ok(r.value.stdout.trim());
|
|
524
|
-
}
|
|
525
436
|
}
|
|
526
437
|
export function adbExecutionEnvFactory(config) {
|
|
527
438
|
return (_ctx) => new RemoteAdbExecutionEnv(config);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { FileError, type ExecutionError, type RemoteExecutionError } from "@sema-agent/core";
|
|
2
2
|
export declare function fileErrorFromExec(e: ExecutionError | RemoteExecutionError, path?: string): FileError;
|
|
3
|
+
export declare function classifyFsStderr(stderr: string, p: string | undefined, fallback?: "unknown" | "not_found"): FileError;
|
|
3
4
|
//# sourceMappingURL=remote-env-file-error.d.ts.map
|
|
@@ -3,4 +3,20 @@ export function fileErrorFromExec(e, path) {
|
|
|
3
3
|
const code = e.code === "aborted" ? "aborted" : "unknown";
|
|
4
4
|
return new FileError(code, e.message, path, e);
|
|
5
5
|
}
|
|
6
|
+
export function classifyFsStderr(stderr, p, fallback = "unknown") {
|
|
7
|
+
const msg = stderr.trim();
|
|
8
|
+
if (/command not found|applet not found|:\s+not found\s*$/im.test(msg))
|
|
9
|
+
return new FileError("unknown", msg, p);
|
|
10
|
+
if (/input\/output error|too many levels|file name too long|no space left/i.test(msg))
|
|
11
|
+
return new FileError("unknown", msg, p);
|
|
12
|
+
if (/no such file/i.test(msg))
|
|
13
|
+
return new FileError("not_found", msg, p);
|
|
14
|
+
if (/permission denied/i.test(msg))
|
|
15
|
+
return new FileError("permission_denied", msg, p);
|
|
16
|
+
if (/is a directory/i.test(msg))
|
|
17
|
+
return new FileError("is_directory", msg, p);
|
|
18
|
+
if (/not a directory/i.test(msg))
|
|
19
|
+
return new FileError("not_directory", msg, p);
|
|
20
|
+
return new FileError(fallback, msg || "command failed", p);
|
|
21
|
+
}
|
|
6
22
|
//# sourceMappingURL=remote-env-file-error.js.map
|
|
@@ -6,6 +6,7 @@ import { randomBytes } from "node:crypto";
|
|
|
6
6
|
import { openSync, closeSync, readSync, statSync, truncateSync, unlinkSync, mkdirSync, rmdirSync, mkdtempSync, existsSync, realpathSync } from "node:fs";
|
|
7
7
|
import { StringDecoder } from "node:string_decoder";
|
|
8
8
|
import { armPipeDestroyGrace } from "./remote-shell.js";
|
|
9
|
+
import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
|
|
9
10
|
import { resolveHostShell, hostShell, spawnGroupOptions, killTreeHard, killTreeSoft, collapseWin32EnvKeys } from "./host-platform.js";
|
|
10
11
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
11
12
|
import { FileError, ExecutionError, RemoteExecutionError, scrubSecretEnv, RollingTailBuffer, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
|
|
@@ -149,11 +150,11 @@ export class RemoteHostExecutionEnv {
|
|
|
149
150
|
}
|
|
150
151
|
get backgroundCapabilities() {
|
|
151
152
|
return {
|
|
152
|
-
supported: (this.cfg.backgroundShell ?? true) &&
|
|
153
|
+
supported: (this.cfg.backgroundShell ?? true) && hostBackgroundShellEnabled(),
|
|
153
154
|
maxConcurrent: HOST_BG_MAX_CONCURRENT,
|
|
154
155
|
defaultBgTimeoutSec: HOST_BG_DEFAULT_TIMEOUT_SEC,
|
|
155
156
|
maxBgTimeoutSec: HOST_BG_MAX_TIMEOUT_SEC,
|
|
156
|
-
supportsDetach: (this.cfg.backgroundShell ?? true) &&
|
|
157
|
+
supportsDetach: (this.cfg.backgroundShell ?? true) && hostBackgroundShellEnabled(),
|
|
157
158
|
};
|
|
158
159
|
}
|
|
159
160
|
_bgManager;
|
|
@@ -331,7 +332,7 @@ export class RemoteHostExecutionEnv {
|
|
|
331
332
|
const cwd = this.resolve(options?.cwd ?? this.cwd);
|
|
332
333
|
const timeoutMs = options?.timeout != null ? options.timeout * 1000 : this.cfg.commandTimeoutMs;
|
|
333
334
|
const detachSignal = options?.detachSignal;
|
|
334
|
-
if (process.platform !== "win32" &&
|
|
335
|
+
if (process.platform !== "win32" && hostExecSpoolEnabled()) {
|
|
335
336
|
const spool = this.openFgSpool();
|
|
336
337
|
if (spool)
|
|
337
338
|
return this.execViaSpool(command, options, cwd, timeoutMs, detachSignal, spool);
|
|
@@ -149,6 +149,8 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
149
149
|
writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
150
150
|
appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
151
151
|
private writeChunked;
|
|
152
|
+
private posixFsInst?;
|
|
153
|
+
private get posixFs();
|
|
152
154
|
fileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
|
|
153
155
|
listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
|
|
154
156
|
readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
@@ -171,8 +173,6 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
171
173
|
}): Promise<Result<string, FileError>>;
|
|
172
174
|
private execData;
|
|
173
175
|
private withCwdEnv;
|
|
174
|
-
private fileErrorFrom;
|
|
175
|
-
private tempResult;
|
|
176
176
|
}
|
|
177
177
|
export declare function k8sExecutionEnvFactory(config: K8sEnvConfig): ExecutionEnvFactory;
|
|
178
178
|
//# sourceMappingURL=remote-env-k8s.d.ts.map
|