@marifold/core 0.69.0 → 0.71.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/dist/agent/AgentRunner.d.ts +7 -1
- package/dist/agent/AgentRunner.d.ts.map +1 -1
- package/dist/agent/AgentRunner.js +93 -20
- package/dist/agent/AgentRunner.js.map +1 -1
- package/dist/agent/RunArtifacts.d.ts +6 -1
- package/dist/agent/RunArtifacts.d.ts.map +1 -1
- package/dist/agent/RunArtifacts.js +16 -3
- package/dist/agent/RunArtifacts.js.map +1 -1
- package/dist/agent/RunWorkspace.d.ts +3 -0
- package/dist/agent/RunWorkspace.d.ts.map +1 -1
- package/dist/agent/RunWorkspace.js +5 -0
- package/dist/agent/RunWorkspace.js.map +1 -1
- package/dist/agent/ScopedProcess.d.ts.map +1 -1
- package/dist/agent/ScopedProcess.js +1 -0
- package/dist/agent/ScopedProcess.js.map +1 -1
- package/dist/agent/ToolRegistry.d.ts +11 -1
- package/dist/agent/ToolRegistry.d.ts.map +1 -1
- package/dist/agent/ToolRegistry.js +4 -1
- package/dist/agent/ToolRegistry.js.map +1 -1
- package/dist/agent/tools/ReadFileTool.d.ts.map +1 -1
- package/dist/agent/tools/ReadFileTool.js +4 -0
- package/dist/agent/tools/ReadFileTool.js.map +1 -1
- package/dist/agent/tools/ReadWebPageTool.d.ts +30 -0
- package/dist/agent/tools/ReadWebPageTool.d.ts.map +1 -0
- package/dist/agent/tools/ReadWebPageTool.js +56 -0
- package/dist/agent/tools/ReadWebPageTool.js.map +1 -0
- package/dist/agent/tools/WebSearchTool.d.ts +1 -0
- package/dist/agent/tools/WebSearchTool.d.ts.map +1 -1
- package/dist/agent/tools/WebSearchTool.js +19 -4
- package/dist/agent/tools/WebSearchTool.js.map +1 -1
- package/dist/agent/tools/WriteFileTool.d.ts.map +1 -1
- package/dist/agent/tools/WriteFileTool.js +4 -0
- package/dist/agent/tools/WriteFileTool.js.map +1 -1
- package/dist/app/SkillAppInstanceRegistry.d.ts.map +1 -1
- package/dist/app/SkillAppInstanceRegistry.js +5 -0
- package/dist/app/SkillAppInstanceRegistry.js.map +1 -1
- package/dist/config/ChatGptCatalogVersion.d.ts +3 -0
- package/dist/config/ChatGptCatalogVersion.d.ts.map +1 -0
- package/dist/config/ChatGptCatalogVersion.js +55 -0
- package/dist/config/ChatGptCatalogVersion.js.map +1 -0
- package/dist/config/ConfigLoader.js +2 -2
- package/dist/config/ConfigLoader.js.map +1 -1
- package/dist/config/ConfigManager.js +2 -2
- package/dist/config/ConfigManager.js.map +1 -1
- package/dist/config/ConfigSchema.d.ts +4 -4
- package/dist/config/ConfigSchema.d.ts.map +1 -1
- package/dist/config/ConfigSchema.js +2 -2
- package/dist/config/ConfigSchema.js.map +1 -1
- package/dist/config/ProviderInspector.d.ts.map +1 -1
- package/dist/config/ProviderInspector.js +2 -5
- package/dist/config/ProviderInspector.js.map +1 -1
- package/dist/errors/MarifoldError.d.ts +1 -1
- package/dist/errors/MarifoldError.d.ts.map +1 -1
- package/dist/errors/MarifoldError.js.map +1 -1
- package/dist/images/AvatarThumbnail.d.ts +3 -0
- package/dist/images/AvatarThumbnail.d.ts.map +1 -0
- package/dist/images/AvatarThumbnail.js +16 -0
- package/dist/images/AvatarThumbnail.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/dist/profiles/ProfileManager.d.ts.map +1 -1
- package/dist/profiles/ProfileManager.js +2 -3
- package/dist/profiles/ProfileManager.js.map +1 -1
- package/dist/runs/RunRegistry.d.ts +27 -1
- package/dist/runs/RunRegistry.d.ts.map +1 -1
- package/dist/runs/RunRegistry.js +75 -16
- package/dist/runs/RunRegistry.js.map +1 -1
- package/dist/runtime/MarifoldRuntime.d.ts +9 -2
- package/dist/runtime/MarifoldRuntime.d.ts.map +1 -1
- package/dist/runtime/MarifoldRuntime.js +40 -9
- package/dist/runtime/MarifoldRuntime.js.map +1 -1
- package/dist/search/BuiltInSearchBackend.d.ts +11 -0
- package/dist/search/BuiltInSearchBackend.d.ts.map +1 -0
- package/dist/search/BuiltInSearchBackend.js +100 -0
- package/dist/search/BuiltInSearchBackend.js.map +1 -0
- package/dist/search/BuiltInSearchParser.d.ts +5 -0
- package/dist/search/BuiltInSearchParser.d.ts.map +1 -0
- package/dist/search/BuiltInSearchParser.js +107 -0
- package/dist/search/BuiltInSearchParser.js.map +1 -0
- package/dist/search/PublicWebUrl.d.ts +4 -0
- package/dist/search/PublicWebUrl.d.ts.map +1 -0
- package/dist/search/PublicWebUrl.js +48 -0
- package/dist/search/PublicWebUrl.js.map +1 -0
- package/dist/search/SearchBackend.d.ts +3 -4
- package/dist/search/SearchBackend.d.ts.map +1 -1
- package/dist/search/SearchBackend.js +1 -1
- package/dist/search/SearchBackend.js.map +1 -1
- package/dist/search/WebPageReader.d.ts +16 -0
- package/dist/search/WebPageReader.d.ts.map +1 -0
- package/dist/search/WebPageReader.js +136 -0
- package/dist/search/WebPageReader.js.map +1 -0
- package/dist/search/WebPageText.d.ts +5 -0
- package/dist/search/WebPageText.d.ts.map +1 -0
- package/dist/search/WebPageText.js +47 -0
- package/dist/search/WebPageText.js.map +1 -0
- package/dist/search/WebResearchGuidance.d.ts +5 -0
- package/dist/search/WebResearchGuidance.d.ts.map +1 -0
- package/dist/search/WebResearchGuidance.js +27 -0
- package/dist/search/WebResearchGuidance.js.map +1 -0
- package/dist/search/createSearchBackend.d.ts +1 -0
- package/dist/search/createSearchBackend.d.ts.map +1 -1
- package/dist/search/createSearchBackend.js +4 -0
- package/dist/search/createSearchBackend.js.map +1 -1
- package/dist/workspace/DeviceDelegateTool.d.ts +40 -0
- package/dist/workspace/DeviceDelegateTool.d.ts.map +1 -0
- package/dist/workspace/DeviceDelegateTool.js +79 -0
- package/dist/workspace/DeviceDelegateTool.js.map +1 -0
- package/dist/workspace/WorkspaceArtifactTransfer.d.ts +8 -0
- package/dist/workspace/WorkspaceArtifactTransfer.d.ts.map +1 -0
- package/dist/workspace/WorkspaceArtifactTransfer.js +45 -0
- package/dist/workspace/WorkspaceArtifactTransfer.js.map +1 -0
- package/dist/workspace/WorkspaceExecutor.d.ts +19 -0
- package/dist/workspace/WorkspaceExecutor.d.ts.map +1 -0
- package/dist/workspace/WorkspaceExecutor.js +212 -0
- package/dist/workspace/WorkspaceExecutor.js.map +1 -0
- package/dist/workspace/WorkspaceManager.d.ts +43 -0
- package/dist/workspace/WorkspaceManager.d.ts.map +1 -0
- package/dist/workspace/WorkspaceManager.js +378 -0
- package/dist/workspace/WorkspaceManager.js.map +1 -0
- package/dist/workspace/WorkspaceRuns.d.ts +18 -0
- package/dist/workspace/WorkspaceRuns.d.ts.map +1 -0
- package/dist/workspace/WorkspaceRuns.js +152 -0
- package/dist/workspace/WorkspaceRuns.js.map +1 -0
- package/dist/workspace/WorkspaceStore.d.ts +49 -0
- package/dist/workspace/WorkspaceStore.d.ts.map +1 -0
- package/dist/workspace/WorkspaceStore.js +419 -0
- package/dist/workspace/WorkspaceStore.js.map +1 -0
- package/dist/workspace/WorkspaceTerminal.d.ts +4 -0
- package/dist/workspace/WorkspaceTerminal.d.ts.map +1 -0
- package/dist/workspace/WorkspaceTerminal.js +80 -0
- package/dist/workspace/WorkspaceTerminal.js.map +1 -0
- package/dist/workspace/bridge/BridgePeer.d.ts +48 -0
- package/dist/workspace/bridge/BridgePeer.d.ts.map +1 -0
- package/dist/workspace/bridge/BridgePeer.js +307 -0
- package/dist/workspace/bridge/BridgePeer.js.map +1 -0
- package/dist/workspace/bridge/ChunkTransfers.d.ts +19 -0
- package/dist/workspace/bridge/ChunkTransfers.d.ts.map +1 -0
- package/dist/workspace/bridge/ChunkTransfers.js +125 -0
- package/dist/workspace/bridge/ChunkTransfers.js.map +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.WorkspaceExecutor = void 0;
|
|
37
|
+
exports.executionTools = executionTools;
|
|
38
|
+
const WorkspaceArtifactTransfer_1 = require("./WorkspaceArtifactTransfer");
|
|
39
|
+
const fs = __importStar(require("node:fs"));
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
41
|
+
const workspace_protocol_1 = require("@marifold/workspace-protocol");
|
|
42
|
+
const RunWorkspace_1 = require("../agent/RunWorkspace");
|
|
43
|
+
const RunArtifacts_1 = require("../agent/RunArtifacts");
|
|
44
|
+
const ToolRegistry_1 = require("../agent/ToolRegistry");
|
|
45
|
+
const InspectAttachmentTool_1 = require("../agent/tools/InspectAttachmentTool");
|
|
46
|
+
const ReadAttachmentTool_1 = require("../agent/tools/ReadAttachmentTool");
|
|
47
|
+
const SearchAttachmentTool_1 = require("../agent/tools/SearchAttachmentTool");
|
|
48
|
+
const ReadFileTool_1 = require("../agent/tools/ReadFileTool");
|
|
49
|
+
const WriteFileTool_1 = require("../agent/tools/WriteFileTool");
|
|
50
|
+
const ShellExecTool_1 = require("../agent/tools/ShellExecTool");
|
|
51
|
+
const WorkspacePaths_1 = require("./WorkspacePaths");
|
|
52
|
+
function executionTools() {
|
|
53
|
+
return [
|
|
54
|
+
new InspectAttachmentTool_1.InspectAttachmentTool(),
|
|
55
|
+
new ReadAttachmentTool_1.ReadAttachmentTool(),
|
|
56
|
+
new SearchAttachmentTool_1.SearchAttachmentTool(),
|
|
57
|
+
new ReadFileTool_1.ReadFileTool(),
|
|
58
|
+
new WriteFileTool_1.WriteFileTool(),
|
|
59
|
+
new ShellExecTool_1.ShellExecTool(),
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
/** A device owns its own capability construction. Host paths, trusted folders,
|
|
63
|
+
* shell environments, and credentials are never accepted as grants over RPC. */
|
|
64
|
+
class WorkspaceExecutor {
|
|
65
|
+
config;
|
|
66
|
+
runsDir;
|
|
67
|
+
protectedPaths;
|
|
68
|
+
runs = new Map();
|
|
69
|
+
registry = new ToolRegistry_1.ToolRegistry();
|
|
70
|
+
timer;
|
|
71
|
+
constructor(config, runsDir, protectedPaths = []) {
|
|
72
|
+
this.config = config;
|
|
73
|
+
this.runsDir = runsDir;
|
|
74
|
+
this.protectedPaths = protectedPaths;
|
|
75
|
+
for (const tool of executionTools())
|
|
76
|
+
this.registry.register(tool);
|
|
77
|
+
this.timer = setInterval(() => {
|
|
78
|
+
for (const [id, run] of this.runs)
|
|
79
|
+
if (run.lease < Date.now()) {
|
|
80
|
+
run.abort.abort();
|
|
81
|
+
this.runs.delete(id);
|
|
82
|
+
}
|
|
83
|
+
}, 5000);
|
|
84
|
+
this.timer.unref();
|
|
85
|
+
}
|
|
86
|
+
close() {
|
|
87
|
+
clearInterval(this.timer);
|
|
88
|
+
for (const run of this.runs.values())
|
|
89
|
+
run.abort.abort();
|
|
90
|
+
this.runs.clear();
|
|
91
|
+
}
|
|
92
|
+
cancelWorkspace(workspaceId) {
|
|
93
|
+
for (const [key, run] of this.runs)
|
|
94
|
+
if (key.startsWith(`${workspaceId}:`)) {
|
|
95
|
+
run.abort.abort();
|
|
96
|
+
this.runs.delete(key);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async handle(operation, value, context) {
|
|
100
|
+
const b = (0, workspace_protocol_1.record)(value);
|
|
101
|
+
const runId = (0, workspace_protocol_1.identifier)(b.runId);
|
|
102
|
+
const key = `${context.workspaceId}:${runId}`;
|
|
103
|
+
// Completed output is read-only and recoverable after a guest restart. Never
|
|
104
|
+
// reconstruct execution capabilities or grants from an on-disk run directory.
|
|
105
|
+
if (operation === 'executor.artifact') {
|
|
106
|
+
const artifact = (0, RunArtifacts_1.resolveRunArtifact)(`ws_${(0, workspace_protocol_1.identifier)(context.workspaceId)}_${runId}`, (0, workspace_protocol_1.identifier)(b.artifactId), this.runsDir ?? path.join((0, WorkspacePaths_1.marifoldHome)(), 'runs'));
|
|
107
|
+
if (!artifact)
|
|
108
|
+
throw new Error('Artifact is unavailable.');
|
|
109
|
+
const offset = b.offset;
|
|
110
|
+
if (typeof offset !== 'number' || !Number.isSafeInteger(offset) || offset < 0 || offset > artifact.size)
|
|
111
|
+
throw new Error('Invalid artifact offset.');
|
|
112
|
+
const fd = fs.openSync(artifact.path, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
113
|
+
try {
|
|
114
|
+
const bytes = Buffer.alloc(Math.min((0, WorkspaceArtifactTransfer_1.artifactReadLength)(b.length), artifact.size - offset));
|
|
115
|
+
const n = fs.readSync(fd, bytes, 0, bytes.length, offset);
|
|
116
|
+
return { data: bytes.subarray(0, n).toString('base64'), size: artifact.size };
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
fs.closeSync(fd);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (operation === 'executor.prepare') {
|
|
123
|
+
if (this.runs.has(key))
|
|
124
|
+
throw new Error('Execution already prepared.');
|
|
125
|
+
if (this.runs.size >= 50)
|
|
126
|
+
throw new Error('Execution capacity reached.');
|
|
127
|
+
if (Array.isArray(b.images) && b.images.some((image) => !image || typeof image !== 'object' || 'path' in image))
|
|
128
|
+
throw new Error('Remote image inputs must carry bytes or URLs, never device paths.');
|
|
129
|
+
const config = this.config();
|
|
130
|
+
const workspace = (0, RunWorkspace_1.createRunWorkspace)({
|
|
131
|
+
deniedRoots: this.protectedPaths,
|
|
132
|
+
id: `ws_${context.workspaceId}_${runId}`,
|
|
133
|
+
cwd: typeof b.cwd === 'string' ? b.cwd : undefined,
|
|
134
|
+
trustedFolders: config.trustedFolders,
|
|
135
|
+
files: Array.isArray(b.files) ? b.files : undefined,
|
|
136
|
+
images: Array.isArray(b.images) ? b.images : undefined,
|
|
137
|
+
...(this.runsDir ? { runsDir: this.runsDir } : {}),
|
|
138
|
+
});
|
|
139
|
+
this.runs.set(key, {
|
|
140
|
+
workspace,
|
|
141
|
+
abort: new AbortController(),
|
|
142
|
+
lease: Date.now() + 45000,
|
|
143
|
+
grants: new Map(),
|
|
144
|
+
completed: false,
|
|
145
|
+
});
|
|
146
|
+
return workspace;
|
|
147
|
+
}
|
|
148
|
+
const run = this.runs.get(key);
|
|
149
|
+
if (!run)
|
|
150
|
+
throw new Error('Execution is unavailable or its lease expired. Start a new run explicitly.');
|
|
151
|
+
if (operation === 'executor.cancel') {
|
|
152
|
+
run.abort.abort();
|
|
153
|
+
return { cancelled: true };
|
|
154
|
+
}
|
|
155
|
+
if (operation === 'executor.lease') {
|
|
156
|
+
if (!run.completed && !run.abort.signal.aborted)
|
|
157
|
+
run.lease = Date.now() + 45000;
|
|
158
|
+
return { active: !run.abort.signal.aborted };
|
|
159
|
+
}
|
|
160
|
+
if (operation === 'executor.artifacts') {
|
|
161
|
+
run.completed = true;
|
|
162
|
+
run.lease = Date.now() + 24 * 60 * 60 * 1000;
|
|
163
|
+
return (0, RunArtifacts_1.listRunArtifacts)(run.workspace);
|
|
164
|
+
}
|
|
165
|
+
if (run.completed || run.abort.signal.aborted)
|
|
166
|
+
throw new Error('Execution has ended.');
|
|
167
|
+
const tool = this.registry.get(String(b.tool));
|
|
168
|
+
if (!tool || tool.kind === 'interaction')
|
|
169
|
+
throw new Error('Unsupported execution tool.');
|
|
170
|
+
const input = (0, workspace_protocol_1.record)(b.input);
|
|
171
|
+
const toolContext = {
|
|
172
|
+
workspace: run.workspace,
|
|
173
|
+
cwd: run.workspace.cwd,
|
|
174
|
+
trustedFolders: this.config().trustedFolders,
|
|
175
|
+
outputLimit: this.config().toolOutputLimit,
|
|
176
|
+
signal: run.abort.signal,
|
|
177
|
+
};
|
|
178
|
+
const risk = (await tool.assessRisk?.(input, toolContext)) ?? { escalate: false };
|
|
179
|
+
const blocked = risk.blocked || (!risk.trusted && this.config().approval[tool.kind] === 'deny');
|
|
180
|
+
const hash = (0, workspace_protocol_1.digest)(JSON.stringify([tool.definition.name, input]));
|
|
181
|
+
if (operation === 'executor.assess') {
|
|
182
|
+
const grant = (0, workspace_protocol_1.randomId)();
|
|
183
|
+
for (const [id, g] of run.grants)
|
|
184
|
+
if (g.expires < Date.now())
|
|
185
|
+
run.grants.delete(id);
|
|
186
|
+
if (run.grants.size >= 128)
|
|
187
|
+
throw new Error('Too many outstanding execution grants.');
|
|
188
|
+
if (!blocked)
|
|
189
|
+
run.grants.set(grant, { hash, expires: Date.now() + 6 * 60 * 1000 });
|
|
190
|
+
return {
|
|
191
|
+
...risk,
|
|
192
|
+
blocked,
|
|
193
|
+
grant,
|
|
194
|
+
persistable: false,
|
|
195
|
+
trusted: false,
|
|
196
|
+
escalate: true,
|
|
197
|
+
reason: blocked
|
|
198
|
+
? (risk.reason ?? 'Denied by execution device policy.')
|
|
199
|
+
: (risk.reason ?? 'Approve this call on the selected device.'),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (operation !== 'executor.execute')
|
|
203
|
+
throw new Error('Unsupported executor operation.');
|
|
204
|
+
const grant = run.grants.get(String(b.grant));
|
|
205
|
+
run.grants.delete(String(b.grant));
|
|
206
|
+
if (blocked || !grant || grant.hash !== hash || grant.expires < Date.now())
|
|
207
|
+
throw new Error('Execution grant is invalid or expired.');
|
|
208
|
+
return tool.execute(input, toolContext);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.WorkspaceExecutor = WorkspaceExecutor;
|
|
212
|
+
//# sourceMappingURL=WorkspaceExecutor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkspaceExecutor.js","sourceRoot":"","sources":["../../src/workspace/WorkspaceExecutor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { WorkspaceSummary } from '@marifold/workspace-protocol';
|
|
2
|
+
import { WorkspaceStore } from './WorkspaceStore';
|
|
3
|
+
export interface WorkspaceOperationContext {
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
senderDeviceId: string;
|
|
6
|
+
hostDeviceId: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class WorkspaceManager {
|
|
9
|
+
readonly store: WorkspaceStore;
|
|
10
|
+
private peers;
|
|
11
|
+
private hostStatus;
|
|
12
|
+
private presence;
|
|
13
|
+
onMembershipRemoved?: (workspaceId: string, deviceId?: string) => void;
|
|
14
|
+
private heartbeat?;
|
|
15
|
+
private remoteHandler?;
|
|
16
|
+
private executorHandler?;
|
|
17
|
+
constructor(configPath: string);
|
|
18
|
+
start(handler: NonNullable<WorkspaceManager['remoteHandler']>, executor?: WorkspaceManager['executorHandler']): void;
|
|
19
|
+
close(): void;
|
|
20
|
+
list(): WorkspaceSummary[];
|
|
21
|
+
create(name: string, url: string, registrationToken: string): Promise<{
|
|
22
|
+
workspace: WorkspaceSummary;
|
|
23
|
+
invitation: string;
|
|
24
|
+
}>;
|
|
25
|
+
add(url: string, token: string, executor?: boolean): Promise<WorkspaceSummary>;
|
|
26
|
+
devices(id: string): {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
host: boolean;
|
|
30
|
+
platform: string;
|
|
31
|
+
architecture: string;
|
|
32
|
+
executor: boolean;
|
|
33
|
+
online: boolean;
|
|
34
|
+
}[];
|
|
35
|
+
request(id: string, operation: string, input: unknown, requestId?: string): Promise<unknown>;
|
|
36
|
+
execute(id: string, device: string, operation: string, input: unknown, requestId?: string): Promise<unknown>;
|
|
37
|
+
remove(id: string): Promise<void>;
|
|
38
|
+
setExecutor(id: string, enabled: boolean): Promise<void>;
|
|
39
|
+
private connect;
|
|
40
|
+
private checkHost;
|
|
41
|
+
private dispatch;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=WorkspaceManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkspaceManager.d.ts","sourceRoot":"","sources":["../../src/workspace/WorkspaceManager.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAGV,gBAAgB,EAGjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,cAAc,EAAuB,MAAM,kBAAkB,CAAC;AAGvE,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AACD,qBAAa,gBAAgB;IAC3B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,QAAQ,CAAkG;IAClH,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACvE,OAAO,CAAC,SAAS,CAAC,CAAiC;IACnD,OAAO,CAAC,aAAa,CAAC,CAA8F;IACpH,OAAO,CAAC,eAAe,CAAC,CAA8F;IACtH,YAAY,UAAU,EAAE,MAAM,EAE7B;IACD,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,gBAAgB,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAQnH;IACD,KAAK,IAAI,IAAI,CAIZ;IACD,IAAI,IAAI,gBAAgB,EAAE,CAazB;IACK,MAAM,CACV,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC;QAAE,SAAS,EAAE,gBAAgB,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAoB9D;IACK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAuDjF;IACD,OAAO,CAAC,EAAE,EAAE,MAAM;;;;;;;;QAmBjB;IACK,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,SAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BrG;IACK,OAAO,CACX,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,EACd,SAAS,SAAa,GACrB,OAAO,CAAC,OAAO,CAAC,CAelB;IACK,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBtC;IACK,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAK7D;IACD,OAAO,CAAC,OAAO;YAiCD,SAAS;YAoBT,QAAQ;CAoFvB"}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.WorkspaceManager = void 0;
|
|
37
|
+
const MarifoldError_1 = require("../errors/MarifoldError");
|
|
38
|
+
const os = __importStar(require("node:os"));
|
|
39
|
+
const workspace_protocol_1 = require("@marifold/workspace-protocol");
|
|
40
|
+
const WorkspaceStore_1 = require("./WorkspaceStore");
|
|
41
|
+
const BridgePeer_1 = require("./bridge/BridgePeer");
|
|
42
|
+
class WorkspaceManager {
|
|
43
|
+
store;
|
|
44
|
+
peers = new Map();
|
|
45
|
+
hostStatus = new Map();
|
|
46
|
+
presence = new Map();
|
|
47
|
+
onMembershipRemoved;
|
|
48
|
+
heartbeat;
|
|
49
|
+
remoteHandler;
|
|
50
|
+
executorHandler;
|
|
51
|
+
constructor(configPath) {
|
|
52
|
+
this.store = new WorkspaceStore_1.WorkspaceStore(configPath);
|
|
53
|
+
}
|
|
54
|
+
start(handler, executor) {
|
|
55
|
+
this.remoteHandler = handler;
|
|
56
|
+
this.executorHandler = executor;
|
|
57
|
+
this.store.interruptRequests();
|
|
58
|
+
for (const c of this.store.list())
|
|
59
|
+
this.connect(c);
|
|
60
|
+
this.heartbeat = setInterval(() => {
|
|
61
|
+
for (const c of this.store.list())
|
|
62
|
+
if (c.role === 'guest')
|
|
63
|
+
void this.checkHost(c);
|
|
64
|
+
}, 15000);
|
|
65
|
+
}
|
|
66
|
+
close() {
|
|
67
|
+
clearInterval(this.heartbeat);
|
|
68
|
+
for (const peer of this.peers.values())
|
|
69
|
+
peer.close();
|
|
70
|
+
this.store.close();
|
|
71
|
+
}
|
|
72
|
+
list() {
|
|
73
|
+
return this.store
|
|
74
|
+
.list()
|
|
75
|
+
.map(({ id, name, role, bridgeUrl, deviceId, hostDeviceId, executor }) => ({
|
|
76
|
+
id,
|
|
77
|
+
name,
|
|
78
|
+
role,
|
|
79
|
+
bridgeUrl,
|
|
80
|
+
deviceId,
|
|
81
|
+
hostDeviceId,
|
|
82
|
+
executor,
|
|
83
|
+
online: role === 'host' ? Boolean(this.peers.get(id)?.online) : this.hostStatus.get(id) === true,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
async create(name, url, registrationToken) {
|
|
87
|
+
const c = await this.store.create(name, url, os.hostname());
|
|
88
|
+
try {
|
|
89
|
+
const response = await fetch(`${c.bridgeUrl}/v1/hosts`, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: { authorization: `Bearer ${registrationToken}`, 'content-type': 'application/json' },
|
|
92
|
+
body: JSON.stringify({ workspaceId: c.id, hostDeviceId: c.hostDeviceId, host: c.host }),
|
|
93
|
+
signal: AbortSignal.timeout(10000),
|
|
94
|
+
redirect: 'error',
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok)
|
|
97
|
+
throw new Error(`Bridge registration failed (HTTP ${response.status}).`);
|
|
98
|
+
const peer = this.connect(c);
|
|
99
|
+
await peer.ready();
|
|
100
|
+
return { workspace: this.list().find((w) => w.id === c.id), invitation: this.store.invite(c.id) };
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
this.peers.get(c.id)?.close();
|
|
104
|
+
this.peers.delete(c.id);
|
|
105
|
+
this.store.remove(c.id);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async add(url, token, executor = false) {
|
|
110
|
+
if (token.length > 16384)
|
|
111
|
+
throw new Error('Invalid invitation.');
|
|
112
|
+
const invitation = (0, workspace_protocol_1.record)(JSON.parse(Buffer.from(token, 'base64url').toString('utf8')));
|
|
113
|
+
if (invitation.version !== 1 ||
|
|
114
|
+
invitation.expiresAt < Date.now() ||
|
|
115
|
+
(0, workspace_protocol_1.bridgeOrigin)(url) !== invitation.bridgeUrl ||
|
|
116
|
+
typeof invitation.secret !== 'string')
|
|
117
|
+
throw new Error('Invitation expired or belongs to a different bridge.');
|
|
118
|
+
const workspaceId = (0, workspace_protocol_1.identifier)(invitation.workspaceId);
|
|
119
|
+
if (this.store.list().some((c) => c.id === workspaceId))
|
|
120
|
+
throw new Error('Workspace already connected.');
|
|
121
|
+
const identity = await (0, workspace_protocol_1.createIdentity)();
|
|
122
|
+
const pendingId = `pending_${(0, workspace_protocol_1.randomId)()}`;
|
|
123
|
+
const temporary = new BridgePeer_1.BridgePeer({
|
|
124
|
+
bridgeUrl: invitation.bridgeUrl,
|
|
125
|
+
workspaceId,
|
|
126
|
+
deviceId: pendingId,
|
|
127
|
+
identity,
|
|
128
|
+
host: invitation.host,
|
|
129
|
+
onRequest: async () => {
|
|
130
|
+
throw new Error('Pairing is not an execution connection.');
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
temporary.start();
|
|
134
|
+
try {
|
|
135
|
+
await temporary.ready();
|
|
136
|
+
const reply = (0, workspace_protocol_1.record)(await temporary.request('join', { secret: invitation.secret, name: os.hostname() }));
|
|
137
|
+
const certificate = reply.certificate;
|
|
138
|
+
if (!(0, workspace_protocol_1.validMembership)(certificate, invitation.host) ||
|
|
139
|
+
certificate.membership.workspaceId !== workspaceId ||
|
|
140
|
+
JSON.stringify(certificate.membership.identity) !== JSON.stringify((0, workspace_protocol_1.publicIdentity)(identity)))
|
|
141
|
+
throw new Error('Host returned invalid membership.');
|
|
142
|
+
const c = {
|
|
143
|
+
id: workspaceId,
|
|
144
|
+
name: String(reply.name),
|
|
145
|
+
role: 'guest',
|
|
146
|
+
bridgeUrl: invitation.bridgeUrl,
|
|
147
|
+
hostDeviceId: temporary.hostId,
|
|
148
|
+
deviceId: certificate.membership.deviceId,
|
|
149
|
+
identity,
|
|
150
|
+
host: invitation.host,
|
|
151
|
+
certificate,
|
|
152
|
+
executor,
|
|
153
|
+
};
|
|
154
|
+
this.store.save(c);
|
|
155
|
+
const peer = this.connect(c);
|
|
156
|
+
await peer.ready();
|
|
157
|
+
await this.checkHost(c);
|
|
158
|
+
return this.list().find((w) => w.id === c.id);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
temporary.close();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
devices(id) {
|
|
165
|
+
const c = this.store.get(id);
|
|
166
|
+
return this.store
|
|
167
|
+
.devices(c.id)
|
|
168
|
+
.filter((d) => !d.revoked)
|
|
169
|
+
.map((d) => {
|
|
170
|
+
const m = d.certificate.membership;
|
|
171
|
+
const host = m.deviceId === c.hostDeviceId;
|
|
172
|
+
const p = this.presence.get(`${c.id}:${m.deviceId}`);
|
|
173
|
+
return {
|
|
174
|
+
id: m.deviceId,
|
|
175
|
+
name: m.name,
|
|
176
|
+
host,
|
|
177
|
+
platform: host ? os.platform() : (p?.platform ?? ''),
|
|
178
|
+
architecture: host ? os.arch() : (p?.architecture ?? ''),
|
|
179
|
+
executor: host || p?.executor === true,
|
|
180
|
+
online: host || Boolean(p && Date.now() - p.seen < 35000),
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async request(id, operation, input, requestId = (0, workspace_protocol_1.randomId)()) {
|
|
185
|
+
const c = this.store.get(id);
|
|
186
|
+
if (c.role === 'host')
|
|
187
|
+
return this.dispatch(c, { type: 'request', operation, input, id: requestId }, { id: c.deviceId, identity: c.host, certificate: c.certificate });
|
|
188
|
+
const peer = this.peers.get(c.id);
|
|
189
|
+
if (!peer?.online || this.hostStatus.get(c.id) !== true)
|
|
190
|
+
throw new MarifoldError_1.MarifoldError('WORKSPACE_OFFLINE', 'Workspace host is offline.');
|
|
191
|
+
const read = ['devices', 'run.events', 'artifact.read'].includes(operation) ||
|
|
192
|
+
(operation === 'api' && (0, workspace_protocol_1.record)(input).method === 'GET');
|
|
193
|
+
let result;
|
|
194
|
+
try {
|
|
195
|
+
result = await peer.request(operation, input, c.hostDeviceId, c.host, requestId, read ? 60000 : 120000);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (!peer.online ||
|
|
199
|
+
this.hostStatus.get(c.id) !== true)
|
|
200
|
+
throw new MarifoldError_1.MarifoldError('WORKSPACE_OFFLINE', 'Workspace host is unavailable.');
|
|
201
|
+
if (read && error instanceof Error && error.message.includes('timed out'))
|
|
202
|
+
throw new MarifoldError_1.MarifoldError('WORKSPACE_TIMEOUT', 'Workspace request timed out while the bridge remained connected.');
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
if (operation === 'rename' && typeof (0, workspace_protocol_1.record)(result).name === 'string')
|
|
206
|
+
this.store.rename(c.id, String((0, workspace_protocol_1.record)(result).name));
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
async execute(id, device, operation, input, requestId = (0, workspace_protocol_1.randomId)()) {
|
|
210
|
+
const c = this.store.get(id);
|
|
211
|
+
if (c.role !== 'host')
|
|
212
|
+
throw new Error('Only the workspace host coordinates execution.');
|
|
213
|
+
const member = this.store.devices(c.id).find((d) => d.certificate.membership.deviceId === device && !d.revoked);
|
|
214
|
+
if (!member)
|
|
215
|
+
throw new Error('Execution device is not a workspace member.');
|
|
216
|
+
const peer = this.peers.get(c.id);
|
|
217
|
+
if (!peer?.online)
|
|
218
|
+
throw new MarifoldError_1.MarifoldError('WORKSPACE_OFFLINE', 'Execution device is unavailable.');
|
|
219
|
+
return peer.request(operation, input, device, member.certificate.membership.identity, requestId, operation === 'executor.execute' ? 120000 : 10000);
|
|
220
|
+
}
|
|
221
|
+
async remove(id) {
|
|
222
|
+
const c = this.store.get(id);
|
|
223
|
+
this.onMembershipRemoved?.(c.id);
|
|
224
|
+
if (c.role === 'host') {
|
|
225
|
+
const peer = this.peers.get(c.id);
|
|
226
|
+
if (peer?.online) {
|
|
227
|
+
for (const d of this.store.devices(c.id))
|
|
228
|
+
if (d.certificate.membership.deviceId !== c.deviceId)
|
|
229
|
+
await peer.revoke(d.certificate.membership.deviceId);
|
|
230
|
+
await peer.revoke(c.deviceId);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else if (this.peers.get(c.id)?.online) {
|
|
234
|
+
// Leaving locally must remain available even when the host disappears.
|
|
235
|
+
try {
|
|
236
|
+
await this.peers.get(c.id).request('leave', {}, c.hostDeviceId, c.host, (0, workspace_protocol_1.randomId)(), 3000);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
/* Host can revoke an offline former device separately. */
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
this.peers.get(c.id)?.close();
|
|
243
|
+
this.peers.delete(c.id);
|
|
244
|
+
this.hostStatus.delete(c.id);
|
|
245
|
+
this.store.remove(c.id);
|
|
246
|
+
}
|
|
247
|
+
async setExecutor(id, enabled) {
|
|
248
|
+
const connection = this.store.get(id);
|
|
249
|
+
if (connection.role !== 'guest')
|
|
250
|
+
throw new Error('The workspace host always executes its local tools.');
|
|
251
|
+
this.store.save({ ...connection, executor: enabled });
|
|
252
|
+
await this.checkHost(this.store.get(connection.id));
|
|
253
|
+
}
|
|
254
|
+
connect(c) {
|
|
255
|
+
this.peers.get(c.id)?.close();
|
|
256
|
+
const peer = new BridgePeer_1.BridgePeer({
|
|
257
|
+
bridgeUrl: c.bridgeUrl,
|
|
258
|
+
workspaceId: c.id,
|
|
259
|
+
deviceId: c.deviceId,
|
|
260
|
+
hostDeviceId: c.hostDeviceId,
|
|
261
|
+
identity: c.identity,
|
|
262
|
+
host: c.host,
|
|
263
|
+
certificate: c.certificate,
|
|
264
|
+
accept: (certificate) => c.role !== 'host' ||
|
|
265
|
+
this.store.devices(c.id).some((d) => !d.revoked && d.certificate.signature === certificate.signature),
|
|
266
|
+
onRequest: (request, sender) => this.dispatch(c, request, sender),
|
|
267
|
+
onStatus: (online) => {
|
|
268
|
+
if (!online)
|
|
269
|
+
this.hostStatus.set(c.id, false);
|
|
270
|
+
else if (c.role === 'host') {
|
|
271
|
+
for (const d of this.store.devices(c.id))
|
|
272
|
+
if (d.revoked)
|
|
273
|
+
void this.peers
|
|
274
|
+
.get(c.id)
|
|
275
|
+
?.revoke(d.certificate.membership.deviceId)
|
|
276
|
+
.catch(() => undefined);
|
|
277
|
+
}
|
|
278
|
+
else if (c.role === 'guest')
|
|
279
|
+
queueMicrotask(() => {
|
|
280
|
+
void this.checkHost(c);
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
this.peers.set(c.id, peer);
|
|
285
|
+
peer.start();
|
|
286
|
+
return peer;
|
|
287
|
+
}
|
|
288
|
+
async checkHost(c) {
|
|
289
|
+
try {
|
|
290
|
+
const peer = this.peers.get(c.id);
|
|
291
|
+
if (!peer?.online)
|
|
292
|
+
throw new Error('offline');
|
|
293
|
+
const status = (0, workspace_protocol_1.record)(await peer.request('status', { platform: os.platform(), architecture: os.arch(), executor: this.store.get(c.id).executor }, c.hostDeviceId, c.host, (0, workspace_protocol_1.randomId)(), 10000));
|
|
294
|
+
if (typeof status.name === 'string' && status.name !== c.name)
|
|
295
|
+
this.store.rename(c.id, status.name);
|
|
296
|
+
this.hostStatus.set(c.id, true);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
this.hostStatus.set(c.id, false);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async dispatch(c, request, sender) {
|
|
303
|
+
const context = { workspaceId: c.id, senderDeviceId: sender.id, hostDeviceId: c.hostDeviceId };
|
|
304
|
+
if (c.role === 'guest') {
|
|
305
|
+
if (sender.id !== c.hostDeviceId || !this.store.get(c.id).executor || !request.operation.startsWith('executor.'))
|
|
306
|
+
throw new Error('This device has not enabled this execution capability.');
|
|
307
|
+
if (!this.executorHandler)
|
|
308
|
+
throw new Error('Executor unavailable.');
|
|
309
|
+
if (['executor.lease', 'executor.assess', 'executor.artifact', 'executor.artifacts', 'executor.cancel'].includes(request.operation))
|
|
310
|
+
return this.executorHandler(request.operation, request.input, context);
|
|
311
|
+
return this.store.once(c.id, sender.id, request.id, request, () => this.executorHandler(request.operation, request.input, context));
|
|
312
|
+
}
|
|
313
|
+
if (request.operation === 'status') {
|
|
314
|
+
const info = (0, workspace_protocol_1.record)(request.input);
|
|
315
|
+
this.presence.set(`${c.id}:${sender.id}`, {
|
|
316
|
+
seen: Date.now(),
|
|
317
|
+
platform: String(info.platform ?? ''),
|
|
318
|
+
architecture: String(info.architecture ?? ''),
|
|
319
|
+
executor: info.executor === true,
|
|
320
|
+
});
|
|
321
|
+
return { name: this.store.get(c.id).name, hostDeviceId: c.hostDeviceId };
|
|
322
|
+
}
|
|
323
|
+
if (sender.certificate && ['run.events', 'artifact.read'].includes(request.operation))
|
|
324
|
+
return this.remoteHandler(request.operation, request.input, context);
|
|
325
|
+
if (sender.certificate &&
|
|
326
|
+
request.operation === 'api' &&
|
|
327
|
+
((0, workspace_protocol_1.record)(request.input).method === 'GET' || (0, workspace_protocol_1.record)(request.input).path === '/v1/terminal/snapshot'))
|
|
328
|
+
return this.remoteHandler(request.operation, request.input, context);
|
|
329
|
+
if (sender.certificate && request.operation === 'devices')
|
|
330
|
+
return { devices: this.devices(c.id) };
|
|
331
|
+
return this.store.once(c.id, sender.id, request.id, request, async () => {
|
|
332
|
+
const input = (0, workspace_protocol_1.record)(request.input);
|
|
333
|
+
if (request.operation === 'join') {
|
|
334
|
+
if (sender.certificate || typeof input.secret !== 'string')
|
|
335
|
+
throw new Error('Invalid pairing request.');
|
|
336
|
+
return {
|
|
337
|
+
name: this.store.get(c.id).name,
|
|
338
|
+
certificate: this.store.enroll(c.id, input.secret, (0, workspace_protocol_1.randomId)(), String(input.name), sender.identity),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (!sender.certificate)
|
|
342
|
+
throw new Error('Workspace membership required.');
|
|
343
|
+
if (request.operation === 'invite')
|
|
344
|
+
return { invitation: this.store.invite(c.id) };
|
|
345
|
+
if (request.operation === 'rename') {
|
|
346
|
+
this.store.rename(c.id, String(input.name));
|
|
347
|
+
return { name: this.store.get(c.id).name };
|
|
348
|
+
}
|
|
349
|
+
if (request.operation === 'devices')
|
|
350
|
+
return { devices: this.devices(c.id) };
|
|
351
|
+
if (request.operation === 'revoke' || request.operation === 'leave') {
|
|
352
|
+
const deviceId = request.operation === 'leave' ? sender.id : (0, workspace_protocol_1.identifier)(input.deviceId);
|
|
353
|
+
this.store.revoke(c.id, deviceId);
|
|
354
|
+
this.onMembershipRemoved?.(c.id, deviceId);
|
|
355
|
+
// The host's authoritative membership check takes effect immediately.
|
|
356
|
+
// Allow the departing device's final response to enter the relay first.
|
|
357
|
+
if (deviceId === sender.id)
|
|
358
|
+
setTimeout(() => {
|
|
359
|
+
void this.peers
|
|
360
|
+
.get(c.id)
|
|
361
|
+
?.revoke(deviceId)
|
|
362
|
+
.catch(() => undefined);
|
|
363
|
+
}, 1000).unref();
|
|
364
|
+
else
|
|
365
|
+
await this.peers.get(c.id)?.revoke(deviceId);
|
|
366
|
+
return { revoked: true };
|
|
367
|
+
}
|
|
368
|
+
if (!this.remoteHandler)
|
|
369
|
+
throw new Error('Workspace operation unavailable.');
|
|
370
|
+
return this.remoteHandler(request.operation, request.input, context);
|
|
371
|
+
}, () => {
|
|
372
|
+
if (request.operation === 'join')
|
|
373
|
+
this.store.validateInvitation(c.id, (0, workspace_protocol_1.record)(request.input).secret);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
exports.WorkspaceManager = WorkspaceManager;
|
|
378
|
+
//# sourceMappingURL=WorkspaceManager.js.map
|