@amalgm/shell 0.1.161 → 0.1.163
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/PURPOSE.md +98 -14
- package/README.md +16 -0
- package/dist/authorization/recovery-host.d.ts +2 -1
- package/dist/authorization/recovery-host.js +3 -15
- package/dist/authorization/recovery-host.js.map +1 -1
- package/dist/cli-arguments.d.ts +8 -0
- package/dist/cli-arguments.js +47 -0
- package/dist/cli-arguments.js.map +1 -0
- package/dist/cli.d.ts +6 -4
- package/dist/cli.js +85 -73
- package/dist/cli.js.map +1 -1
- package/dist/compose.js +4 -3
- package/dist/compose.js.map +1 -1
- package/dist/development/profile.d.ts +27 -0
- package/dist/development/profile.js +142 -0
- package/dist/development/profile.js.map +1 -0
- package/dist/files-register-host.d.ts +1 -0
- package/dist/files-register-host.js +1 -0
- package/dist/files-register-host.js.map +1 -1
- package/dist/installation/environment.d.ts +30 -0
- package/dist/installation/environment.js +28 -0
- package/dist/installation/environment.js.map +1 -0
- package/dist/installation/lease.d.ts +6 -0
- package/dist/installation/lease.js +28 -0
- package/dist/installation/lease.js.map +1 -0
- package/dist/installation/ownership.d.ts +30 -0
- package/dist/installation/ownership.js +156 -0
- package/dist/installation/ownership.js.map +1 -0
- package/dist/installation/selection.d.ts +8 -0
- package/dist/installation/selection.js +29 -0
- package/dist/installation/selection.js.map +1 -0
- package/dist/machine-host.d.ts +26 -5
- package/dist/machine-host.js +192 -69
- package/dist/machine-host.js.map +1 -1
- package/dist/main.js +56 -2
- package/dist/main.js.map +1 -1
- package/dist/platform-product-host.d.ts +9 -9
- package/dist/platform-product-host.js +17 -17
- package/dist/platform-product-host.js.map +1 -1
- package/dist/platform-tools.d.ts +4 -3
- package/dist/platform-tools.js +34 -37
- package/dist/platform-tools.js.map +1 -1
- package/dist/process-lifecycle.d.ts +8 -0
- package/dist/process-lifecycle.js +32 -0
- package/dist/process-lifecycle.js.map +1 -0
- package/dist/runtime-command-host.d.ts +7 -0
- package/dist/runtime-command-host.js +49 -13
- package/dist/runtime-command-host.js.map +1 -1
- package/dist/service/adapter.d.ts +36 -0
- package/dist/service/adapter.js +24 -0
- package/dist/service/adapter.js.map +1 -0
- package/dist/service/cli.d.ts +8 -0
- package/dist/service/cli.js +196 -0
- package/dist/service/cli.js.map +1 -0
- package/dist/service/control.d.ts +29 -0
- package/dist/service/control.js +68 -0
- package/dist/service/control.js.map +1 -0
- package/dist/service/index.d.ts +9 -0
- package/dist/service/index.js +10 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/keepalive.d.ts +11 -0
- package/dist/service/keepalive.js +180 -0
- package/dist/service/keepalive.js.map +1 -0
- package/dist/service/launchd.d.ts +9 -0
- package/dist/service/launchd.js +105 -0
- package/dist/service/launchd.js.map +1 -0
- package/dist/service/owner.d.ts +4 -0
- package/dist/service/owner.js +14 -0
- package/dist/service/owner.js.map +1 -0
- package/dist/service/portable.d.ts +10 -0
- package/dist/service/portable.js +32 -0
- package/dist/service/portable.js.map +1 -0
- package/dist/service/registration.d.ts +82 -0
- package/dist/service/registration.js +114 -0
- package/dist/service/registration.js.map +1 -0
- package/dist/service/systemd.d.ts +9 -0
- package/dist/service/systemd.js +94 -0
- package/dist/service/systemd.js.map +1 -0
- package/dist/service/windows-task.d.ts +16 -0
- package/dist/service/windows-task.js +82 -0
- package/dist/service/windows-task.js.map +1 -0
- package/dist/service/windows.d.ts +2 -0
- package/dist/service/windows.js +65 -0
- package/dist/service/windows.js.map +1 -0
- package/dist/user-ground-host.d.ts +3 -0
- package/dist/user-ground-host.js +13 -0
- package/dist/user-ground-host.js.map +1 -1
- package/npm-shrinkwrap.json +49 -83
- package/package.json +9 -9
package/dist/machine-host.js
CHANGED
|
@@ -2,40 +2,23 @@ import { randomBytes, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
3
3
|
import { homedir as osHomedir, hostname, platform, release } from "node:os";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
import { createRestServer as createAgentsRest } from "@amalgm/agents/http";
|
|
7
|
-
import { createMcpServer as createAgentsMcp, createMcpTools as createAgentsMcpTools } from "@amalgm/agents/mcp";
|
|
8
|
-
import { createMcpServer as createAppsMcp, createMcpTools as createAppsMcpTools } from "@amalgm/apps";
|
|
9
|
-
import { createBrowser } from "@amalgm/browser";
|
|
10
|
-
import { createBrowserMcpServer, createBrowserMcpTools } from "@amalgm/browser/mcp";
|
|
11
|
-
import { createNodeAutomationProcessHost } from "@amalgm/automations";
|
|
12
|
-
import { createAutomationMcpServer } from "@amalgm/automations/mcp";
|
|
13
|
-
import { createShellChannelsCommands } from "@amalgm/channels/cli";
|
|
14
|
-
import { createChannelsMcpServer } from "@amalgm/channels/mcp";
|
|
15
|
-
import { createChatMcpServer } from "@amalgm/chat/mcp";
|
|
16
|
-
import { buildRootManifest, joinMachine, mergeRecordAuth, resumeMachine, scopedAmalgmDir, userMachineDir, } from "@amalgm/core/identity";
|
|
17
|
-
import { addFilesWorkspace, registerFilesWorkspace, } from "@amalgm/live/files";
|
|
18
|
-
import { createMcpServer as createToolsMcp, } from "@amalgm/tools";
|
|
19
|
-
import { createToolboxHttpServer } from "@amalgm/tools/http";
|
|
5
|
+
import { buildRootManifest, approveMachineSetup, adoptMachineSetup, joinApprovedMachine, mergeRecordAuth, resumeMachine, resolveRuntimeBinding, runtimeBindingsMatch, runtimeBindingFromRecord, RUNTIME_IDENTITY_PATH, createRuntimeIdentity, runtimeRootSegments, resolveRuntimeEndpoint, scopedAmalgmDir, userMachineDir, } from "@amalgm/core/identity";
|
|
20
6
|
import { atomicJson, atomicJsonSet, ensurePrivateDir, readJson, } from "./filesystem.js";
|
|
21
7
|
import { emitFilesStage, } from "./files-observability.js";
|
|
22
8
|
import { FilesStageRecorder, serializeStages } from "./files-trace.js";
|
|
23
|
-
import { createShellChatComposition } from "./chat-composition.js";
|
|
24
9
|
import { createShellAuthorization, loadInstallationDeviceKey, } from "./authorization/index.js";
|
|
25
10
|
import { recoverMachineAuthorization, } from "./authorization/recovery-host.js";
|
|
26
11
|
import { postRegistration } from "./authorization/registration-http.js";
|
|
27
12
|
import { postSetupJson, setupOrigin } from "./authorization/setup-http.js";
|
|
28
13
|
import { installBuiltinAgents } from "./builtin-agents.js";
|
|
14
|
+
import { InstallationOwnership } from "./installation/ownership.js";
|
|
15
|
+
import { runtimeAppOrigin, runtimeProxyOrigin, runtimeWireUrl } from "./installation/environment.js";
|
|
29
16
|
import { forwardMount } from "./forward.js";
|
|
30
17
|
import { createGateway } from "./gateway.js";
|
|
31
18
|
import { localMount } from "./local-host.js";
|
|
32
|
-
import { connectOfficialMcpServer } from "./official-mcp-host.js";
|
|
33
|
-
import { createPlatformProductHost, } from "./platform-product-host.js";
|
|
34
19
|
import { createPtyHost } from "./pty-host.js";
|
|
35
|
-
import { callRuntimeCommand, inspectRuntimeCommands, publishRuntimeCommands, runtimeCommandConnection, unpublishRuntimeCommands, } from "./runtime-command-host.js";
|
|
20
|
+
import { callRuntimeCommand, assertRuntimeCanStart, inspectRuntimeCommands, publishRuntimeCommands, runtimeCommandConnection, unpublishRuntimeCommands, } from "./runtime-command-host.js";
|
|
36
21
|
import { shellReleaseCatalog, shellReleaseVerifier } from "./release-catalog.js";
|
|
37
|
-
import { platformToolDefinitions } from "./platform-tools.js";
|
|
38
|
-
import { UserGroundHost, } from "./user-ground-host.js";
|
|
39
22
|
/** The production effects around Core's machine-setup operation. It does not
|
|
40
23
|
* decide ordering, readiness, or installation selection; those remain Core. */
|
|
41
24
|
export class MachineHost {
|
|
@@ -44,19 +27,41 @@ export class MachineHost {
|
|
|
44
27
|
amalgmRoot;
|
|
45
28
|
deviceFile;
|
|
46
29
|
ports;
|
|
30
|
+
binding;
|
|
31
|
+
ownership;
|
|
47
32
|
selectedUserEmail;
|
|
48
33
|
groundHost = null;
|
|
49
34
|
running = new Set();
|
|
50
35
|
stages;
|
|
51
36
|
constructor(options) {
|
|
52
37
|
setupOrigin(options.appUrl);
|
|
53
|
-
this.
|
|
38
|
+
this.binding = resolveRuntimeBinding(options);
|
|
39
|
+
if (options.services && this.binding.runtimeLabel !== 'local')
|
|
40
|
+
throw new Error('Only a local profile may override runtime service selection.');
|
|
41
|
+
runtimeAppOrigin(this.binding, options.appUrl);
|
|
42
|
+
for (const [role, endpoint] of [['proxy', options.proxyUrl], ['wire', options.wireUrl],
|
|
43
|
+
['automations', options.platformProducts?.automationsBaseUrl], ['channels', options.platformProducts?.channelsBaseUrl]]) {
|
|
44
|
+
if (endpoint)
|
|
45
|
+
resolveRuntimeEndpoint(this.binding, role, endpoint, options.appUrl);
|
|
46
|
+
}
|
|
47
|
+
this.options = { ...options, ...this.binding };
|
|
54
48
|
this.stages = new FilesStageRecorder(options.onFilesStage);
|
|
55
49
|
this.homedir = options.homedir ?? osHomedir();
|
|
56
|
-
this.amalgmRoot = options.amalgmRoot ?? join(this.homedir,
|
|
50
|
+
this.amalgmRoot = resolve(options.amalgmRoot ?? join(this.homedir, ...runtimeRootSegments(this.binding)));
|
|
51
|
+
this.ownership = new InstallationOwnership({
|
|
52
|
+
home: this.homedir,
|
|
53
|
+
installationRoot: this.amalgmRoot,
|
|
54
|
+
binding: this.binding,
|
|
55
|
+
});
|
|
57
56
|
this.deviceFile = join(this.amalgmRoot, ".amalgm", "device.json");
|
|
58
57
|
this.selectedUserEmail = normalizeEmail(options.userEmail);
|
|
59
|
-
|
|
58
|
+
try {
|
|
59
|
+
this.ownership.initialize(() => this.ensureInstallationRoot());
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
this.ownership.close();
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
60
65
|
const deviceStore = {
|
|
61
66
|
load: async () => readJson(this.deviceFile),
|
|
62
67
|
save: async (device) => atomicJson(this.deviceFile, device),
|
|
@@ -96,17 +101,17 @@ export class MachineHost {
|
|
|
96
101
|
: {}),
|
|
97
102
|
...(this.options.authorizationPlatform
|
|
98
103
|
? { platform: this.options.authorizationPlatform }
|
|
99
|
-
:
|
|
104
|
+
: {}),
|
|
100
105
|
});
|
|
101
106
|
const response = await postRegistration(globalThis.fetch, this.options.appUrl, {
|
|
102
107
|
...body, authorization_key_thumbprint: signer.keyThumbprint,
|
|
103
108
|
}, token, signer);
|
|
109
|
+
runtimeWireUrl(this.binding, this.options.appUrl, text(response.tunnel_url), this.options.wireUrl);
|
|
104
110
|
const email = normalizeEmail(response.user_email);
|
|
105
111
|
if (!email)
|
|
106
112
|
throw Object.assign(new Error("registration did not return a user email"), {
|
|
107
113
|
code: "missing_user_email",
|
|
108
114
|
});
|
|
109
|
-
this.selectedUserEmail = email;
|
|
110
115
|
return response;
|
|
111
116
|
},
|
|
112
117
|
};
|
|
@@ -138,12 +143,14 @@ export class MachineHost {
|
|
|
138
143
|
throw Object.assign(new Error("machine tunnel credentials are missing"), { code: "tunnel_credentials_missing" });
|
|
139
144
|
}
|
|
140
145
|
await this.groundHost?.close();
|
|
146
|
+
const { UserGroundHost } = await import("./user-ground-host.js");
|
|
141
147
|
this.groundHost = new UserGroundHost({
|
|
142
148
|
amalgmRoot: this.amalgmRoot,
|
|
143
149
|
deviceId: identity.deviceId,
|
|
144
150
|
tunnelUrl,
|
|
145
151
|
tunnelToken,
|
|
146
152
|
runtimeVersion: this.options.runtimeVersion,
|
|
153
|
+
reserveWritableRoot: (path) => { this.ownership.reserve(path, identity.userEmail); },
|
|
147
154
|
openWatch: this.options.openWatch,
|
|
148
155
|
onWatchHealth: this.options.onWatchHealth,
|
|
149
156
|
onDetectScan: this.options.onDetectScan,
|
|
@@ -179,19 +186,79 @@ export class MachineHost {
|
|
|
179
186
|
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
180
187
|
};
|
|
181
188
|
}
|
|
182
|
-
|
|
183
|
-
|
|
189
|
+
async approveSetup(input) {
|
|
190
|
+
const approved = await approveMachineSetup({
|
|
184
191
|
...input,
|
|
185
192
|
appUrl: this.options.appUrl,
|
|
186
193
|
platform: `${platform()}-${release()}`,
|
|
187
194
|
appVersion: this.options.runtimeVersion,
|
|
188
|
-
|
|
195
|
+
...this.binding,
|
|
189
196
|
branch: this.options.branch ?? "main",
|
|
197
|
+
...(this.binding.localSession ? { localSession: this.binding.localSession } : {}),
|
|
190
198
|
}, this.ports);
|
|
199
|
+
const email = approved.grant.approved_by.email;
|
|
200
|
+
if (this.options.userEmail && normalizeEmail(this.options.userEmail) !== email) {
|
|
201
|
+
throw Object.assign(new Error('Setup was approved by a different account than the selected user.'), { code: 'approved_user_mismatch' });
|
|
202
|
+
}
|
|
203
|
+
this.selectedUserEmail = email;
|
|
204
|
+
return approved;
|
|
205
|
+
}
|
|
206
|
+
async join(input) {
|
|
207
|
+
await this.ownership.waitForRuntime('setup');
|
|
208
|
+
try {
|
|
209
|
+
const approved = await this.approveSetup(input);
|
|
210
|
+
this.ownership.acquireRuntime(this.requireSelectedUser());
|
|
211
|
+
this.assertRuntimeCanStart();
|
|
212
|
+
return await joinApprovedMachine(approved, this.ports);
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
this.ownership.releaseRuntime('setup');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** Shared, finite UI setup. An existing owner is never disturbed by adoption. */
|
|
219
|
+
async setup(input) {
|
|
220
|
+
await this.ownership.waitForRuntime('setup');
|
|
221
|
+
let enrolling = false;
|
|
222
|
+
try {
|
|
223
|
+
const approved = await this.approveSetup(input);
|
|
224
|
+
const machine = await this.loadMachine();
|
|
225
|
+
if (machine?.ready) {
|
|
226
|
+
await adoptMachineSetup(approved, machine, this.ports);
|
|
227
|
+
return { disposition: 'adopted', userEmail: this.requireSelectedUser(),
|
|
228
|
+
userId: approved.grant.approved_by.user_id, computerId: text(machine.publicRecord.computer_id),
|
|
229
|
+
deviceId: approved.device.device_id };
|
|
230
|
+
}
|
|
231
|
+
this.ownership.acquireRuntime(this.requireSelectedUser());
|
|
232
|
+
this.assertRuntimeCanStart();
|
|
233
|
+
enrolling = true;
|
|
234
|
+
const ready = await joinApprovedMachine(approved, this.ports);
|
|
235
|
+
return { disposition: 'enrolled', userEmail: this.requireSelectedUser(),
|
|
236
|
+
userId: ready.userId, computerId: ready.computerId, deviceId: ready.deviceId };
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
try {
|
|
240
|
+
if (enrolling) {
|
|
241
|
+
await this.drainRuntime();
|
|
242
|
+
this.ownership.releaseRuntime(this.requireSelectedUser());
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
this.ownership.releaseRuntime('setup');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
191
249
|
}
|
|
192
250
|
resume() {
|
|
193
251
|
this.resolveUserForResume();
|
|
194
|
-
|
|
252
|
+
this.ownership.acquireRuntime(this.requireSelectedUser());
|
|
253
|
+
this.assertRuntimeCanStart();
|
|
254
|
+
return resumeMachine(this.ports, { services: this.options.services });
|
|
255
|
+
}
|
|
256
|
+
assertRuntimeCanStart() {
|
|
257
|
+
const deviceId = text(readJson(this.deviceFile)?.device_id);
|
|
258
|
+
if (!deviceId)
|
|
259
|
+
return;
|
|
260
|
+
assertRuntimeCanStart({ userRoot: this.paths().userRoot, deviceId,
|
|
261
|
+
...this.binding, userScope: this.requireSelectedUser() });
|
|
195
262
|
}
|
|
196
263
|
async recoverAuthorizationKey(input = {}) {
|
|
197
264
|
this.resolveUserForResume();
|
|
@@ -204,7 +271,7 @@ export class MachineHost {
|
|
|
204
271
|
machine,
|
|
205
272
|
appUrl: this.options.appUrl,
|
|
206
273
|
runtimeVersion: this.options.runtimeVersion,
|
|
207
|
-
|
|
274
|
+
...this.binding,
|
|
208
275
|
branch: this.options.branch ?? "main",
|
|
209
276
|
fallbackName: defaultMachineName(),
|
|
210
277
|
fallbackPlatform: `${platform()}-${release()}`,
|
|
@@ -226,6 +293,7 @@ export class MachineHost {
|
|
|
226
293
|
const deviceId = text(device?.device_id) || null;
|
|
227
294
|
if (!deviceId) {
|
|
228
295
|
return {
|
|
296
|
+
binding: this.binding,
|
|
229
297
|
user,
|
|
230
298
|
deviceId: null,
|
|
231
299
|
registration: "absent",
|
|
@@ -240,6 +308,8 @@ export class MachineHost {
|
|
|
240
308
|
}
|
|
241
309
|
const paths = this.paths(user);
|
|
242
310
|
const publicRecord = readJson(paths.publicRecord);
|
|
311
|
+
if (publicRecord)
|
|
312
|
+
this.assertRecordBinding(publicRecord);
|
|
243
313
|
const secretAuth = readJson(paths.secretAuth);
|
|
244
314
|
const installation = readJson(paths.installation);
|
|
245
315
|
const storedStatus = readJson(paths.status);
|
|
@@ -251,11 +321,12 @@ export class MachineHost {
|
|
|
251
321
|
? await inspectRuntimeCommands({
|
|
252
322
|
userRoot: paths.userRoot,
|
|
253
323
|
deviceId,
|
|
254
|
-
|
|
324
|
+
...this.binding,
|
|
255
325
|
userScope: user,
|
|
256
326
|
})
|
|
257
327
|
: { published: false, running: false, pid: null, port: null };
|
|
258
328
|
return {
|
|
329
|
+
binding: this.binding,
|
|
259
330
|
user,
|
|
260
331
|
deviceId,
|
|
261
332
|
registration,
|
|
@@ -282,6 +353,7 @@ export class MachineHost {
|
|
|
282
353
|
primitive: "download", stage: "files-add", status: "started", workspaceId,
|
|
283
354
|
});
|
|
284
355
|
try {
|
|
356
|
+
const { addFilesWorkspace } = await import("@amalgm/live/files");
|
|
285
357
|
const added = await addFilesWorkspace(workspaceId, destinationParent, this.groundHost.filesCommandPorts().add);
|
|
286
358
|
const result = { ...added, watch: this.groundHost.addedWorkspaceEvidence(added.workspaceId) };
|
|
287
359
|
emitFilesStage(this.stages.observe, {
|
|
@@ -318,7 +390,7 @@ export class MachineHost {
|
|
|
318
390
|
primitive: "register", stage: "durable-declaration", status: "started", path: requestedPath,
|
|
319
391
|
});
|
|
320
392
|
try {
|
|
321
|
-
const declared = await registerFilesWorkspace(requestedPath, this.groundHost.filesCommandPorts().register);
|
|
393
|
+
const declared = await (await import("@amalgm/live/files")).registerFilesWorkspace(requestedPath, this.groundHost.filesCommandPorts().register);
|
|
322
394
|
emitFilesStage(this.stages.observe, {
|
|
323
395
|
primitive: "register",
|
|
324
396
|
stage: "durable-declaration",
|
|
@@ -383,6 +455,10 @@ export class MachineHost {
|
|
|
383
455
|
return Object.freeze(adapter);
|
|
384
456
|
}
|
|
385
457
|
async close(options = {}) {
|
|
458
|
+
await this.drainRuntime(options);
|
|
459
|
+
this.ownership.close();
|
|
460
|
+
}
|
|
461
|
+
async drainRuntime(options = {}) {
|
|
386
462
|
const runtimes = [...this.running];
|
|
387
463
|
this.running.clear();
|
|
388
464
|
await this.groundHost?.close(options);
|
|
@@ -398,11 +474,20 @@ export class MachineHost {
|
|
|
398
474
|
nowIso: () => new Date().toISOString(),
|
|
399
475
|
randomHex: (bytes) => randomBytes(bytes).toString("hex"),
|
|
400
476
|
});
|
|
401
|
-
|
|
402
|
-
|
|
477
|
+
const previousBinding = existing?.runtimeBinding;
|
|
478
|
+
if (previousBinding && !runtimeBindingsMatch(previousBinding, this.binding)) {
|
|
479
|
+
throw new Error('This installation root belongs to another runtime binding.');
|
|
480
|
+
}
|
|
481
|
+
if (existing?.installationId && !previousBinding && this.binding.runtimeLabel !== 'main') {
|
|
482
|
+
throw new Error('An unbound existing installation must be adopted explicitly; use a fresh root for preview/local.');
|
|
483
|
+
}
|
|
484
|
+
if (!existing?.installationId || !previousBinding) {
|
|
485
|
+
atomicJson(manifestFile, { ...manifest, runtimeBinding: this.binding });
|
|
486
|
+
}
|
|
403
487
|
}
|
|
404
488
|
async openChannelsCommands() {
|
|
405
489
|
const access = runtimeCommandConnection(await this.runtimeCommandAddress());
|
|
490
|
+
const { createShellChannelsCommands } = await import("@amalgm/channels/cli");
|
|
406
491
|
return createShellChannelsCommands(access);
|
|
407
492
|
}
|
|
408
493
|
async runtimeCommandAddress() {
|
|
@@ -419,7 +504,7 @@ export class MachineHost {
|
|
|
419
504
|
return {
|
|
420
505
|
userRoot: this.paths().userRoot,
|
|
421
506
|
deviceId,
|
|
422
|
-
|
|
507
|
+
...this.binding,
|
|
423
508
|
userScope: userEmail,
|
|
424
509
|
};
|
|
425
510
|
}
|
|
@@ -440,7 +525,7 @@ export class MachineHost {
|
|
|
440
525
|
: {}),
|
|
441
526
|
...(this.options.authorizationPlatform
|
|
442
527
|
? { platform: this.options.authorizationPlatform }
|
|
443
|
-
:
|
|
528
|
+
: {}),
|
|
444
529
|
};
|
|
445
530
|
}
|
|
446
531
|
paths(email = this.requireSelectedUser()) {
|
|
@@ -494,6 +579,17 @@ export class MachineHost {
|
|
|
494
579
|
});
|
|
495
580
|
}
|
|
496
581
|
}
|
|
582
|
+
assertRecordBinding(record) {
|
|
583
|
+
const stored = runtimeBindingFromRecord(record);
|
|
584
|
+
if (!runtimeBindingsMatch(stored, this.binding)
|
|
585
|
+
|| (typeof record.app_url === 'string' && setupOrigin(record.app_url) !== setupOrigin(this.options.appUrl))) {
|
|
586
|
+
throw Object.assign(new Error('Stored machine belongs to another runtime binding or control plane.'), {
|
|
587
|
+
code: 'machine_binding_mismatch',
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
if (record.tunnel_url !== undefined)
|
|
591
|
+
runtimeWireUrl(this.binding, this.options.appUrl, text(record.tunnel_url), this.options.wireUrl);
|
|
592
|
+
}
|
|
497
593
|
async loadMachine() {
|
|
498
594
|
if (!this.selectedUserEmail)
|
|
499
595
|
this.resolveUserForResume();
|
|
@@ -506,12 +602,15 @@ export class MachineHost {
|
|
|
506
602
|
const status = readJson(paths.status);
|
|
507
603
|
if (!publicRecord || !secretAuth || !installation || !status)
|
|
508
604
|
return null;
|
|
605
|
+
this.assertRecordBinding(publicRecord);
|
|
509
606
|
return { publicRecord, secretAuth, installation, ready: status.ready === true };
|
|
510
607
|
}
|
|
511
608
|
async saveMachine(machine) {
|
|
609
|
+
this.assertRecordBinding(machine.publicRecord);
|
|
512
610
|
const recordEmail = normalizeEmail(machine.publicRecord.user_email);
|
|
513
611
|
if (recordEmail)
|
|
514
612
|
this.selectedUserEmail = recordEmail;
|
|
613
|
+
this.ownership.acquireRuntime(this.requireSelectedUser());
|
|
515
614
|
const paths = this.paths();
|
|
516
615
|
atomicJsonSet([
|
|
517
616
|
[paths.publicRecord, machine.publicRecord],
|
|
@@ -535,6 +634,9 @@ export class MachineHost {
|
|
|
535
634
|
const userRoot = this.paths(userEmail).userRoot;
|
|
536
635
|
const workspaceRoot = join(userRoot, "workspaces");
|
|
537
636
|
const runtimeToken = randomBytes(32).toString("base64url");
|
|
637
|
+
const runtimeIdentity = createRuntimeIdentity({
|
|
638
|
+
binding: this.binding, userScope: userEmail, deviceId, instanceId: randomUUID(),
|
|
639
|
+
});
|
|
538
640
|
const env = {
|
|
539
641
|
...process.env,
|
|
540
642
|
AMALGM_ROOT: this.amalgmRoot,
|
|
@@ -545,7 +647,8 @@ export class MachineHost {
|
|
|
545
647
|
AMALGM_RUNTIME_LABEL: this.options.runtimeLabel ?? "main",
|
|
546
648
|
AMALGM_BRANCH: this.options.branch ?? "main",
|
|
547
649
|
AMALGM_RUNTIME_TOKEN: runtimeToken,
|
|
548
|
-
|
|
650
|
+
AMALGM_LOCAL_SESSION: this.binding.localSession,
|
|
651
|
+
AMALGM_PROXY_URL: runtimeProxyOrigin(this.binding, this.options.appUrl, this.options.proxyUrl),
|
|
549
652
|
AMALGM_WORKSPACES_DIR: workspaceRoot,
|
|
550
653
|
};
|
|
551
654
|
const pty = createPtyHost({
|
|
@@ -554,37 +657,48 @@ export class MachineHost {
|
|
|
554
657
|
});
|
|
555
658
|
const services = new Set(machine.installation.services);
|
|
556
659
|
const serviceStateRoot = join(this.paths(userEmail).machineDir, "services");
|
|
557
|
-
const
|
|
558
|
-
|
|
660
|
+
const agentsSdk = services.has("agents") ? await import("@amalgm/agents") : null;
|
|
661
|
+
const agentsMcpSdk = agentsSdk ? await import("@amalgm/agents/mcp") : null;
|
|
662
|
+
const agentsHttpSdk = agentsSdk ? await import("@amalgm/agents/http") : null;
|
|
663
|
+
const toolsSdk = services.has("tools") ? await import("@amalgm/tools") : null;
|
|
664
|
+
const toolsHttpSdk = toolsSdk ? await import("@amalgm/tools/http") : null;
|
|
665
|
+
const includeApps = services.has("apps") || machine.installation.selection === "all";
|
|
666
|
+
const includeBrowser = services.has("browser") || machine.installation.selection === "all";
|
|
667
|
+
const appsSdk = toolsSdk && includeApps ? await import("@amalgm/apps") : null;
|
|
668
|
+
const browserSdk = toolsSdk && includeBrowser ? await import("@amalgm/browser") : null;
|
|
669
|
+
const browserMcpSdk = browserSdk ? await import("@amalgm/browser/mcp") : null;
|
|
670
|
+
const agentsService = agentsSdk
|
|
671
|
+
? new agentsSdk.Agents({ stateDir: join(serviceStateRoot, "agents") })
|
|
559
672
|
: null;
|
|
560
673
|
if (agentsService)
|
|
561
674
|
installBuiltinAgents(agentsService);
|
|
562
|
-
const agentsMcp = agentsService ?
|
|
563
|
-
const agentsRest = agentsService ?
|
|
675
|
+
const agentsMcp = agentsService ? agentsMcpSdk.createMcpServer({ agents: agentsService }) : null;
|
|
676
|
+
const agentsRest = agentsService ? agentsHttpSdk.createRestServer({ agents: agentsService }) : null;
|
|
564
677
|
const agentsAddress = agentsRest ? await agentsRest.listen(0, "127.0.0.1") : null;
|
|
565
678
|
const platformTools = services.has("tools")
|
|
566
|
-
? await platformToolDefinitions({
|
|
567
|
-
agents: agentsMcp
|
|
568
|
-
apps: { tools:
|
|
569
|
-
browser: { tools: createBrowserMcpTools({}) },
|
|
679
|
+
? await (await import("./platform-tools.js")).platformToolDefinitions({
|
|
680
|
+
...(agentsMcp ? { agents: agentsMcp } : {}),
|
|
681
|
+
...(appsSdk ? { apps: { tools: appsSdk.createMcpTools({}) } } : {}),
|
|
682
|
+
...(browserMcpSdk ? { browser: { tools: browserMcpSdk.createBrowserMcpTools({}) } } : {}),
|
|
683
|
+
chat: services.has("chat"),
|
|
570
684
|
products: {
|
|
571
|
-
automations: Boolean(this.options.platformProducts),
|
|
572
|
-
channels: Boolean(this.options.platformProducts),
|
|
685
|
+
automations: Boolean(this.options.platformProducts?.automationsBaseUrl),
|
|
686
|
+
channels: Boolean(this.options.platformProducts?.channelsBaseUrl),
|
|
573
687
|
},
|
|
574
688
|
})
|
|
575
689
|
: [];
|
|
576
690
|
const toolsMcp = services.has("tools")
|
|
577
|
-
?
|
|
691
|
+
? toolsSdk.createMcpServer({
|
|
578
692
|
databaseFile: join(serviceStateRoot, "tools", "tools.db"),
|
|
579
693
|
deploymentDir: join(userRoot, "toolbox"),
|
|
580
694
|
systemTools: [...platformTools],
|
|
581
695
|
})
|
|
582
696
|
: null;
|
|
583
|
-
const toolsManagement = toolsMcp ?
|
|
697
|
+
const toolsManagement = toolsMcp ? toolsSdk.createMcpServer({
|
|
584
698
|
toolbox: toolsMcp.toolbox,
|
|
585
699
|
mcp: { loadout: [], includeManagement: true },
|
|
586
700
|
}) : null;
|
|
587
|
-
const toolsHttp = toolsMcp ? createToolboxHttpServer({ toolbox: toolsMcp.toolbox }) : null;
|
|
701
|
+
const toolsHttp = toolsMcp ? toolsHttpSdk.createToolboxHttpServer({ toolbox: toolsMcp.toolbox }) : null;
|
|
588
702
|
const toolsAddress = toolsHttp ? await toolsHttp.listen(0, "127.0.0.1") : null;
|
|
589
703
|
if (services.has("chat") && (!agentsService || !toolsMcp)) {
|
|
590
704
|
throw new Error("The Chat release requires Agents and Tools");
|
|
@@ -600,7 +714,7 @@ export class MachineHost {
|
|
|
600
714
|
: {}),
|
|
601
715
|
...(this.options.authorizationPlatform
|
|
602
716
|
? { platform: this.options.authorizationPlatform }
|
|
603
|
-
:
|
|
717
|
+
: {}),
|
|
604
718
|
});
|
|
605
719
|
let gateway = null;
|
|
606
720
|
const gatewayPort = () => {
|
|
@@ -610,7 +724,7 @@ export class MachineHost {
|
|
|
610
724
|
return address.port;
|
|
611
725
|
};
|
|
612
726
|
const chat = services.has("chat") && agentsService && toolsMcp
|
|
613
|
-
? await createShellChatComposition({
|
|
727
|
+
? await (await import("./chat-composition.js")).createShellChatComposition({
|
|
614
728
|
ownerUserId: userId,
|
|
615
729
|
computerId,
|
|
616
730
|
stateDir: join(serviceStateRoot, "chat"),
|
|
@@ -625,13 +739,15 @@ export class MachineHost {
|
|
|
625
739
|
})
|
|
626
740
|
: null;
|
|
627
741
|
const products = this.options.platformProducts
|
|
628
|
-
? createPlatformProductHost({
|
|
742
|
+
? await (await import("./platform-product-host.js")).createPlatformProductHost({
|
|
629
743
|
...this.options.platformProducts,
|
|
630
744
|
authorization,
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
745
|
+
...(this.options.platformProducts.automationsBaseUrl ? {
|
|
746
|
+
processes: (await import("@amalgm/automations")).createNodeAutomationProcessHost({
|
|
747
|
+
defaultCwd: this.homedir,
|
|
748
|
+
environment: { ...process.env, HOME: this.homedir },
|
|
749
|
+
}),
|
|
750
|
+
} : {}),
|
|
635
751
|
...(toolsMcp ? { toolbox: toolsMcp.toolbox } : {}),
|
|
636
752
|
...(chat ? { actions: { 'chat.chat_agent_run': chatAgentAction(chat.agentRunner) } } : {}),
|
|
637
753
|
...(chat ? { inboundExecutor: {
|
|
@@ -647,23 +763,27 @@ export class MachineHost {
|
|
|
647
763
|
} } : {}),
|
|
648
764
|
})
|
|
649
765
|
: null;
|
|
650
|
-
const automationsMcp = products
|
|
651
|
-
? await connectOfficialMcpServer(createAutomationMcpServer(products.automations), { name: 'amalgm-automations', version: '0.2.5' })
|
|
766
|
+
const automationsMcp = products?.automations
|
|
767
|
+
? await (await import("./official-mcp-host.js")).connectOfficialMcpServer((await import("@amalgm/automations/mcp")).createAutomationMcpServer(products.automations), { name: 'amalgm-automations', version: '0.2.5' })
|
|
652
768
|
: null;
|
|
653
|
-
const channelsMcp = products
|
|
654
|
-
? await connectOfficialMcpServer(createChannelsMcpServer(products.channels), { name: 'amalgm-channels', version: '0.1.0' })
|
|
769
|
+
const channelsMcp = products?.channels
|
|
770
|
+
? await (await import("./official-mcp-host.js")).connectOfficialMcpServer((await import("@amalgm/channels/mcp")).createChannelsMcpServer(products.channels), { name: 'amalgm-channels', version: '0.1.0' })
|
|
655
771
|
: null;
|
|
656
|
-
const chatMcp = chat ? createChatMcpServer(chat.chat, { agentRunner: chat.agentRunner }) : null;
|
|
772
|
+
const chatMcp = chat ? (await import("@amalgm/chat/mcp")).createChatMcpServer(chat.chat, { agentRunner: chat.agentRunner }) : null;
|
|
657
773
|
let appsMcp = null;
|
|
658
774
|
const selectedAppsMcp = () => {
|
|
659
|
-
|
|
775
|
+
if (!appsSdk)
|
|
776
|
+
throw new Error("Apps is not selected in this installation.");
|
|
777
|
+
appsMcp ??= appsSdk.createMcpServer({ stateDir: join(serviceStateRoot, "apps") });
|
|
660
778
|
return appsMcp;
|
|
661
779
|
};
|
|
662
780
|
let browserMcp = null;
|
|
663
781
|
let browserService = null;
|
|
664
782
|
const selectedBrowserMcp = () => {
|
|
665
|
-
|
|
666
|
-
|
|
783
|
+
if (!browserSdk || !browserMcpSdk)
|
|
784
|
+
throw new Error("Browser is not selected in this installation.");
|
|
785
|
+
browserService ??= browserSdk.createBrowser({ root: join(serviceStateRoot, "browser") });
|
|
786
|
+
browserMcp ??= browserMcpSdk.createBrowserMcpServer(browserService);
|
|
667
787
|
return browserMcp;
|
|
668
788
|
};
|
|
669
789
|
const closeSelectedProducts = async () => {
|
|
@@ -678,6 +798,7 @@ export class MachineHost {
|
|
|
678
798
|
env,
|
|
679
799
|
exemptPrefixes: ["/chat/egress/"],
|
|
680
800
|
mounts: [
|
|
801
|
+
{ prefix: RUNTIME_IDENTITY_PATH, handle: async () => runtimeIdentity },
|
|
681
802
|
{
|
|
682
803
|
prefix: "/entities/register",
|
|
683
804
|
handle: (body) => this.journey(body, () => this.registerWorkspace(bodyText(body, "path"))),
|
|
@@ -696,11 +817,12 @@ export class MachineHost {
|
|
|
696
817
|
protocol: "mcp-json-rpc",
|
|
697
818
|
handle: (body) => toolsMcp.handle(body),
|
|
698
819
|
}] : []),
|
|
699
|
-
...(
|
|
820
|
+
...(appsSdk ? [{
|
|
700
821
|
prefix: "/mcp/apps",
|
|
701
822
|
protocol: "mcp-json-rpc",
|
|
702
823
|
handle: (body) => selectedAppsMcp().handle(body),
|
|
703
|
-
},
|
|
824
|
+
}] : []),
|
|
825
|
+
...(browserSdk ? [{
|
|
704
826
|
prefix: "/mcp/browser",
|
|
705
827
|
protocol: "mcp-json-rpc",
|
|
706
828
|
handle: (body) => selectedBrowserMcp().handle(body),
|
|
@@ -757,9 +879,10 @@ export class MachineHost {
|
|
|
757
879
|
await this.groundHost.activateRuntimeTunnel(address.port, runtimeToken);
|
|
758
880
|
products?.runner.start();
|
|
759
881
|
commandPublication = {
|
|
882
|
+
instanceId: runtimeIdentity.instanceId,
|
|
760
883
|
userRoot: this.paths(userEmail).userRoot,
|
|
761
884
|
deviceId,
|
|
762
|
-
|
|
885
|
+
...this.binding,
|
|
763
886
|
userScope: userEmail,
|
|
764
887
|
port: address.port,
|
|
765
888
|
token: runtimeToken,
|