@botlearn-course/daemon 0.0.20-beta.9 → 0.0.21-beta.1
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 +5 -5
- package/dist/agent-service-sandbox.d.ts +9 -2
- package/dist/agent-service-sandbox.js +94 -322
- package/dist/agent-service-ws-protocol.d.ts +1 -1
- package/dist/agent-service-ws-protocol.js +2 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/redaction.js +2 -0
- package/dist/run-dispatcher.js +4 -3
- package/dist/runtime-env.d.ts +1 -1
- package/dist/runtime-env.js +1 -1
- package/dist/runtimes/deepseek-tui-base-prompt.d.ts +4 -0
- package/dist/runtimes/deepseek-tui-base-prompt.js +86 -0
- package/dist/runtimes/deepseek-tui.js +19 -1
- package/dist/runtimes/engine.d.ts +2 -0
- package/dist/runtimes/engine.js +1 -0
- package/dist/sandbox-supervisor.js +1 -1
- package/dist/tool-observation.js +2 -0
- package/dist/workspace-filesystem.d.ts +0 -1
- package/dist/workspace-filesystem.js +0 -10
- package/dist/workspace-snapshot-control.js +0 -25
- package/dist/workspace-snapshot-policy.d.ts +1 -1
- package/dist/workspace-snapshot-policy.js +4 -2
- package/dist/workspace.d.ts +10 -66
- package/dist/workspace.js +44 -594
- package/package.json +1 -1
- package/dist/workspace-materialization.d.ts +0 -16
- package/dist/workspace-materialization.js +0 -136
- package/dist/workspace-quota.d.ts +0 -4
- package/dist/workspace-quota.js +0 -42
- package/dist/workspace-restore.d.ts +0 -42
- package/dist/workspace-restore.js +0 -347
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { chmodSync, closeSync, createWriteStream, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
-
import { Readable } from "node:stream";
|
|
4
|
-
import { pipeline } from "node:stream/promises";
|
|
1
|
+
import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
5
2
|
import path from "node:path";
|
|
6
3
|
import { ensureDaemonHome } from "./auth-store.js";
|
|
7
4
|
import { AGENT_SERVICE_WS_SCHEMA, AGENT_SERVICE_WS_SUBPROTOCOL, createSandboxFrame, createWorkspaceFileChunk, parseSandboxFrame, UnsupportedSandboxProtocolError, WORKSPACE_FILE_READ_BINARY_CAPABILITY, } from "./agent-service-ws-protocol.js";
|
|
@@ -11,17 +8,14 @@ import { activationRuntimeEnv, runtimeChildEnv } from "./runtime-env.js";
|
|
|
11
8
|
import { redactSecretString } from "./redaction.js";
|
|
12
9
|
import { parseRuntimeSkillProviderGrantSet, prepareRuntimeSkillProvider, RuntimeSkillProviderError, } from "./runtime-skills.js";
|
|
13
10
|
import { RunDispatcher, } from "./run-dispatcher.js";
|
|
14
|
-
import {
|
|
11
|
+
import { ensureRuntimeSessionDirectories, ensureRuntimeSessionWorkspace, exposeRuntimeSessionWorkspace, removeRuntimeSessionWorkspace, revokeRuntimeSessionWorkspace, runtimeSessionWorkspaceDir, } from "./workspace.js";
|
|
15
12
|
import { readWorkspaceFile, WorkspaceFileReadError } from "./workspace-file-read.js";
|
|
16
13
|
import { checkpointWorkspace, WorkspaceSnapshotControlError, } from "./workspace-snapshot-control.js";
|
|
17
14
|
import { assertWorkspaceWriterFreezeSupported, freezeRuntimeWriters, issueWorkspaceWriterFreezeProof, quiesceRuntimeWriters, thawRuntimeWriters, } from "./runtime-quiescence.js";
|
|
18
15
|
import { WorkspaceSnapshotStagingError } from "./workspace-snapshot-staging.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { readWorkspaceMaterializationMarker } from "./workspace-materialization.js";
|
|
23
|
-
import { assertWorkspaceFilesystem, flushWorkspaceFilesystem, nasWorkspaceEnabled, } from "./workspace-filesystem.js";
|
|
24
|
-
import { applyRuntimeWorkspaceQuota } from "./workspace-quota.js";
|
|
16
|
+
import { WorkspaceEntrySetError, } from "./workspace-entry-set.js";
|
|
17
|
+
import { workspaceSnapshotPolicyCapability, } from "./workspace-snapshot-policy.js";
|
|
18
|
+
import { assertWorkspaceFilesystem, flushWorkspaceFilesystem, } from "./workspace-filesystem.js";
|
|
25
19
|
import { WebSocketClient, } from "./websocket-client.js";
|
|
26
20
|
const RECONNECT_DELAYS_MS = [1_000, 2_000, 4_000, 8_000, 16_000, 30_000];
|
|
27
21
|
const MAX_SPOOL_FRAMES = 1024;
|
|
@@ -29,6 +23,7 @@ const MAX_SPOOL_BYTES = 8 * 1024 * 1024;
|
|
|
29
23
|
const MAX_EVENT_ACK_WINDOW = 64;
|
|
30
24
|
const LEGACY_EVENT_ACK_WINDOW = 1;
|
|
31
25
|
const INPUT_ATTACHMENT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
26
|
+
const ASSET_ACTIVATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
32
27
|
/** 出站 seq 基址:seq = connection_epoch * SEQ_EPOCH_BASE + n,跨重连单调(合同 §1.1)。 */
|
|
33
28
|
const SEQ_EPOCH_BASE = 1_000_000_000;
|
|
34
29
|
function agentServiceHttpBase(wsUrl) {
|
|
@@ -41,6 +36,24 @@ function agentServiceHttpBase(wsUrl) {
|
|
|
41
36
|
}
|
|
42
37
|
const RUNTIME_LOG_WINDOW_MS = 60_000;
|
|
43
38
|
const WORKSPACE_FILE_TRANSFER_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
39
|
+
export function workspaceDurabilityCapabilities() {
|
|
40
|
+
return [
|
|
41
|
+
"workspace_writer_freeze_v1",
|
|
42
|
+
"workspace_snapshot_v2",
|
|
43
|
+
"workspace_nas_v1",
|
|
44
|
+
"workspace_identity_v1",
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* NAS 是运行时 workspace 的唯一持久事实来源,committed snapshot 只供前端查看。
|
|
49
|
+
* 任何 restore payload 都意味着对端仍在下发已删除的 S3 回写指令,必须 fail closed,
|
|
50
|
+
* 否则可能用落后的派生快照覆盖 NAS 中较新的内容。
|
|
51
|
+
*/
|
|
52
|
+
export function assertWorkspaceRestoreAllowed(requestedRestore) {
|
|
53
|
+
if (requestedRestore !== null && requestedRestore !== undefined) {
|
|
54
|
+
throw new Error("workspace_restore_forbidden_for_nas");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
44
57
|
const DISABLED_RUNTIME_LOG_POLICY = {
|
|
45
58
|
enabled: false,
|
|
46
59
|
maxEventBytes: 4096,
|
|
@@ -231,8 +244,8 @@ function sandboxStatePath(sandboxId) {
|
|
|
231
244
|
function emptySessionState() {
|
|
232
245
|
return {
|
|
233
246
|
courseRunId: null,
|
|
247
|
+
workspaceId: null,
|
|
234
248
|
workspaceRef: null,
|
|
235
|
-
workspaceCopyReceipt: null,
|
|
236
249
|
runtimeId: null,
|
|
237
250
|
nativeSessionId: null,
|
|
238
251
|
contextRevision: 0,
|
|
@@ -250,8 +263,8 @@ function normalizeSessionState(value) {
|
|
|
250
263
|
return base;
|
|
251
264
|
return {
|
|
252
265
|
courseRunId: typeof value.courseRunId === "string" ? value.courseRunId : null,
|
|
266
|
+
workspaceId: typeof value.workspaceId === "string" ? value.workspaceId : null,
|
|
253
267
|
workspaceRef: typeof value.workspaceRef === "string" ? value.workspaceRef : null,
|
|
254
|
-
workspaceCopyReceipt: (value.workspaceCopyReceipt && typeof value.workspaceCopyReceipt === "object") ? value.workspaceCopyReceipt : null,
|
|
255
268
|
runtimeId: typeof value.runtimeId === "string" ? value.runtimeId : null,
|
|
256
269
|
nativeSessionId: typeof value.nativeSessionId === "string" ? value.nativeSessionId : null,
|
|
257
270
|
contextRevision: Number(value.contextRevision ?? 0),
|
|
@@ -422,10 +435,10 @@ export class AgentServiceSandboxClient {
|
|
|
422
435
|
if (!activation || activation.activationId !== scope.activationId) {
|
|
423
436
|
throw new Error("managed turn activation context is unavailable");
|
|
424
437
|
}
|
|
425
|
-
const prepared = ensureRuntimeSessionWorkspace(scope.sessionId, this.sandboxGeneration, payload.agent_run_id);
|
|
438
|
+
const prepared = ensureRuntimeSessionWorkspace(scope.sessionId, this.sandboxGeneration, payload.agent_run_id, session.workspaceId ?? scope.sessionId);
|
|
426
439
|
// ensureRuntimeSessionWorkspace creates missing paths with the closed-by-default
|
|
427
440
|
// mode; re-expose only after the activation fence above has been checked.
|
|
428
|
-
exposeRuntimeSessionWorkspace(scope.sessionId, this.sandboxGeneration);
|
|
441
|
+
exposeRuntimeSessionWorkspace(scope.sessionId, this.sandboxGeneration, session.workspaceId ?? scope.sessionId);
|
|
429
442
|
this.currentTurnSessionId = scope.sessionId;
|
|
430
443
|
const runtimeEnv = runtimeChildEnv(process.env);
|
|
431
444
|
delete runtimeEnv.DEEPSEEK_API_KEY;
|
|
@@ -524,6 +537,29 @@ export class AgentServiceSandboxClient {
|
|
|
524
537
|
// there is no safe monotonic ordering from which to infer that an old replay died.
|
|
525
538
|
session.completedActivations.push(pending.activationId);
|
|
526
539
|
}
|
|
540
|
+
let cleanupFrame = null;
|
|
541
|
+
if (ASSET_ACTIVATION_ID_PATTERN.test(pending.activationId)) {
|
|
542
|
+
cleanupFrame = createSandboxFrame({
|
|
543
|
+
type: "activation.cleaned",
|
|
544
|
+
sandboxId: this.options.sandboxId,
|
|
545
|
+
sandboxGeneration: this.sandboxGeneration,
|
|
546
|
+
connectionEpoch: this.connectionEpoch,
|
|
547
|
+
seq: this.nextOutboundSeq(),
|
|
548
|
+
runtimeSessionId: sessionId,
|
|
549
|
+
agentRunId: pending.agentRunId,
|
|
550
|
+
workerAttempt: pending.workerAttempt,
|
|
551
|
+
activationId: pending.activationId,
|
|
552
|
+
});
|
|
553
|
+
session.spool.push(cleanupFrame);
|
|
554
|
+
try {
|
|
555
|
+
this.enforceSpoolLimit();
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
session.spool.pop();
|
|
559
|
+
this.persist();
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
527
563
|
const commandId = `run:${pending.agentRunId}:${pending.workerAttempt}`;
|
|
528
564
|
if (!session.completedCommands.includes(commandId)) {
|
|
529
565
|
session.completedCommands.push(commandId);
|
|
@@ -535,6 +571,8 @@ export class AgentServiceSandboxClient {
|
|
|
535
571
|
if (this.currentTurnSessionId === sessionId)
|
|
536
572
|
this.currentTurnSessionId = null;
|
|
537
573
|
this.persist();
|
|
574
|
+
if (cleanupFrame !== null && this.runtimeLogHandshakeReady)
|
|
575
|
+
await this.sendFrame(cleanupFrame);
|
|
538
576
|
}
|
|
539
577
|
async recoverPendingActivationCleanups() {
|
|
540
578
|
for (const sessionId of Object.keys(this.state.sessions)) {
|
|
@@ -588,7 +626,7 @@ export class AgentServiceSandboxClient {
|
|
|
588
626
|
this.dispatcher.cancelAll();
|
|
589
627
|
for (const [sessionId, session] of Object.entries(this.state.sessions)) {
|
|
590
628
|
session.activationId = null;
|
|
591
|
-
revokeRuntimeSessionWorkspace(sessionId, this.sandboxGeneration);
|
|
629
|
+
revokeRuntimeSessionWorkspace(sessionId, this.sandboxGeneration, session.workspaceId ?? sessionId);
|
|
592
630
|
}
|
|
593
631
|
this.activationContexts.clear();
|
|
594
632
|
this.sessionProfiles.clear();
|
|
@@ -819,9 +857,6 @@ export class AgentServiceSandboxClient {
|
|
|
819
857
|
}
|
|
820
858
|
}
|
|
821
859
|
async handleHello(frame) {
|
|
822
|
-
// A reconnect can race a copy that started on the previous socket. Wait for
|
|
823
|
-
// the serialized lifecycle attempt to converge before deleting crash-left
|
|
824
|
-
// staging and advertising capability on the new connection.
|
|
825
860
|
await this.lifecycleChain;
|
|
826
861
|
if (frame.sandbox_id !== this.options.sandboxId) {
|
|
827
862
|
throw new SandboxClosedError(4403, "sandbox_mismatch");
|
|
@@ -834,9 +869,10 @@ export class AgentServiceSandboxClient {
|
|
|
834
869
|
this.dispatcher.cancelAll();
|
|
835
870
|
await quiesceRuntimeWriters(null);
|
|
836
871
|
this.rejectPending(new Error("Agent Service sandbox generation changed"));
|
|
837
|
-
for (const sessionId of Object.
|
|
838
|
-
revokeRuntimeSessionWorkspace(sessionId, this.state.sandboxGeneration);
|
|
839
|
-
|
|
872
|
+
for (const [sessionId, session] of Object.entries(this.state.sessions)) {
|
|
873
|
+
revokeRuntimeSessionWorkspace(sessionId, this.state.sandboxGeneration, session.workspaceId ?? sessionId);
|
|
874
|
+
// NAS image 跨 generation 保留正文,只清掉本地 daemon 侧的 session 状态。
|
|
875
|
+
removeRuntimeSessionWorkspace(sessionId, this.state.sandboxGeneration, true);
|
|
840
876
|
}
|
|
841
877
|
this.state.sessions = {};
|
|
842
878
|
this.state.sandboxGeneration = frame.sandbox_generation;
|
|
@@ -864,7 +900,6 @@ export class AgentServiceSandboxClient {
|
|
|
864
900
|
: LEGACY_EVENT_ACK_WINDOW;
|
|
865
901
|
this.maxUnackedEvents = Math.min(MAX_EVENT_ACK_WINDOW, ackWindow);
|
|
866
902
|
this.runtimeLogPolicy = runtimeLogPolicy(frame.payload.runtime_log_policy);
|
|
867
|
-
await cleanupRuntimeSessionWorkspaceCopyStaging();
|
|
868
903
|
this.persist();
|
|
869
904
|
assertWorkspaceFilesystem(process.env.BOTLEARN_AGENT_SERVICE_WORKSPACE_ROOT ?? "");
|
|
870
905
|
assertWorkspaceWriterFreezeSupported();
|
|
@@ -872,11 +907,7 @@ export class AgentServiceSandboxClient {
|
|
|
872
907
|
protocol_versions: [AGENT_SERVICE_WS_SCHEMA],
|
|
873
908
|
capabilities: [
|
|
874
909
|
WORKSPACE_FILE_READ_BINARY_CAPABILITY,
|
|
875
|
-
|
|
876
|
-
"workspace_snapshot_v2",
|
|
877
|
-
"workspace_restore_v1",
|
|
878
|
-
...(nasWorkspaceEnabled() ? ["workspace_nas_v1"] : []),
|
|
879
|
-
...(workspaceCopyV1Supported() ? ["workspace_copy_v1"] : []),
|
|
910
|
+
...workspaceDurabilityCapabilities(),
|
|
880
911
|
],
|
|
881
912
|
workspace_snapshot_policy: workspaceSnapshotPolicyCapability(),
|
|
882
913
|
daemon_version: this.options.daemonVersion,
|
|
@@ -1056,310 +1087,54 @@ export class AgentServiceSandboxClient {
|
|
|
1056
1087
|
const sessionId = frame.runtime_session_id;
|
|
1057
1088
|
const session = this.state.sessions[sessionId] ?? emptySessionState();
|
|
1058
1089
|
const courseRunId = frame.payload.course_run_id;
|
|
1090
|
+
// Mixed-version reconciliation may replay a pre-Workspace-identity session.open.
|
|
1091
|
+
// Its physical directory was keyed by RuntimeSession id, which is the exact backfill
|
|
1092
|
+
// identity used by the database migration.
|
|
1093
|
+
const requestedWorkspaceId = typeof frame.payload.workspace_id === "string"
|
|
1094
|
+
? frame.payload.workspace_id
|
|
1095
|
+
: session.workspaceId ?? sessionId;
|
|
1059
1096
|
const requestedWorkspaceRef = frame.payload.workspace_ref;
|
|
1060
|
-
|
|
1061
|
-
const requestedRestore = frame.payload.workspace_restore;
|
|
1062
|
-
const requestedCopyRecord = requestedCopy && typeof requestedCopy === "object"
|
|
1063
|
-
? requestedCopy
|
|
1064
|
-
: undefined;
|
|
1065
|
-
const copyId = requestedCopyRecord
|
|
1066
|
-
? requestedCopyRecord.copy_id
|
|
1067
|
-
: undefined;
|
|
1068
|
-
const requestedCopySource = requestedCopyRecord
|
|
1069
|
-
? requestedCopyRecord.source_runtime_session_id
|
|
1070
|
-
: undefined;
|
|
1097
|
+
assertWorkspaceRestoreAllowed(frame.payload.workspace_restore);
|
|
1071
1098
|
if (typeof courseRunId !== "string" ||
|
|
1072
1099
|
courseRunId.length < 1 ||
|
|
1100
|
+
requestedWorkspaceId.length < 1 ||
|
|
1073
1101
|
(requestedWorkspaceRef !== null &&
|
|
1074
1102
|
requestedWorkspaceRef !== undefined &&
|
|
1075
|
-
(typeof requestedWorkspaceRef !== "string" || requestedWorkspaceRef.length < 1))
|
|
1076
|
-
(requestedCopy !== null &&
|
|
1077
|
-
requestedCopy !== undefined &&
|
|
1078
|
-
(typeof requestedCopy !== "object" ||
|
|
1079
|
-
typeof copyId !== "string" ||
|
|
1080
|
-
copyId.length < 1 ||
|
|
1081
|
-
typeof requestedCopySource !== "string" ||
|
|
1082
|
-
requestedCopySource.length < 1))
|
|
1083
|
-
|| (requestedRestore !== null && requestedRestore !== undefined &&
|
|
1084
|
-
(typeof requestedRestore !== "object" || Array.isArray(requestedRestore)))
|
|
1085
|
-
|| (requestedCopy !== null && requestedCopy !== undefined &&
|
|
1086
|
-
requestedRestore !== null && requestedRestore !== undefined)) {
|
|
1103
|
+
(typeof requestedWorkspaceRef !== "string" || requestedWorkspaceRef.length < 1))) {
|
|
1087
1104
|
throw new Error("invalid session.open payload");
|
|
1088
1105
|
}
|
|
1089
1106
|
if (session.courseRunId !== null && session.courseRunId !== courseRunId) {
|
|
1090
1107
|
throw new Error("runtime session course_run_id cannot be rebound");
|
|
1091
1108
|
}
|
|
1109
|
+
if (session.workspaceId !== null && session.workspaceId !== requestedWorkspaceId) {
|
|
1110
|
+
throw new Error("runtime session workspace_id cannot be rebound");
|
|
1111
|
+
}
|
|
1092
1112
|
if (session.workspaceRef !== null &&
|
|
1093
1113
|
typeof requestedWorkspaceRef === "string" &&
|
|
1094
1114
|
session.workspaceRef !== requestedWorkspaceRef) {
|
|
1095
1115
|
throw new Error("runtime session workspace_ref cannot be rebound");
|
|
1096
1116
|
}
|
|
1097
1117
|
const isNewSession = session.courseRunId === null;
|
|
1098
|
-
let markerPath = null;
|
|
1099
|
-
try {
|
|
1100
|
-
if (requestedRestore && typeof requestedRestore === "object") {
|
|
1101
|
-
const raw = requestedRestore;
|
|
1102
|
-
const descriptorRaw = raw.descriptor;
|
|
1103
|
-
const controlFence = raw.control_fence;
|
|
1104
|
-
let restoreControlToken = raw.control_token;
|
|
1105
|
-
if (!controlFence || typeof controlFence !== "object" || Array.isArray(controlFence)) {
|
|
1106
|
-
throw new Error("invalid workspace restore control fence");
|
|
1107
|
-
}
|
|
1108
|
-
if (typeof restoreControlToken !== "string" || restoreControlToken.length < 32) {
|
|
1109
|
-
throw new Error("invalid workspace restore control token");
|
|
1110
|
-
}
|
|
1111
|
-
if (!descriptorRaw || !Array.isArray(raw.entries)) {
|
|
1112
|
-
throw new Error("invalid workspace restore payload");
|
|
1113
|
-
}
|
|
1114
|
-
const descriptor = {
|
|
1115
|
-
sandboxId: this.options.sandboxId,
|
|
1116
|
-
sandboxGeneration: this.sandboxGeneration,
|
|
1117
|
-
runtimeSessionId: sessionId,
|
|
1118
|
-
continuityState: descriptorRaw.continuity_state,
|
|
1119
|
-
continuityErrorCode: descriptorRaw.continuity_error_code,
|
|
1120
|
-
snapshotId: descriptorRaw.snapshot_id,
|
|
1121
|
-
revision: Number(descriptorRaw.revision),
|
|
1122
|
-
contentSha256: descriptorRaw.content_sha256,
|
|
1123
|
-
expectedFileCount: Number(descriptorRaw.expected_file_count),
|
|
1124
|
-
expectedEntryCount: Number(descriptorRaw.expected_entry_count),
|
|
1125
|
-
expectedTotalBytes: Number(descriptorRaw.expected_total_bytes),
|
|
1126
|
-
};
|
|
1127
|
-
const workspaceDirectory = runtimeSessionWorkspaceDir(sessionId, this.sandboxGeneration);
|
|
1128
|
-
const requiredFreeBytes = Math.max(WORKSPACE_SNAPSHOT_POLICY_V1.requiredRestoreFreeBytes, descriptor.expectedTotalBytes + WORKSPACE_SNAPSHOT_POLICY_V1.requiredRestoreFreeBytes);
|
|
1129
|
-
// Fail before issuing or consuming any object grant. The download staging is a
|
|
1130
|
-
// sibling on this same bounded backing filesystem and is renamed, not copied,
|
|
1131
|
-
// into the atomic restore staging tree.
|
|
1132
|
-
assertWorkspaceRestoreCapacity({
|
|
1133
|
-
workspaceDirectory,
|
|
1134
|
-
descriptor,
|
|
1135
|
-
requiredFreeBytes,
|
|
1136
|
-
entryCopies: 2,
|
|
1137
|
-
});
|
|
1138
|
-
const entries = [];
|
|
1139
|
-
const predownloadRoot = path.join(path.dirname(workspaceDirectory), `.botlearn-predownload-${sessionId}-${randomUUID()}`);
|
|
1140
|
-
mkdirSync(predownloadRoot, { recursive: true, mode: 0o700 });
|
|
1141
|
-
try {
|
|
1142
|
-
let nextCursor = null;
|
|
1143
|
-
do {
|
|
1144
|
-
if (nextCursor !== null && nextCursor.length > 2048) {
|
|
1145
|
-
throw new Error("invalid workspace restore cursor");
|
|
1146
|
-
}
|
|
1147
|
-
const pageSignal = AbortSignal.timeout(60_000);
|
|
1148
|
-
const response = await fetch(`${agentServiceHttpBase(this.options.wsUrl)}/runtime-sessions/${sessionId}/restore-grants`, {
|
|
1149
|
-
method: "POST",
|
|
1150
|
-
headers: {
|
|
1151
|
-
Authorization: `Bearer ${restoreControlToken}`,
|
|
1152
|
-
"Content-Type": "application/json",
|
|
1153
|
-
},
|
|
1154
|
-
body: JSON.stringify({
|
|
1155
|
-
policy_id: WORKSPACE_SNAPSHOT_POLICY_V1.policyId,
|
|
1156
|
-
fence: controlFence,
|
|
1157
|
-
cursor: nextCursor,
|
|
1158
|
-
// Fetch one canonical entry at a time so a grant is consumed as soon as
|
|
1159
|
-
// it is signed, including deployments using the minimum 60-second TTL.
|
|
1160
|
-
max_entries: 1,
|
|
1161
|
-
}),
|
|
1162
|
-
signal: pageSignal,
|
|
1163
|
-
});
|
|
1164
|
-
if (!response.ok) {
|
|
1165
|
-
let code = "workspace_restore_unavailable";
|
|
1166
|
-
let retryable = response.status >= 500 || response.status === 408 ||
|
|
1167
|
-
response.status === 429;
|
|
1168
|
-
try {
|
|
1169
|
-
const rejected = await response.json();
|
|
1170
|
-
const detail = rejected.detail;
|
|
1171
|
-
if (detail && typeof detail === "object" && !Array.isArray(detail)) {
|
|
1172
|
-
const stableCode = detail.code;
|
|
1173
|
-
if (typeof stableCode === "string" && stableCode.length <= 120) {
|
|
1174
|
-
code = stableCode;
|
|
1175
|
-
}
|
|
1176
|
-
if (typeof detail.retryable === "boolean") {
|
|
1177
|
-
retryable = detail.retryable;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
catch {
|
|
1182
|
-
// Content-free fallback retains the stable local failure code.
|
|
1183
|
-
}
|
|
1184
|
-
throw new WorkspaceRestoreError(code, retryable);
|
|
1185
|
-
}
|
|
1186
|
-
const page = await response.json();
|
|
1187
|
-
if (!Array.isArray(page.entries))
|
|
1188
|
-
throw new Error("invalid workspace restore page");
|
|
1189
|
-
if (typeof page.control_token !== "string" || page.control_token.length < 32) {
|
|
1190
|
-
throw new Error("invalid refreshed workspace restore control token");
|
|
1191
|
-
}
|
|
1192
|
-
restoreControlToken = page.control_token;
|
|
1193
|
-
const pageFiles = [];
|
|
1194
|
-
for (const value of page.entries) {
|
|
1195
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1196
|
-
throw new Error("invalid workspace restore entry");
|
|
1197
|
-
}
|
|
1198
|
-
const item = value;
|
|
1199
|
-
if (item.entry_type === "directory") {
|
|
1200
|
-
entries.push({ type: "directory", path: String(item.path), mode: "0700" });
|
|
1201
|
-
continue;
|
|
1202
|
-
}
|
|
1203
|
-
if (item.entry_type !== "file" || !item.grant || typeof item.grant !== "object") {
|
|
1204
|
-
throw new Error("invalid workspace restore file grant");
|
|
1205
|
-
}
|
|
1206
|
-
const entry = {
|
|
1207
|
-
type: "file",
|
|
1208
|
-
path: String(item.path),
|
|
1209
|
-
mode: item.mode,
|
|
1210
|
-
sizeBytes: Number(item.size_bytes),
|
|
1211
|
-
sha256: String(item.sha256),
|
|
1212
|
-
};
|
|
1213
|
-
entries.push(entry);
|
|
1214
|
-
pageFiles.push({ entry, grant: item.grant });
|
|
1215
|
-
}
|
|
1216
|
-
// Validate all paths and duplicates before using any page path locally.
|
|
1217
|
-
validateWorkspaceEntrySet({ schemaVersion: "agent-workspace-entry-set/1", entries }, WORKSPACE_SNAPSHOT_POLICY_V1.limits);
|
|
1218
|
-
for (const { entry, grant } of pageFiles) {
|
|
1219
|
-
if (typeof grant.url !== "string")
|
|
1220
|
-
throw new Error("workspace restore grant missing");
|
|
1221
|
-
const destination = path.join(predownloadRoot, ...entry.path.split("/"));
|
|
1222
|
-
mkdirSync(path.dirname(destination), { recursive: true, mode: 0o700 });
|
|
1223
|
-
const part = `${destination}.part`;
|
|
1224
|
-
const downloadSignal = AbortSignal.timeout(60_000);
|
|
1225
|
-
const download = await fetch(grant.url, {
|
|
1226
|
-
method: "GET",
|
|
1227
|
-
headers: grant.headers,
|
|
1228
|
-
signal: downloadSignal,
|
|
1229
|
-
});
|
|
1230
|
-
if (!download.ok)
|
|
1231
|
-
throw new Error(`workspace_restore_download_${download.status}`);
|
|
1232
|
-
if (!download.body)
|
|
1233
|
-
throw new Error("workspace_restore_download_body_missing");
|
|
1234
|
-
await pipeline(Readable.fromWeb(download.body), createWriteStream(part, { mode: 0o600 }), { signal: downloadSignal });
|
|
1235
|
-
renameSync(part, destination);
|
|
1236
|
-
}
|
|
1237
|
-
const rawNext = page.next_cursor;
|
|
1238
|
-
if (rawNext !== null && rawNext !== undefined && typeof rawNext !== "string") {
|
|
1239
|
-
throw new Error("invalid workspace restore cursor");
|
|
1240
|
-
}
|
|
1241
|
-
nextCursor = rawNext ?? null;
|
|
1242
|
-
} while (nextCursor !== null);
|
|
1243
|
-
await restoreWorkspaceSnapshot({
|
|
1244
|
-
workspaceDirectory,
|
|
1245
|
-
controlDirectory: path.join(ensureDaemonHome(), "agent-service-sandboxes", this.options.sandboxId, "workspace-control"),
|
|
1246
|
-
descriptor,
|
|
1247
|
-
entrySet: { schemaVersion: "agent-workspace-entry-set/1", entries },
|
|
1248
|
-
limits: WORKSPACE_SNAPSHOT_POLICY_V1.limits,
|
|
1249
|
-
requiredFreeBytes,
|
|
1250
|
-
downloadFile: async (entry, partPath) => {
|
|
1251
|
-
renameSync(path.join(predownloadRoot, ...entry.path.split("/")), partPath);
|
|
1252
|
-
},
|
|
1253
|
-
});
|
|
1254
|
-
}
|
|
1255
|
-
finally {
|
|
1256
|
-
rmSync(predownloadRoot, { recursive: true, force: true });
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
if (typeof requestedCopySource === "string" && typeof copyId === "string") {
|
|
1260
|
-
if (session.workspaceCopyReceipt !== null) {
|
|
1261
|
-
const receipt = session.workspaceCopyReceipt;
|
|
1262
|
-
if (receipt.copy_id !== copyId ||
|
|
1263
|
-
receipt.source_runtime_session_id !== requestedCopySource ||
|
|
1264
|
-
receipt.target_runtime_session_id !== sessionId ||
|
|
1265
|
-
receipt.source_sandbox_id !== this.options.sandboxId ||
|
|
1266
|
-
receipt.source_generation !== this.sandboxGeneration) {
|
|
1267
|
-
throw new WorkspaceCopyError("workspace_migration_receipt_mismatch");
|
|
1268
|
-
}
|
|
1269
|
-
markerPath = path.join(runtimeSessionWorkspaceDir(sessionId, this.sandboxGeneration), WORKSPACE_COPY_POLICY_V1.markerName);
|
|
1270
|
-
}
|
|
1271
|
-
else if (!isNewSession) {
|
|
1272
|
-
throw new WorkspaceCopyError("workspace_migration_receipt_mismatch");
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
if (isNewSession && typeof requestedCopySource === "string" && typeof copyId === "string") {
|
|
1276
|
-
const sourceSession = this.state.sessions[requestedCopySource];
|
|
1277
|
-
if (!sourceSession ||
|
|
1278
|
-
sourceSession.courseRunId === null ||
|
|
1279
|
-
this.activeSessionId === requestedCopySource ||
|
|
1280
|
-
this.currentTurnSessionId === requestedCopySource) {
|
|
1281
|
-
throw new WorkspaceCopyError("workspace_migration_source_unavailable");
|
|
1282
|
-
}
|
|
1283
|
-
revokeRuntimeSessionWorkspace(requestedCopySource, this.sandboxGeneration);
|
|
1284
|
-
const copied = await copyRuntimeSessionWorkspace({
|
|
1285
|
-
copyId,
|
|
1286
|
-
sourceRuntimeSessionId: requestedCopySource,
|
|
1287
|
-
targetRuntimeSessionId: sessionId,
|
|
1288
|
-
sandboxId: this.options.sandboxId,
|
|
1289
|
-
sandboxGeneration: this.sandboxGeneration,
|
|
1290
|
-
});
|
|
1291
|
-
session.workspaceCopyReceipt = copied.receipt;
|
|
1292
|
-
markerPath = copied.markerPath;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
catch (error) {
|
|
1296
|
-
if (error instanceof WorkspaceCopyError) {
|
|
1297
|
-
await this.sendSessionFrame("session.open_failed", sessionId, {
|
|
1298
|
-
error_code: error.code,
|
|
1299
|
-
});
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
if (requestedRestore) {
|
|
1303
|
-
const restoreError = error instanceof WorkspaceRestoreError ? error : null;
|
|
1304
|
-
await this.sendSessionFrame("session.open_failed", sessionId, {
|
|
1305
|
-
error_code: restoreError?.code ?? "workspace_restore_unavailable",
|
|
1306
|
-
retryable: restoreError?.retryable ?? true,
|
|
1307
|
-
});
|
|
1308
|
-
return;
|
|
1309
|
-
}
|
|
1310
|
-
throw error;
|
|
1311
|
-
}
|
|
1312
1118
|
session.courseRunId = courseRunId;
|
|
1119
|
+
session.workspaceId = requestedWorkspaceId;
|
|
1313
1120
|
session.workspaceRef = session.workspaceRef ?? (typeof requestedWorkspaceRef === "string"
|
|
1314
1121
|
? requestedWorkspaceRef
|
|
1315
1122
|
: `ws_${sessionId.replaceAll("-", "")}_g${this.sandboxGeneration}`);
|
|
1316
1123
|
// Replaying session.open is part of reconnect reconciliation. An already-active
|
|
1317
1124
|
// runtime may still be creating files in this workspace, so never transiently revoke
|
|
1318
1125
|
// its group write/execute access before the following session.activate arrives.
|
|
1319
|
-
if (
|
|
1320
|
-
ensureRuntimeSessionDirectories(sessionId, this.sandboxGeneration);
|
|
1321
|
-
}
|
|
1322
|
-
if (markerPath !== null) {
|
|
1323
|
-
await finalizeRuntimeSessionWorkspaceCopy(markerPath);
|
|
1324
|
-
}
|
|
1325
|
-
try {
|
|
1326
|
-
if (!nasWorkspaceEnabled()) {
|
|
1327
|
-
await applyRuntimeWorkspaceQuota(sessionId, this.sandboxGeneration);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
catch (error) {
|
|
1331
|
-
if (requestedRestore) {
|
|
1332
|
-
await this.sendSessionFrame("session.open_failed", sessionId, {
|
|
1333
|
-
error_code: "workspace_restore_unavailable",
|
|
1334
|
-
});
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
if (requestedCopy) {
|
|
1338
|
-
await this.sendSessionFrame("session.open_failed", sessionId, {
|
|
1339
|
-
error_code: "workspace_migration_io_failed",
|
|
1340
|
-
});
|
|
1341
|
-
return;
|
|
1342
|
-
}
|
|
1343
|
-
throw error;
|
|
1126
|
+
if (isNewSession || session.activationId === null) {
|
|
1127
|
+
ensureRuntimeSessionDirectories(sessionId, this.sandboxGeneration, requestedWorkspaceId);
|
|
1344
1128
|
}
|
|
1345
1129
|
this.state.sessions[sessionId] = session;
|
|
1346
1130
|
this.persist();
|
|
1347
|
-
|
|
1131
|
+
// NAS 是唯一的 workspace 权威来源,所以 session.opened 不携带 snapshot 物化标记:
|
|
1132
|
+
// 挂载证明只说明当前 generation 已挂上 NAS,不要求运行目录等于某个 committed revision。
|
|
1348
1133
|
await this.sendSessionFrame("session.opened", sessionId, {
|
|
1134
|
+
workspace_id: session.workspaceId,
|
|
1349
1135
|
workspace_ref: session.workspaceRef,
|
|
1350
1136
|
native_session_id: session.nativeSessionId,
|
|
1351
|
-
|
|
1352
|
-
? {
|
|
1353
|
-
workspace_marker_version: 1,
|
|
1354
|
-
workspace_continuity_state: materialization.workspaceContinuityState,
|
|
1355
|
-
workspace_snapshot_id: materialization.snapshotId,
|
|
1356
|
-
workspace_revision: materialization.revision,
|
|
1357
|
-
workspace_content_sha256: materialization.contentSha256,
|
|
1358
|
-
}
|
|
1359
|
-
: {}),
|
|
1360
|
-
...(session.workspaceCopyReceipt !== null
|
|
1361
|
-
? { workspace_copy_receipt: session.workspaceCopyReceipt }
|
|
1362
|
-
: {}),
|
|
1137
|
+
workspace_authority: "nas",
|
|
1363
1138
|
});
|
|
1364
1139
|
}
|
|
1365
1140
|
async handleWorkspaceCheckpoint(frame) {
|
|
@@ -1379,8 +1154,6 @@ export class AgentServiceSandboxClient {
|
|
|
1379
1154
|
"sandbox_pause",
|
|
1380
1155
|
"sandbox_cleanup",
|
|
1381
1156
|
"generation_rotation",
|
|
1382
|
-
"initial_migration",
|
|
1383
|
-
"live_workspace_copy",
|
|
1384
1157
|
"maintenance_repair",
|
|
1385
1158
|
].includes(String(sourceKind)) ||
|
|
1386
1159
|
!Number.isSafeInteger(baseRevision) ||
|
|
@@ -1408,7 +1181,7 @@ export class AgentServiceSandboxClient {
|
|
|
1408
1181
|
await checkpointWorkspace({
|
|
1409
1182
|
wsUrl: this.options.wsUrl,
|
|
1410
1183
|
reconnectToken: controlToken,
|
|
1411
|
-
workspaceDirectory: runtimeSessionWorkspaceDir(sessionId, this.sandboxGeneration),
|
|
1184
|
+
workspaceDirectory: runtimeSessionWorkspaceDir(session.workspaceId ?? sessionId, this.sandboxGeneration),
|
|
1412
1185
|
scope: {
|
|
1413
1186
|
checkpointId,
|
|
1414
1187
|
baseRevision,
|
|
@@ -1420,17 +1193,12 @@ export class AgentServiceSandboxClient {
|
|
|
1420
1193
|
freezeProof,
|
|
1421
1194
|
});
|
|
1422
1195
|
checkpointCommitted = true;
|
|
1423
|
-
|
|
1424
|
-
if (!marker)
|
|
1425
|
-
throw new Error("workspace checkpoint marker missing");
|
|
1196
|
+
// committed revision 只是派生投影,不改变运行目录,所以这里不上报物化标记。
|
|
1426
1197
|
await this.sendSessionFrame("session.opened", sessionId, {
|
|
1198
|
+
workspace_id: session.workspaceId,
|
|
1427
1199
|
workspace_ref: session.workspaceRef,
|
|
1428
1200
|
native_session_id: session.nativeSessionId,
|
|
1429
|
-
|
|
1430
|
-
workspace_continuity_state: marker.workspaceContinuityState,
|
|
1431
|
-
workspace_snapshot_id: marker.snapshotId,
|
|
1432
|
-
workspace_revision: marker.revision,
|
|
1433
|
-
workspace_content_sha256: marker.contentSha256,
|
|
1201
|
+
workspace_authority: "nas",
|
|
1434
1202
|
});
|
|
1435
1203
|
await this.sendCommandAck(frame, "ok");
|
|
1436
1204
|
if (!lifecycleCheckpoint)
|
|
@@ -1525,7 +1293,7 @@ export class AgentServiceSandboxClient {
|
|
|
1525
1293
|
if (required.length === 0)
|
|
1526
1294
|
return [];
|
|
1527
1295
|
if (!availableCapabilities) {
|
|
1528
|
-
const workspace = ensureRuntimeSessionDirectories(sessionId, this.sandboxGeneration);
|
|
1296
|
+
const workspace = ensureRuntimeSessionDirectories(sessionId, this.sandboxGeneration, session.workspaceId ?? sessionId);
|
|
1529
1297
|
availableCapabilities = new Set(availableRunCapabilities({
|
|
1530
1298
|
agent_run_id: "activation-probe",
|
|
1531
1299
|
course_run_id: session.courseRunId ?? "",
|
|
@@ -1608,7 +1376,7 @@ export class AgentServiceSandboxClient {
|
|
|
1608
1376
|
session.runtimeId = runtimeId;
|
|
1609
1377
|
session.contextRevision = contextRevision;
|
|
1610
1378
|
session.activationId = activationId;
|
|
1611
|
-
exposeRuntimeSessionWorkspace(sessionId, this.sandboxGeneration);
|
|
1379
|
+
exposeRuntimeSessionWorkspace(sessionId, this.sandboxGeneration, session.workspaceId ?? sessionId);
|
|
1612
1380
|
this.activationContexts.set(sessionId, {
|
|
1613
1381
|
activationId,
|
|
1614
1382
|
runtimeEnv,
|
|
@@ -1779,8 +1547,8 @@ export class AgentServiceSandboxClient {
|
|
|
1779
1547
|
await this.finishTurn({ agent_run_id: runId });
|
|
1780
1548
|
}
|
|
1781
1549
|
/**
|
|
1782
|
-
* 幂等关闭一个 runtime session:终止其 runtime
|
|
1783
|
-
*
|
|
1550
|
+
* 幂等关闭一个 runtime session:终止其 runtime 子进程并清空 session-local state。
|
|
1551
|
+
* Durable Workspace 由独立身份拥有,关闭执行上下文不会删除学员文件。
|
|
1784
1552
|
*/
|
|
1785
1553
|
async closeSession(sessionId, reason, sourceFrame, fence) {
|
|
1786
1554
|
const session = this.state.sessions[sessionId];
|
|
@@ -1795,7 +1563,11 @@ export class AgentServiceSandboxClient {
|
|
|
1795
1563
|
}
|
|
1796
1564
|
await this.terminateSessionWriters(session);
|
|
1797
1565
|
session.activationId = null;
|
|
1798
|
-
|
|
1566
|
+
const workspaceId = session.workspaceId ?? sessionId;
|
|
1567
|
+
const sharedWorkspaceIsOpen = Object.entries(this.state.sessions).some(([otherSessionId, other]) => otherSessionId !== sessionId && (other.workspaceId ?? otherSessionId) === workspaceId);
|
|
1568
|
+
if (!sharedWorkspaceIsOpen) {
|
|
1569
|
+
revokeRuntimeSessionWorkspace(sessionId, this.sandboxGeneration, workspaceId);
|
|
1570
|
+
}
|
|
1799
1571
|
if (fence && !this.isCurrentLifecycleFence(fence))
|
|
1800
1572
|
return;
|
|
1801
1573
|
if (this.activeSessionId === sessionId)
|
|
@@ -1804,7 +1576,7 @@ export class AgentServiceSandboxClient {
|
|
|
1804
1576
|
this.currentTurnSessionId = null;
|
|
1805
1577
|
for (const runId of runIds)
|
|
1806
1578
|
this.turnScopes.delete(runId);
|
|
1807
|
-
removeRuntimeSessionWorkspace(sessionId, this.sandboxGeneration);
|
|
1579
|
+
removeRuntimeSessionWorkspace(sessionId, this.sandboxGeneration, true, workspaceId);
|
|
1808
1580
|
delete this.state.sessions[sessionId];
|
|
1809
1581
|
this.persist();
|
|
1810
1582
|
}
|
|
@@ -1946,7 +1718,7 @@ export class AgentServiceSandboxClient {
|
|
|
1946
1718
|
this.activationContexts.delete(sessionId);
|
|
1947
1719
|
this.sessionProfiles.delete(sessionId);
|
|
1948
1720
|
if (terminateWriters) {
|
|
1949
|
-
revokeRuntimeSessionWorkspace(sessionId, this.sandboxGeneration);
|
|
1721
|
+
revokeRuntimeSessionWorkspace(sessionId, this.sandboxGeneration, session.workspaceId ?? sessionId);
|
|
1950
1722
|
}
|
|
1951
1723
|
if (this.activeSessionId === sessionId)
|
|
1952
1724
|
this.activeSessionId = null;
|
|
@@ -2108,7 +1880,7 @@ export class AgentServiceSandboxClient {
|
|
|
2108
1880
|
}
|
|
2109
1881
|
this.workspaceFileReadInFlight = true;
|
|
2110
1882
|
try {
|
|
2111
|
-
const result = await readWorkspaceFile(runtimeSessionWorkspaceDir(sessionId, this.sandboxGeneration), relativePath, {
|
|
1883
|
+
const result = await readWorkspaceFile(runtimeSessionWorkspaceDir(this.state.sessions[sessionId]?.workspaceId ?? sessionId, this.sandboxGeneration), relativePath, {
|
|
2112
1884
|
maxBytes: maxBytes,
|
|
2113
1885
|
expectedSizeBytes: expectedSizeBytes,
|
|
2114
1886
|
expectedSha256,
|
|
@@ -3,7 +3,7 @@ export declare const AGENT_SERVICE_WS_SUBPROTOCOL: "botlearn-agent-sandbox.v4";
|
|
|
3
3
|
export declare const WORKSPACE_FILE_READ_BINARY_CAPABILITY: "workspace_file_read_binary_v1";
|
|
4
4
|
export declare const WORKSPACE_FILE_CHUNK_BYTES: number;
|
|
5
5
|
export declare const WORKSPACE_FILE_CHUNK_HEADER_BYTES = 29;
|
|
6
|
-
export type SandboxFrameType = "sandbox.hello" | "sandbox.sync" | "session.open" | "session.activate" | "turn.start" | "turn.cancel" | "session.close" | "sandbox.drain" | "sandbox.shutdown" | "event.ack" | "auth.rotate" | "workspace.file.read" | "workspace.checkpoint" | "workspace.thaw" | "ping" | "sandbox.ready" | "sandbox.heartbeat" | "command.ack" | "session.opened" | "session.open_failed" | "session.closed" | "turn.event" | "turn.file.report" | "workspace.file.result" | "sandbox.drained" | "sandbox.log" | "pong" | "protocol.error";
|
|
6
|
+
export type SandboxFrameType = "sandbox.hello" | "sandbox.sync" | "session.open" | "session.activate" | "turn.start" | "turn.cancel" | "session.close" | "sandbox.drain" | "sandbox.shutdown" | "event.ack" | "auth.rotate" | "workspace.file.read" | "workspace.checkpoint" | "workspace.thaw" | "ping" | "sandbox.ready" | "sandbox.heartbeat" | "command.ack" | "session.opened" | "session.open_failed" | "session.closed" | "activation.cleaned" | "turn.event" | "turn.file.report" | "workspace.file.result" | "sandbox.drained" | "sandbox.log" | "pong" | "protocol.error";
|
|
7
7
|
export declare class UnsupportedSandboxProtocolError extends Error {
|
|
8
8
|
readonly schemaVersion: unknown;
|
|
9
9
|
constructor(schemaVersion: unknown);
|
|
@@ -27,6 +27,7 @@ const FRAME_TYPES = new Set([
|
|
|
27
27
|
"session.opened",
|
|
28
28
|
"session.open_failed",
|
|
29
29
|
"session.closed",
|
|
30
|
+
"activation.cleaned",
|
|
30
31
|
"turn.event",
|
|
31
32
|
"turn.file.report",
|
|
32
33
|
"workspace.file.result",
|
|
@@ -52,6 +53,7 @@ const TURN_TYPES = new Set([
|
|
|
52
53
|
"turn.start",
|
|
53
54
|
"turn.cancel",
|
|
54
55
|
"event.ack",
|
|
56
|
+
"activation.cleaned",
|
|
55
57
|
"turn.event",
|
|
56
58
|
"turn.file.report",
|
|
57
59
|
]);
|