@botlearn-course/daemon 0.0.6 → 0.0.8
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 +3 -0
- package/dist/agent-service-client.js +8 -8
- package/dist/agent-service-sandbox.d.ts +108 -0
- package/dist/agent-service-sandbox.js +1400 -0
- package/dist/agent-service-ws-protocol.d.ts +15 -7
- package/dist/agent-service-ws-protocol.js +86 -24
- package/dist/cli.js +26 -53
- package/dist/course-client.js +9 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/run-dispatcher.d.ts +3 -0
- package/dist/run-dispatcher.js +194 -13
- package/dist/runtime-env.d.ts +6 -0
- package/dist/runtime-env.js +35 -2
- package/dist/runtime-profile.js +12 -1
- package/dist/runtimes/claude-code.js +14 -3
- package/dist/runtimes/codex.js +17 -1
- package/dist/runtimes/deepseek-tui.d.ts +3 -2
- package/dist/runtimes/deepseek-tui.js +87 -15
- package/dist/runtimes/engine.d.ts +5 -0
- package/dist/runtimes/engine.js +22 -2
- package/dist/runtimes/fake.js +102 -6
- package/dist/runtimes/gemini.js +14 -1
- package/dist/runtimes/hermes-agent.js +19 -2
- package/dist/runtimes/kimi.js +9 -1
- package/dist/runtimes/openclaw-acp.js +21 -2
- package/dist/runtimes/progress.d.ts +13 -1
- package/dist/runtimes/progress.js +61 -6
- package/dist/sandbox-supervisor.d.ts +1 -1
- package/dist/sandbox-supervisor.js +23 -23
- package/dist/types.d.ts +5 -0
- package/dist/workspace.d.ts +27 -4
- package/dist/workspace.js +96 -23
- package/package.json +1 -1
- package/dist/agent-service-session.d.ts +0 -67
- package/dist/agent-service-session.js +0 -796
package/dist/workspace.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmodSync,
|
|
1
|
+
import { chmodSync, mkdirSync, rmSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { daemonHome } from "./auth-store.js";
|
|
4
4
|
/**
|
|
@@ -42,34 +42,51 @@ export function runtimeProfileDir(agentRunId) {
|
|
|
42
42
|
export function runtimeProfileRunRootDir(agentRunId) {
|
|
43
43
|
return path.dirname(runtimeProfileDir(agentRunId));
|
|
44
44
|
}
|
|
45
|
-
export function runtimeSessionRootDir(runtimeSessionId,
|
|
45
|
+
export function runtimeSessionRootDir(runtimeSessionId, sandboxGeneration) {
|
|
46
46
|
assertSafeId(runtimeSessionId, "runtime_session_id");
|
|
47
|
-
if (!Number.isInteger(
|
|
48
|
-
throw new Error("unsafe
|
|
47
|
+
if (!Number.isInteger(sandboxGeneration) || sandboxGeneration < 1) {
|
|
48
|
+
throw new Error("unsafe sandbox_generation: expected a positive integer");
|
|
49
49
|
}
|
|
50
|
-
return path.join(daemonHome(), "agent-service-sessions", runtimeSessionId, `generation-${
|
|
50
|
+
return path.join(daemonHome(), "agent-service-sessions", runtimeSessionId, `generation-${sandboxGeneration}`);
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* per-session workspace(ADR-015 §7):managed sandbox 下为
|
|
54
|
+
* `<BOTLEARN_AGENT_SERVICE_WORKSPACE_ROOT>/<runtime_session_id>/generation-<sandbox_generation>`,
|
|
55
|
+
* 由 daemon 在 session.open 时创建。
|
|
56
|
+
*/
|
|
57
|
+
export function runtimeSessionWorkspaceDir(runtimeSessionId, sandboxGeneration) {
|
|
58
|
+
assertSafeId(runtimeSessionId, "runtime_session_id");
|
|
59
|
+
if (!Number.isInteger(sandboxGeneration) || sandboxGeneration < 1) {
|
|
60
|
+
throw new Error("unsafe sandbox_generation: expected a positive integer");
|
|
61
|
+
}
|
|
53
62
|
const managedRoot = process.env.BOTLEARN_AGENT_SERVICE_WORKSPACE_ROOT?.trim();
|
|
54
63
|
if (managedRoot) {
|
|
55
|
-
return path.join(managedRoot, runtimeSessionId, `generation-${
|
|
64
|
+
return path.join(managedRoot, runtimeSessionId, `generation-${sandboxGeneration}`);
|
|
56
65
|
}
|
|
57
|
-
return path.join(runtimeSessionRootDir(runtimeSessionId,
|
|
66
|
+
return path.join(runtimeSessionRootDir(runtimeSessionId, sandboxGeneration), "workspace");
|
|
67
|
+
}
|
|
68
|
+
function managedRuntimeSessionParentDir(runtimeSessionId) {
|
|
69
|
+
assertSafeId(runtimeSessionId, "runtime_session_id");
|
|
70
|
+
const managedRoot = process.env.BOTLEARN_AGENT_SERVICE_WORKSPACE_ROOT?.trim();
|
|
71
|
+
return managedRoot ? path.join(managedRoot, runtimeSessionId) : null;
|
|
58
72
|
}
|
|
59
|
-
export function runtimeSessionTranscriptPath(runtimeSessionId,
|
|
73
|
+
export function runtimeSessionTranscriptPath(runtimeSessionId, sandboxGeneration, agentRunId) {
|
|
60
74
|
assertSafeId(agentRunId, "agent_run_id");
|
|
61
|
-
return path.join(runtimeSessionRootDir(runtimeSessionId,
|
|
75
|
+
return path.join(runtimeSessionRootDir(runtimeSessionId, sandboxGeneration), "transcripts", `${agentRunId}.jsonl`);
|
|
62
76
|
}
|
|
63
77
|
// recursive mkdir 只对新建目录生效 mode,已存在目录需 best-effort 收紧。
|
|
64
|
-
function mkdirTolerant(dir) {
|
|
65
|
-
mkdirSync(dir, { recursive: true, mode
|
|
78
|
+
function mkdirTolerant(dir, mode = 0o700) {
|
|
79
|
+
mkdirSync(dir, { recursive: true, mode });
|
|
66
80
|
try {
|
|
67
|
-
chmodSync(dir,
|
|
81
|
+
chmodSync(dir, mode);
|
|
68
82
|
}
|
|
69
83
|
catch {
|
|
70
84
|
// Windows 等不支持 chmod 时忽略。
|
|
71
85
|
}
|
|
72
86
|
}
|
|
87
|
+
function isMissingPathError(error) {
|
|
88
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
89
|
+
}
|
|
73
90
|
export function ensureRunWorkspace(agentRunId) {
|
|
74
91
|
const rootDir = runRootDir(agentRunId);
|
|
75
92
|
const workspaceDir = runWorkspaceDir(agentRunId);
|
|
@@ -77,19 +94,75 @@ export function ensureRunWorkspace(agentRunId) {
|
|
|
77
94
|
mkdirTolerant(workspaceDir);
|
|
78
95
|
return { rootDir, workspaceDir };
|
|
79
96
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
/**
|
|
98
|
+
* session.open 时创建 per-session 目录。managed workspace 默认保持 0700,只有当前
|
|
99
|
+
* activation 会通过 exposeRuntimeSessionWorkspace() 临时开放给 runtime 组。
|
|
100
|
+
*/
|
|
101
|
+
export function ensureRuntimeSessionDirectories(runtimeSessionId, sandboxGeneration) {
|
|
102
|
+
const rootDir = runtimeSessionRootDir(runtimeSessionId, sandboxGeneration);
|
|
103
|
+
const workspaceDir = runtimeSessionWorkspaceDir(runtimeSessionId, sandboxGeneration);
|
|
84
104
|
mkdirTolerant(rootDir);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
const managedParent = managedRuntimeSessionParentDir(runtimeSessionId);
|
|
106
|
+
if (managedParent)
|
|
107
|
+
mkdirTolerant(managedParent, 0o700);
|
|
108
|
+
mkdirTolerant(workspaceDir, 0o700);
|
|
109
|
+
return { rootDir, workspaceDir };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 暴露当前 activation 的 managed workspace。sandbox 根目录由 supervisor 设为
|
|
113
|
+
* 0710,因此 runtime 只能穿过根目录,不能列举其他 session id;未激活 session
|
|
114
|
+
* 的父目录与 workspace 始终为 0700。
|
|
115
|
+
*/
|
|
116
|
+
export function exposeRuntimeSessionWorkspace(runtimeSessionId, sandboxGeneration) {
|
|
117
|
+
const managedParent = managedRuntimeSessionParentDir(runtimeSessionId);
|
|
118
|
+
if (!managedParent)
|
|
119
|
+
return;
|
|
120
|
+
const { workspaceDir } = ensureRuntimeSessionDirectories(runtimeSessionId, sandboxGeneration);
|
|
121
|
+
// Parent is traverse-only for the runtime group: allowing group write here would let
|
|
122
|
+
// the runtime replace the generation directory with a symlink before revoke/cleanup.
|
|
123
|
+
chmodSync(managedParent, 0o710);
|
|
124
|
+
chmodSync(workspaceDir, 0o770);
|
|
125
|
+
}
|
|
126
|
+
/** Revoke a managed workspace without deleting the session's durable local materialization. */
|
|
127
|
+
export function revokeRuntimeSessionWorkspace(runtimeSessionId, sandboxGeneration) {
|
|
128
|
+
const managedParent = managedRuntimeSessionParentDir(runtimeSessionId);
|
|
129
|
+
if (!managedParent)
|
|
130
|
+
return;
|
|
131
|
+
const workspaceDir = runtimeSessionWorkspaceDir(runtimeSessionId, sandboxGeneration);
|
|
132
|
+
// Revoke the leaf before the parent so a runtime loses access as early as possible.
|
|
133
|
+
try {
|
|
134
|
+
chmodSync(workspaceDir, 0o700);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
// session.close / generation cleanup may already have removed it. Permission and I/O
|
|
138
|
+
// failures must remain visible: proceeding would leave an inactive workspace exposed.
|
|
139
|
+
if (!isMissingPathError(error))
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
chmodSync(managedParent, 0o700);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (!isMissingPathError(error))
|
|
147
|
+
throw error;
|
|
89
148
|
}
|
|
90
|
-
|
|
91
|
-
|
|
149
|
+
}
|
|
150
|
+
/** session.close 时删除 workspace 与本地 session 物化状态(transcripts 等)。幂等。 */
|
|
151
|
+
export function removeRuntimeSessionWorkspace(runtimeSessionId, sandboxGeneration) {
|
|
152
|
+
// 校验路径段安全后按 session 整体删除(含全部 generation 与 transcripts)。
|
|
153
|
+
runtimeSessionRootDir(runtimeSessionId, sandboxGeneration);
|
|
154
|
+
rmSync(path.join(daemonHome(), "agent-service-sessions", runtimeSessionId), {
|
|
155
|
+
recursive: true,
|
|
156
|
+
force: true,
|
|
157
|
+
});
|
|
158
|
+
const managedRoot = process.env.BOTLEARN_AGENT_SERVICE_WORKSPACE_ROOT?.trim();
|
|
159
|
+
if (managedRoot) {
|
|
160
|
+
rmSync(path.join(managedRoot, runtimeSessionId), { recursive: true, force: true });
|
|
92
161
|
}
|
|
162
|
+
}
|
|
163
|
+
export function ensureRuntimeSessionWorkspace(runtimeSessionId, sandboxGeneration, agentRunId) {
|
|
164
|
+
const { rootDir, workspaceDir } = ensureRuntimeSessionDirectories(runtimeSessionId, sandboxGeneration);
|
|
165
|
+
const transcriptFile = runtimeSessionTranscriptPath(runtimeSessionId, sandboxGeneration, agentRunId);
|
|
93
166
|
mkdirTolerant(path.dirname(transcriptFile));
|
|
94
167
|
return { rootDir, workspaceDir, transcriptFile };
|
|
95
168
|
}
|
package/package.json
CHANGED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { type Logger } from "./log.js";
|
|
2
|
-
import { type PersistentSessionExecution, type PreparedPersistentTurn, type RunReportingClient } from "./run-dispatcher.js";
|
|
3
|
-
import type { CourseRuntime, CourseRuntimeProfile, RunEvent, RunFileCandidate, RunFileRecord, RunStartPayload } from "./types.js";
|
|
4
|
-
export interface AgentServiceSessionOptions {
|
|
5
|
-
wsUrl: string;
|
|
6
|
-
sessionId: string;
|
|
7
|
-
sessionToken: string;
|
|
8
|
-
runtimes: Map<string, CourseRuntime>;
|
|
9
|
-
daemonVersion: string;
|
|
10
|
-
runtimeEnv?: Record<string, string>;
|
|
11
|
-
log?: Logger;
|
|
12
|
-
random?: () => number;
|
|
13
|
-
sleep?: (ms: number) => Promise<void>;
|
|
14
|
-
}
|
|
15
|
-
/** Long-running daemon client for one persistent managed sandbox session. */
|
|
16
|
-
export declare class AgentServiceSessionClient implements RunReportingClient, PersistentSessionExecution {
|
|
17
|
-
private readonly options;
|
|
18
|
-
private readonly log;
|
|
19
|
-
private readonly random;
|
|
20
|
-
private readonly sleep;
|
|
21
|
-
private readonly state;
|
|
22
|
-
private readonly dispatcher;
|
|
23
|
-
private readonly attempts;
|
|
24
|
-
private readonly runtimeProfiles;
|
|
25
|
-
private readonly pendingAcks;
|
|
26
|
-
private readonly pendingFilePrepares;
|
|
27
|
-
private readonly pendingFileCommits;
|
|
28
|
-
private readonly fileGrants;
|
|
29
|
-
private readonly inflightCommands;
|
|
30
|
-
private socket;
|
|
31
|
-
private sessionGeneration;
|
|
32
|
-
private connectionEpoch;
|
|
33
|
-
private inboundSeq;
|
|
34
|
-
private heartbeatMs;
|
|
35
|
-
private staleMs;
|
|
36
|
-
private stopped;
|
|
37
|
-
private permanentFailure;
|
|
38
|
-
constructor(options: AgentServiceSessionOptions);
|
|
39
|
-
prepareTurn(payload: RunStartPayload): PreparedPersistentTurn;
|
|
40
|
-
persistNativeSession(sessionId: string): void;
|
|
41
|
-
run(): Promise<void>;
|
|
42
|
-
stop(): void;
|
|
43
|
-
postEvent(agentRunId: string, event: RunEvent): Promise<void>;
|
|
44
|
-
postFile(agentRunId: string, file: RunFileCandidate): Promise<RunFileRecord>;
|
|
45
|
-
uploadFileContent(agentRunId: string, fileId: string, absPath: string, mimeType?: string): Promise<RunFileRecord>;
|
|
46
|
-
getRunRuntimeProfile(agentRunId: string): Promise<CourseRuntimeProfile>;
|
|
47
|
-
private connectOnce;
|
|
48
|
-
private handleHello;
|
|
49
|
-
private assertServerFrame;
|
|
50
|
-
private handleServerFrame;
|
|
51
|
-
private applyDesiredState;
|
|
52
|
-
private reconcileInterruptedCommand;
|
|
53
|
-
private runtimeActivation;
|
|
54
|
-
private ackEventOrFile;
|
|
55
|
-
private acceptFileGrant;
|
|
56
|
-
private replaySpool;
|
|
57
|
-
private sendHeartbeat;
|
|
58
|
-
private sendCommandAck;
|
|
59
|
-
private sendControlFrame;
|
|
60
|
-
private sendFrame;
|
|
61
|
-
private nextOutboundSeq;
|
|
62
|
-
private spoolBytes;
|
|
63
|
-
private enforceSpoolLimit;
|
|
64
|
-
private rejectPending;
|
|
65
|
-
private rejectPendingFiles;
|
|
66
|
-
private persist;
|
|
67
|
-
}
|