@botlearn-course/daemon 0.0.2 → 0.0.3
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/dist/agent-service-session.d.ts +67 -0
- package/dist/agent-service-session.js +796 -0
- package/dist/agent-service-ws-protocol.d.ts +28 -0
- package/dist/agent-service-ws-protocol.js +128 -0
- package/dist/cli.js +112 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/run-dispatcher.d.ts +15 -0
- package/dist/run-dispatcher.js +42 -6
- package/dist/runtime-env.d.ts +5 -0
- package/dist/runtime-env.js +23 -0
- package/dist/runtime-profile.js +25 -13
- package/dist/runtimes/acp-stream.js +2 -0
- package/dist/runtimes/codex.d.ts +1 -1
- package/dist/runtimes/codex.js +2 -2
- package/dist/runtimes/deepseek-tui.js +10 -9
- package/dist/runtimes/engine.d.ts +1 -0
- package/dist/runtimes/engine.js +3 -1
- package/dist/runtimes/hermes-agent.d.ts +1 -1
- package/dist/runtimes/hermes-agent.js +3 -2
- package/dist/runtimes/ndjson-stream.d.ts +1 -1
- package/dist/runtimes/ndjson-stream.js +4 -2
- package/dist/runtimes/openclaw-acp.js +3 -1
- package/dist/sandbox-supervisor.d.ts +3 -0
- package/dist/sandbox-supervisor.js +176 -0
- package/dist/types.d.ts +9 -1
- package/dist/websocket-client.d.ts +43 -0
- package/dist/websocket-client.js +320 -0
- package/dist/workspace.d.ts +9 -0
- package/dist/workspace.js +43 -2
- package/package.json +3 -2
|
@@ -3,7 +3,7 @@ import { existsSync, realpathSync } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import net from "node:net";
|
|
5
5
|
import { MAX_PROGRESS_EVENTS_PER_ATTEMPT } from "../mcp/report-progress.js";
|
|
6
|
-
import { runtimeChildEnv } from "../runtime-env.js";
|
|
6
|
+
import { runtimeChildEnv, runtimeChildIdentity } from "../runtime-env.js";
|
|
7
7
|
import { readCommandVersion, resolveCommandOnPath } from "./probe.js";
|
|
8
8
|
import { adaptDeepseekProgressStarted, cleanupProgressMcpConfig, createDeepseekProgressState, createProgressMcpConfig, deepseekProgressDispositions, isDeepseekProgressCompletion, progressMcpAutoInjectionSupported, progressSystemContext, } from "./progress.js";
|
|
9
9
|
import { consoleLogger, wrapEngineAdapter, } from "./engine.js";
|
|
@@ -210,7 +210,8 @@ export class DeepseekTuiAdapter {
|
|
|
210
210
|
token,
|
|
211
211
|
], {
|
|
212
212
|
cwd: opts.cwd,
|
|
213
|
-
env: this.spawnEnv(progressMcpConfig?.path),
|
|
213
|
+
env: this.spawnEnv(opts, progressMcpConfig?.path),
|
|
214
|
+
...runtimeChildIdentity(),
|
|
214
215
|
stdio: ["ignore", "pipe", "pipe"],
|
|
215
216
|
// 自成进程组:解析到的二进制可能是会再 spawn 真实 deepseek-tui
|
|
216
217
|
// server 的 dispatcher,shutdown 必须对整组发信号而非仅直接子进程。
|
|
@@ -264,9 +265,9 @@ export class DeepseekTuiAdapter {
|
|
|
264
265
|
* 不设置 DEEPSEEK_RUNTIME_DIR:server 跨 run 池化共享,per-run 目录不成立;
|
|
265
266
|
* BYOA 直接用用户本机 deepseek 自身的默认状态目录(含已登录凭据)。
|
|
266
267
|
*/
|
|
267
|
-
spawnEnv(progressMcpConfigPath) {
|
|
268
|
+
spawnEnv(opts, progressMcpConfigPath) {
|
|
268
269
|
const env = {
|
|
269
|
-
...runtimeChildEnv(),
|
|
270
|
+
...runtimeChildEnv(opts.env ?? process.env),
|
|
270
271
|
FORCE_COLOR: "0",
|
|
271
272
|
NO_COLOR: "1",
|
|
272
273
|
};
|
|
@@ -636,11 +637,11 @@ function extractDeepseekError(eventName, payload) {
|
|
|
636
637
|
stringField(payload?.payload, "message") ??
|
|
637
638
|
stringField(payload?.payload, "error"));
|
|
638
639
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
640
|
+
// An item failure is a tool result inside an otherwise live turn. DeepSeek may use it
|
|
641
|
+
// to correct the arguments, choose another tool, and still complete the turn. Treating
|
|
642
|
+
// its detail as a sticky runtime error would discard that recovery and fail the run
|
|
643
|
+
// even after a later assistant message or successful turn completion. Only provider
|
|
644
|
+
// errors and terminal turn failures are run-level errors here.
|
|
644
645
|
if (isDeepseekTerminalEvent(eventName, payload)) {
|
|
645
646
|
const turn = payload?.payload?.turn ?? payload?.turn;
|
|
646
647
|
const status = stringField(turn, "status");
|
package/dist/runtimes/engine.js
CHANGED
|
@@ -114,9 +114,10 @@ export function wrapEngineAdapter(id, engine, opts) {
|
|
|
114
114
|
};
|
|
115
115
|
const result = await engine.run({
|
|
116
116
|
text,
|
|
117
|
-
sessionId: null,
|
|
117
|
+
sessionId: run.nativeSessionId ?? null,
|
|
118
118
|
cwd: run.workspaceDir,
|
|
119
119
|
signal,
|
|
120
|
+
...(run.runtimeEnv ? { env: run.runtimeEnv } : {}),
|
|
120
121
|
...(extraArgs.length > 0 ? { extraArgs } : {}),
|
|
121
122
|
...(systemContext !== undefined ? { systemContext } : {}),
|
|
122
123
|
onBlock: (block) => {
|
|
@@ -143,6 +144,7 @@ export function wrapEngineAdapter(id, engine, opts) {
|
|
|
143
144
|
if (result.progressDispositions) {
|
|
144
145
|
await sink.progressDispositions?.(result.progressDispositions);
|
|
145
146
|
}
|
|
147
|
+
await sink.runtimeSession?.(result.newSessionId);
|
|
146
148
|
if (result.error) {
|
|
147
149
|
throw new RuntimeExecutionError(result.error, "runtime_error", result.runtimeFailure);
|
|
148
150
|
}
|
|
@@ -41,7 +41,7 @@ export declare class HermesAgentAdapter extends AcpRuntimeAdapter {
|
|
|
41
41
|
* 用户 `~/.hermes` 的 `.env` / config.yaml 里。
|
|
42
42
|
*/
|
|
43
43
|
protected buildArgs(_opts: EngineRunOptions): string[];
|
|
44
|
-
protected spawnEnv(
|
|
44
|
+
protected spawnEnv(opts: EngineRunOptions): NodeJS.ProcessEnv;
|
|
45
45
|
/** spawn 前把 systemContext 原子写入 `<cwd>/AGENTS.md`(tmp 0600 + rename)。 */
|
|
46
46
|
protected prepareTurn(opts: EngineRunOptions): void;
|
|
47
47
|
/**
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { AcpRuntimeAdapter, } from "./acp-stream.js";
|
|
4
4
|
import { firstExistingPath, readCommandVersion, resolveCommandOnPath, resolveHomePath, } from "./probe.js";
|
|
5
5
|
import { wrapEngineAdapter, } from "./engine.js";
|
|
6
|
+
import { runtimeChildEnv } from "../runtime-env.js";
|
|
6
7
|
/**
|
|
7
8
|
* `hermes-acp` 不在 PATH 上时的已知绝对位置。上游 `scripts/install.sh`
|
|
8
9
|
* (curl|bash 安装器)把私有 virtualenv 装到 `~/.hermes/hermes-agent/venv/`,
|
|
@@ -80,9 +81,9 @@ export class HermesAgentAdapter extends AcpRuntimeAdapter {
|
|
|
80
81
|
buildArgs(_opts) {
|
|
81
82
|
return [];
|
|
82
83
|
}
|
|
83
|
-
spawnEnv(
|
|
84
|
+
spawnEnv(opts) {
|
|
84
85
|
return {
|
|
85
|
-
...process.env,
|
|
86
|
+
...runtimeChildEnv(opts.env ?? process.env),
|
|
86
87
|
// 保持 ACP stdout 无 ANSI 码。
|
|
87
88
|
NO_COLOR: "1",
|
|
88
89
|
// 危险工具调用走 ACP request_permission。
|
|
@@ -46,6 +46,6 @@ export declare abstract class NdjsonStreamAdapter implements EngineAdapter {
|
|
|
46
46
|
protected abstract buildArgs(opts: EngineRunOptions): string[];
|
|
47
47
|
protected abstract handleEvent(obj: unknown, ctx: NdjsonEventCtx): void;
|
|
48
48
|
/** 覆盖以调整 env(FORCE_COLOR=0、NO_COLOR=1 等)。 */
|
|
49
|
-
protected spawnEnv(
|
|
49
|
+
protected spawnEnv(opts: EngineRunOptions): NodeJS.ProcessEnv;
|
|
50
50
|
run(opts: EngineRunOptions): Promise<EngineRunResult>;
|
|
51
51
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { runtimeChildEnv, runtimeChildIdentity } from "../runtime-env.js";
|
|
2
3
|
import { safeCommand, sanitizeRuntimeFailureText, tailText } from "../redaction.js";
|
|
3
4
|
import { sliceUtf8Bytes, utf8ByteLength } from "./text-cap.js";
|
|
4
5
|
import { consoleLogger, } from "./engine.js";
|
|
@@ -21,8 +22,8 @@ export class NdjsonStreamAdapter {
|
|
|
21
22
|
this.log = logger ?? consoleLogger;
|
|
22
23
|
}
|
|
23
24
|
/** 覆盖以调整 env(FORCE_COLOR=0、NO_COLOR=1 等)。 */
|
|
24
|
-
spawnEnv(
|
|
25
|
-
return
|
|
25
|
+
spawnEnv(opts) {
|
|
26
|
+
return runtimeChildEnv(opts.env ?? process.env);
|
|
26
27
|
}
|
|
27
28
|
async run(opts) {
|
|
28
29
|
if (opts.signal.aborted) {
|
|
@@ -43,6 +44,7 @@ export class NdjsonStreamAdapter {
|
|
|
43
44
|
const child = spawn(binary, args, {
|
|
44
45
|
cwd: opts.cwd,
|
|
45
46
|
env: this.spawnEnv(opts),
|
|
47
|
+
...runtimeChildIdentity(),
|
|
46
48
|
stdio: ["ignore", "pipe", "pipe"],
|
|
47
49
|
});
|
|
48
50
|
// spawn 是同步的,但若在 spawn 与稍后挂监听之间发生 abort 会被漏掉,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { runtimeChildEnv, runtimeChildIdentity } from "../runtime-env.js";
|
|
2
3
|
import { readCommandVersion, resolveCommandOnPath } from "./probe.js";
|
|
3
4
|
import { sliceUtf8Bytes } from "./text-cap.js";
|
|
4
5
|
import { consoleLogger, wrapEngineAdapter, } from "./engine.js";
|
|
@@ -384,7 +385,8 @@ export class OpenclawAcpAdapter {
|
|
|
384
385
|
args.push("--token", gateway.token);
|
|
385
386
|
const child = this.spawnFn(command, args, {
|
|
386
387
|
stdio: ["pipe", "pipe", "pipe"],
|
|
387
|
-
env:
|
|
388
|
+
env: runtimeChildEnv(this.env ?? process.env),
|
|
389
|
+
...runtimeChildIdentity(),
|
|
388
390
|
});
|
|
389
391
|
installExitCleanupHook();
|
|
390
392
|
const handle = {
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
3
|
+
import { chmodSync, chownSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const MAX_BOOTSTRAP_BYTES = 64 * 1024;
|
|
6
|
+
const CONTROL_USER = "botlearn-control";
|
|
7
|
+
const RUNTIME_USER = "user";
|
|
8
|
+
const CONTROL_HOME = "/home/botlearn-control/.botlearn-course/daemon";
|
|
9
|
+
const WORKSPACE = "/workspace";
|
|
10
|
+
const RUNTIME_PROFILE_ROOT = "/run/botlearn-runtime-profiles";
|
|
11
|
+
const SUPERVISOR_LOCK_ROOT = "/run/botlearn-sandbox-supervisors";
|
|
12
|
+
const DAEMON_BINARY = "/usr/local/bin/botlearn-course-daemon";
|
|
13
|
+
function numericId(flag, user) {
|
|
14
|
+
const output = execFileSync("/usr/bin/id", [flag, user], {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
17
|
+
}).trim();
|
|
18
|
+
const value = Number(output);
|
|
19
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
20
|
+
throw new Error(`sandbox supervisor could not resolve ${flag} for ${user}`);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
async function readOneShotBootstrap() {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let size = 0;
|
|
27
|
+
for await (const chunk of process.stdin) {
|
|
28
|
+
const bytes = Buffer.from(chunk);
|
|
29
|
+
size += bytes.length;
|
|
30
|
+
if (size > MAX_BOOTSTRAP_BYTES) {
|
|
31
|
+
throw new Error("sandbox supervisor bootstrap exceeds size limit");
|
|
32
|
+
}
|
|
33
|
+
chunks.push(bytes);
|
|
34
|
+
}
|
|
35
|
+
if (size === 0)
|
|
36
|
+
throw new Error("sandbox supervisor bootstrap is empty");
|
|
37
|
+
return Buffer.concat(chunks);
|
|
38
|
+
}
|
|
39
|
+
export function acquireSessionSupervisorLock(runtimeSessionId, lockRoot = SUPERVISOR_LOCK_ROOT) {
|
|
40
|
+
const lockDir = path.join(lockRoot, runtimeSessionId);
|
|
41
|
+
const pidFile = path.join(lockDir, "pid");
|
|
42
|
+
mkdirSync(lockRoot, { recursive: true, mode: 0o700 });
|
|
43
|
+
try {
|
|
44
|
+
mkdirSync(lockDir, { mode: 0o700 });
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (!existsSync(pidFile))
|
|
48
|
+
throw error;
|
|
49
|
+
const existingPid = Number(readFileSync(pidFile, "utf8").trim());
|
|
50
|
+
if (Number.isInteger(existingPid) && existingPid > 1) {
|
|
51
|
+
try {
|
|
52
|
+
process.kill(existingPid, 0);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Stale lock from a supervisor that no longer exists.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
60
|
+
mkdirSync(lockDir, { mode: 0o700 });
|
|
61
|
+
}
|
|
62
|
+
writeFileSync(pidFile, `${process.pid}\n`, { mode: 0o600 });
|
|
63
|
+
return () => {
|
|
64
|
+
try {
|
|
65
|
+
if (Number(readFileSync(pidFile, "utf8").trim()) === process.pid) {
|
|
66
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// A replacement supervisor already owns or removed the lock.
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function prepareDirectories(controlUid, controlGid, runtimeUid, runtimeSessionId, sessionGeneration) {
|
|
75
|
+
mkdirSync(CONTROL_HOME, { recursive: true, mode: 0o700 });
|
|
76
|
+
chownSync("/home/botlearn-control/.botlearn-course", controlUid, controlGid);
|
|
77
|
+
chownSync(CONTROL_HOME, controlUid, controlGid);
|
|
78
|
+
chmodSync(CONTROL_HOME, 0o700);
|
|
79
|
+
mkdirSync(RUNTIME_PROFILE_ROOT, { recursive: true, mode: 0o750 });
|
|
80
|
+
chownSync(RUNTIME_PROFILE_ROOT, controlUid, controlGid);
|
|
81
|
+
chmodSync(RUNTIME_PROFILE_ROOT, 0o750);
|
|
82
|
+
mkdirSync(WORKSPACE, { recursive: true, mode: 0o770 });
|
|
83
|
+
chownSync(WORKSPACE, runtimeUid, controlGid);
|
|
84
|
+
chmodSync(WORKSPACE, 0o770);
|
|
85
|
+
const sessionWorkspace = path.join(WORKSPACE, runtimeSessionId, `generation-${sessionGeneration}`);
|
|
86
|
+
mkdirSync(sessionWorkspace, { recursive: true, mode: 0o770 });
|
|
87
|
+
chownSync(path.join(WORKSPACE, runtimeSessionId), runtimeUid, controlGid);
|
|
88
|
+
chownSync(sessionWorkspace, runtimeUid, controlGid);
|
|
89
|
+
chmodSync(path.join(WORKSPACE, runtimeSessionId), 0o770);
|
|
90
|
+
chmodSync(sessionWorkspace, 0o770);
|
|
91
|
+
}
|
|
92
|
+
export async function runSandboxSupervisor(argv) {
|
|
93
|
+
if (argv.length !== 2 || argv[0] !== "agent-service" || argv[1] !== "session") {
|
|
94
|
+
throw new Error("sandbox supervisor only permits: agent-service session");
|
|
95
|
+
}
|
|
96
|
+
if (typeof process.getuid !== "function" || process.getuid() !== 0) {
|
|
97
|
+
throw new Error("sandbox supervisor must start as root");
|
|
98
|
+
}
|
|
99
|
+
const controlUid = numericId("-u", CONTROL_USER);
|
|
100
|
+
const controlGid = numericId("-g", CONTROL_USER);
|
|
101
|
+
const runtimeUid = numericId("-u", RUNTIME_USER);
|
|
102
|
+
const bootstrap = await readOneShotBootstrap();
|
|
103
|
+
let child;
|
|
104
|
+
let releaseLock = null;
|
|
105
|
+
try {
|
|
106
|
+
const decoded = JSON.parse(bootstrap.toString("utf8"));
|
|
107
|
+
const runtimeSessionId = decoded.runtimeSessionId;
|
|
108
|
+
const sessionGeneration = Number(decoded.sessionGeneration ?? 0);
|
|
109
|
+
if (typeof runtimeSessionId !== "string" ||
|
|
110
|
+
!/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(runtimeSessionId) ||
|
|
111
|
+
!Number.isInteger(sessionGeneration) ||
|
|
112
|
+
sessionGeneration < 1) {
|
|
113
|
+
throw new Error("sandbox supervisor bootstrap session scope is invalid");
|
|
114
|
+
}
|
|
115
|
+
releaseLock = acquireSessionSupervisorLock(runtimeSessionId);
|
|
116
|
+
if (releaseLock === null)
|
|
117
|
+
return 0;
|
|
118
|
+
prepareDirectories(controlUid, controlGid, runtimeUid, runtimeSessionId, sessionGeneration);
|
|
119
|
+
child = spawn(DAEMON_BINARY, ["agent-service", "session", "--bootstrap-stdin"], {
|
|
120
|
+
uid: controlUid,
|
|
121
|
+
gid: controlGid,
|
|
122
|
+
env: {
|
|
123
|
+
HOME: "/home/botlearn-control",
|
|
124
|
+
PATH: "/usr/local/bin:/usr/bin:/bin",
|
|
125
|
+
BOTLEARN_DAEMON_HOME: CONTROL_HOME,
|
|
126
|
+
BOTLEARN_RUNTIME_UID: String(runtimeUid),
|
|
127
|
+
BOTLEARN_RUNTIME_GID: String(controlGid),
|
|
128
|
+
BOTLEARN_AGENT_SERVICE_WORKSPACE_ROOT: WORKSPACE,
|
|
129
|
+
BOTLEARN_AGENT_SERVICE_PROFILE_ROOT: RUNTIME_PROFILE_ROOT,
|
|
130
|
+
},
|
|
131
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
132
|
+
});
|
|
133
|
+
if (child.stdin === null) {
|
|
134
|
+
child.kill("SIGKILL");
|
|
135
|
+
throw new Error("sandbox supervisor could not open daemon bootstrap pipe");
|
|
136
|
+
}
|
|
137
|
+
child.stdin.end(bootstrap);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
releaseLock?.();
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
bootstrap.fill(0);
|
|
145
|
+
}
|
|
146
|
+
const forward = (signal) => {
|
|
147
|
+
if (!child.killed)
|
|
148
|
+
child.kill(signal);
|
|
149
|
+
};
|
|
150
|
+
process.once("SIGTERM", () => forward("SIGTERM"));
|
|
151
|
+
process.once("SIGINT", () => forward("SIGINT"));
|
|
152
|
+
try {
|
|
153
|
+
return await new Promise((resolve, reject) => {
|
|
154
|
+
child.once("error", reject);
|
|
155
|
+
child.once("exit", (code, signal) => {
|
|
156
|
+
if (signal)
|
|
157
|
+
resolve(128);
|
|
158
|
+
else
|
|
159
|
+
resolve(code ?? 1);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
releaseLock?.();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (process.argv[1]?.endsWith("sandbox-supervisor.js")) {
|
|
168
|
+
runSandboxSupervisor(process.argv.slice(2))
|
|
169
|
+
.then((code) => {
|
|
170
|
+
process.exitCode = code;
|
|
171
|
+
})
|
|
172
|
+
.catch((error) => {
|
|
173
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
174
|
+
process.exitCode = 1;
|
|
175
|
+
});
|
|
176
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -147,12 +147,20 @@ export interface CourseRuntimeSink {
|
|
|
147
147
|
file(file: RunFileCandidate): Promise<void>;
|
|
148
148
|
/** 可选的 run-scoped 内部遥测;不得包含 summary 或 provider raw envelope。 */
|
|
149
149
|
progressDispositions?(dispositions: RuntimeProgressDispositions): Promise<void>;
|
|
150
|
+
/** Persist the runtime-native thread/session id before a terminal turn event is emitted. */
|
|
151
|
+
runtimeSession?(sessionId: string): Promise<void>;
|
|
150
152
|
}
|
|
151
153
|
/** 一次 run 的本地执行上下文:服务器 payload + daemon 本地准备产物。 */
|
|
152
154
|
export interface RunExecution {
|
|
153
155
|
payload: RunStartPayload;
|
|
154
|
-
/**
|
|
156
|
+
/** Runtime cwd. Managed persistent sessions reuse one session-scoped workspace. */
|
|
155
157
|
workspaceDir: string;
|
|
158
|
+
/** Runtime-native thread/session id to resume; null creates the first native session. */
|
|
159
|
+
nativeSessionId?: string | null;
|
|
160
|
+
/** Monotonic Course Service context revision accepted for this turn. */
|
|
161
|
+
contextRevision?: number;
|
|
162
|
+
/** Scoped runtime-only environment (for example a short-lived model proxy grant). */
|
|
163
|
+
runtimeEnv?: NodeJS.ProcessEnv;
|
|
156
164
|
}
|
|
157
165
|
export interface CourseRuntime {
|
|
158
166
|
id: string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
export type WebSocketRawData = Buffer;
|
|
3
|
+
export interface WebSocketClientOptions {
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
maxPayload?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Narrow RFC 6455 client for the daemon control plane.
|
|
9
|
+
*
|
|
10
|
+
* It intentionally supports only text/control frames, no extensions, and one configured
|
|
11
|
+
* subprotocol. Keeping this transport on Node built-ins preserves the daemon's zero
|
|
12
|
+
* production-dependency release invariant.
|
|
13
|
+
*/
|
|
14
|
+
export declare class WebSocketClient extends EventEmitter {
|
|
15
|
+
private readonly options;
|
|
16
|
+
static readonly CONNECTING = 0;
|
|
17
|
+
static readonly OPEN = 1;
|
|
18
|
+
static readonly CLOSING = 2;
|
|
19
|
+
static readonly CLOSED = 3;
|
|
20
|
+
readonly url: URL;
|
|
21
|
+
readonly requestedProtocol: string;
|
|
22
|
+
readonly maxPayload: number;
|
|
23
|
+
protocol: string;
|
|
24
|
+
readyState: number;
|
|
25
|
+
private socket;
|
|
26
|
+
private handshakeBuffer;
|
|
27
|
+
private frameBuffer;
|
|
28
|
+
private fragmentedOpcode;
|
|
29
|
+
private fragmentedChunks;
|
|
30
|
+
private fragmentedBytes;
|
|
31
|
+
private closeEmitted;
|
|
32
|
+
private closeSent;
|
|
33
|
+
constructor(rawUrl: string, protocol: string, options?: WebSocketClientOptions);
|
|
34
|
+
send(data: string, callback?: (error?: Error) => void): void;
|
|
35
|
+
close(code?: number, reason?: string): void;
|
|
36
|
+
private connect;
|
|
37
|
+
private sendHandshake;
|
|
38
|
+
private onData;
|
|
39
|
+
private consumeFrames;
|
|
40
|
+
private emitFragmentedMessage;
|
|
41
|
+
private failProtocol;
|
|
42
|
+
private emitClose;
|
|
43
|
+
}
|