@amalgm/shell 0.1.162 → 0.1.164-main.34182640231
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 +163 -17
- package/README.md +74 -11
- package/config/releases/trust/runtime/main.root.json +84 -0
- package/config/releases/trust/runtime/preview.root.json +84 -0
- package/dist/authorization/recovery-host.d.ts +1 -0
- package/dist/authorization/recovery-host.js +2 -3
- package/dist/authorization/recovery-host.js.map +1 -1
- package/dist/boot.js +28 -20
- package/dist/boot.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 +91 -73
- package/dist/cli.js.map +1 -1
- package/dist/compose.js +4 -3
- package/dist/compose.js.map +1 -1
- package/dist/content-cache-index.js +2 -2
- package/dist/content-cache-index.js.map +1 -1
- package/dist/detection/runtime.js +2 -2
- package/dist/detection/runtime.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/entity-apply-host.js +2 -2
- package/dist/entity-apply-host.js.map +1 -1
- package/dist/entity-apply-store.d.ts +2 -1
- package/dist/entity-apply-store.js +52 -37
- package/dist/entity-apply-store.js.map +1 -1
- 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 +27 -5
- package/dist/machine-host.js +244 -96
- 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 +6 -0
- package/dist/process-lifecycle.js +42 -0
- package/dist/process-lifecycle.js.map +1 -0
- package/dist/pty-host.d.ts +1 -1
- package/dist/pty-host.js +70 -21
- package/dist/pty-host.js.map +1 -1
- 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 +237 -0
- package/dist/service/cli.js.map +1 -0
- package/dist/service/control.d.ts +38 -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 +18 -0
- package/dist/service/keepalive.js +418 -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/recovery.d.ts +27 -0
- package/dist/service/recovery.js +58 -0
- package/dist/service/recovery.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-unit.d.ts +7 -0
- package/dist/service/systemd-unit.js +43 -0
- package/dist/service/systemd-unit.js.map +1 -0
- package/dist/service/systemd.d.ts +5 -0
- package/dist/service/systemd.js +72 -0
- package/dist/service/systemd.js.map +1 -0
- package/dist/service/windows-action.d.ts +9 -0
- package/dist/service/windows-action.js +55 -0
- package/dist/service/windows-action.js.map +1 -0
- package/dist/service/windows-task.d.ts +9 -0
- package/dist/service/windows-task.js +46 -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/sqlite.d.ts +4 -0
- package/dist/sqlite.js +16 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/updates/artifacts.d.ts +40 -0
- package/dist/updates/artifacts.js +299 -0
- package/dist/updates/artifacts.js.map +1 -0
- package/dist/updates/bootstrap.d.ts +48 -0
- package/dist/updates/bootstrap.js +384 -0
- package/dist/updates/bootstrap.js.map +1 -0
- package/dist/updates/controller.d.ts +66 -0
- package/dist/updates/controller.js +322 -0
- package/dist/updates/controller.js.map +1 -0
- package/dist/updates/isolated-command-lifetime.d.ts +1 -0
- package/dist/updates/isolated-command-lifetime.js +63 -0
- package/dist/updates/isolated-command-lifetime.js.map +1 -0
- package/dist/updates/store.d.ts +41 -0
- package/dist/updates/store.js +80 -0
- package/dist/updates/store.js.map +1 -0
- package/dist/updates/trust.d.ts +20 -0
- package/dist/updates/trust.js +138 -0
- package/dist/updates/trust.js.map +1 -0
- package/dist/user-ground-host.d.ts +4 -1
- package/dist/user-ground-host.js +16 -3
- package/dist/user-ground-host.js.map +1 -1
- package/dist/watching/host.js +4 -2
- package/dist/watching/host.js.map +1 -1
- package/dist/workspace-add-staging.js +2 -2
- package/dist/workspace-add-staging.js.map +1 -1
- package/examinations/fixtures/machine-control-plane.js +57 -0
- package/examinations/fixtures/private-wire-authority.js +1065 -0
- package/examinations/native-service.mjs +92 -0
- package/examinations/network-view.mjs +202 -0
- package/examinations/runtime-upgrade.native-test.mjs +198 -0
- package/npm-shrinkwrap.json +2097 -218
- package/package.json +95 -14
package/dist/machine-host.js
CHANGED
|
@@ -2,40 +2,25 @@ 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 {
|
|
7
|
-
import {
|
|
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 { admitRuntimeFormat } from './updates/store.js';
|
|
6
|
+
import { serviceIdentity } from './service/registration.js';
|
|
7
|
+
import { buildRootManifest, approveMachineSetup, adoptMachineSetup, joinApprovedMachine, mergeRecordAuth, resumeMachine, resolveRuntimeBinding, runtimeBindingsMatch, runtimeBindingFromRecord, RUNTIME_IDENTITY_PATH, createRuntimeIdentity, runtimeRootSegments, resolveRuntimeEndpoint, scopedAmalgmDir, userMachineDir, } from "@amalgm/core/identity";
|
|
20
8
|
import { atomicJson, atomicJsonSet, ensurePrivateDir, readJson, } from "./filesystem.js";
|
|
21
9
|
import { emitFilesStage, } from "./files-observability.js";
|
|
22
10
|
import { FilesStageRecorder, serializeStages } from "./files-trace.js";
|
|
23
|
-
import { createShellChatComposition } from "./chat-composition.js";
|
|
24
11
|
import { createShellAuthorization, loadInstallationDeviceKey, } from "./authorization/index.js";
|
|
25
12
|
import { recoverMachineAuthorization, } from "./authorization/recovery-host.js";
|
|
26
13
|
import { postRegistration } from "./authorization/registration-http.js";
|
|
27
14
|
import { postSetupJson, setupOrigin } from "./authorization/setup-http.js";
|
|
28
15
|
import { installBuiltinAgents } from "./builtin-agents.js";
|
|
16
|
+
import { InstallationOwnership } from "./installation/ownership.js";
|
|
17
|
+
import { runtimeAppOrigin, runtimeProxyOrigin, runtimeWireUrl } from "./installation/environment.js";
|
|
29
18
|
import { forwardMount } from "./forward.js";
|
|
30
19
|
import { createGateway } from "./gateway.js";
|
|
31
20
|
import { localMount } from "./local-host.js";
|
|
32
|
-
import { connectOfficialMcpServer } from "./official-mcp-host.js";
|
|
33
|
-
import { createPlatformProductHost, } from "./platform-product-host.js";
|
|
34
21
|
import { createPtyHost } from "./pty-host.js";
|
|
35
|
-
import { callRuntimeCommand, inspectRuntimeCommands, publishRuntimeCommands, runtimeCommandConnection, unpublishRuntimeCommands, } from "./runtime-command-host.js";
|
|
22
|
+
import { callRuntimeCommand, assertRuntimeCanStart, inspectRuntimeCommands, publishRuntimeCommands, runtimeCommandConnection, unpublishRuntimeCommands, } from "./runtime-command-host.js";
|
|
36
23
|
import { shellReleaseCatalog, shellReleaseVerifier } from "./release-catalog.js";
|
|
37
|
-
import { platformToolDefinitions } from "./platform-tools.js";
|
|
38
|
-
import { UserGroundHost, } from "./user-ground-host.js";
|
|
39
24
|
/** The production effects around Core's machine-setup operation. It does not
|
|
40
25
|
* decide ordering, readiness, or installation selection; those remain Core. */
|
|
41
26
|
export class MachineHost {
|
|
@@ -44,19 +29,41 @@ export class MachineHost {
|
|
|
44
29
|
amalgmRoot;
|
|
45
30
|
deviceFile;
|
|
46
31
|
ports;
|
|
32
|
+
binding;
|
|
33
|
+
ownership;
|
|
47
34
|
selectedUserEmail;
|
|
48
35
|
groundHost = null;
|
|
49
36
|
running = new Set();
|
|
50
37
|
stages;
|
|
51
38
|
constructor(options) {
|
|
52
39
|
setupOrigin(options.appUrl);
|
|
53
|
-
this.
|
|
40
|
+
this.binding = resolveRuntimeBinding(options);
|
|
41
|
+
if (options.services && this.binding.runtimeLabel !== 'local')
|
|
42
|
+
throw new Error('Only a local profile may override runtime service selection.');
|
|
43
|
+
runtimeAppOrigin(this.binding, options.appUrl);
|
|
44
|
+
for (const [role, endpoint] of [['proxy', options.proxyUrl], ['wire', options.wireUrl],
|
|
45
|
+
['automations', options.platformProducts?.automationsBaseUrl], ['channels', options.platformProducts?.channelsBaseUrl]]) {
|
|
46
|
+
if (endpoint)
|
|
47
|
+
resolveRuntimeEndpoint(this.binding, role, endpoint, options.appUrl);
|
|
48
|
+
}
|
|
49
|
+
this.options = { ...options, ...this.binding };
|
|
54
50
|
this.stages = new FilesStageRecorder(options.onFilesStage);
|
|
55
51
|
this.homedir = options.homedir ?? osHomedir();
|
|
56
|
-
this.amalgmRoot = options.amalgmRoot ?? join(this.homedir,
|
|
52
|
+
this.amalgmRoot = resolve(options.amalgmRoot ?? join(this.homedir, ...runtimeRootSegments(this.binding)));
|
|
53
|
+
this.ownership = new InstallationOwnership({
|
|
54
|
+
home: this.homedir,
|
|
55
|
+
installationRoot: this.amalgmRoot,
|
|
56
|
+
binding: this.binding,
|
|
57
|
+
});
|
|
57
58
|
this.deviceFile = join(this.amalgmRoot, ".amalgm", "device.json");
|
|
58
59
|
this.selectedUserEmail = normalizeEmail(options.userEmail);
|
|
59
|
-
|
|
60
|
+
try {
|
|
61
|
+
this.ownership.initialize(() => this.ensureInstallationRoot());
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
this.ownership.close();
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
60
67
|
const deviceStore = {
|
|
61
68
|
load: async () => readJson(this.deviceFile),
|
|
62
69
|
save: async (device) => atomicJson(this.deviceFile, device),
|
|
@@ -96,17 +103,17 @@ export class MachineHost {
|
|
|
96
103
|
: {}),
|
|
97
104
|
...(this.options.authorizationPlatform
|
|
98
105
|
? { platform: this.options.authorizationPlatform }
|
|
99
|
-
:
|
|
106
|
+
: {}),
|
|
100
107
|
});
|
|
101
108
|
const response = await postRegistration(globalThis.fetch, this.options.appUrl, {
|
|
102
109
|
...body, authorization_key_thumbprint: signer.keyThumbprint,
|
|
103
110
|
}, token, signer);
|
|
111
|
+
runtimeWireUrl(this.binding, this.options.appUrl, text(response.tunnel_url), this.options.wireUrl);
|
|
104
112
|
const email = normalizeEmail(response.user_email);
|
|
105
113
|
if (!email)
|
|
106
114
|
throw Object.assign(new Error("registration did not return a user email"), {
|
|
107
115
|
code: "missing_user_email",
|
|
108
116
|
});
|
|
109
|
-
this.selectedUserEmail = email;
|
|
110
117
|
return response;
|
|
111
118
|
},
|
|
112
119
|
};
|
|
@@ -138,12 +145,14 @@ export class MachineHost {
|
|
|
138
145
|
throw Object.assign(new Error("machine tunnel credentials are missing"), { code: "tunnel_credentials_missing" });
|
|
139
146
|
}
|
|
140
147
|
await this.groundHost?.close();
|
|
148
|
+
const { UserGroundHost } = await import("./user-ground-host.js");
|
|
141
149
|
this.groundHost = new UserGroundHost({
|
|
142
150
|
amalgmRoot: this.amalgmRoot,
|
|
143
151
|
deviceId: identity.deviceId,
|
|
144
152
|
tunnelUrl,
|
|
145
153
|
tunnelToken,
|
|
146
154
|
runtimeVersion: this.options.runtimeVersion,
|
|
155
|
+
reserveWritableRoot: (path) => { this.ownership.reserve(path, identity.userEmail); },
|
|
147
156
|
openWatch: this.options.openWatch,
|
|
148
157
|
onWatchHealth: this.options.onWatchHealth,
|
|
149
158
|
onDetectScan: this.options.onDetectScan,
|
|
@@ -179,19 +188,79 @@ export class MachineHost {
|
|
|
179
188
|
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
180
189
|
};
|
|
181
190
|
}
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
async approveSetup(input) {
|
|
192
|
+
const approved = await approveMachineSetup({
|
|
184
193
|
...input,
|
|
185
194
|
appUrl: this.options.appUrl,
|
|
186
195
|
platform: `${platform()}-${release()}`,
|
|
187
196
|
appVersion: this.options.runtimeVersion,
|
|
188
|
-
|
|
197
|
+
...this.binding,
|
|
189
198
|
branch: this.options.branch ?? "main",
|
|
199
|
+
...(this.binding.localSession ? { localSession: this.binding.localSession } : {}),
|
|
190
200
|
}, this.ports);
|
|
201
|
+
const email = approved.grant.approved_by.email;
|
|
202
|
+
if (this.options.userEmail && normalizeEmail(this.options.userEmail) !== email) {
|
|
203
|
+
throw Object.assign(new Error('Setup was approved by a different account than the selected user.'), { code: 'approved_user_mismatch' });
|
|
204
|
+
}
|
|
205
|
+
this.selectedUserEmail = email;
|
|
206
|
+
return approved;
|
|
207
|
+
}
|
|
208
|
+
async join(input) {
|
|
209
|
+
await this.ownership.waitForRuntime('setup');
|
|
210
|
+
try {
|
|
211
|
+
const approved = await this.approveSetup(input);
|
|
212
|
+
this.acquireRuntimeOwnership();
|
|
213
|
+
this.assertRuntimeCanStart();
|
|
214
|
+
return await joinApprovedMachine(approved, this.ports);
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
this.ownership.releaseRuntime('setup');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** Shared, finite UI setup. An existing owner is never disturbed by adoption. */
|
|
221
|
+
async setup(input) {
|
|
222
|
+
await this.ownership.waitForRuntime('setup');
|
|
223
|
+
let enrolling = false;
|
|
224
|
+
try {
|
|
225
|
+
const approved = await this.approveSetup(input);
|
|
226
|
+
const machine = await this.loadMachine();
|
|
227
|
+
if (machine?.ready) {
|
|
228
|
+
await adoptMachineSetup(approved, machine, this.ports);
|
|
229
|
+
return { disposition: 'adopted', userEmail: this.requireSelectedUser(),
|
|
230
|
+
userId: approved.grant.approved_by.user_id, computerId: text(machine.publicRecord.computer_id),
|
|
231
|
+
deviceId: approved.device.device_id };
|
|
232
|
+
}
|
|
233
|
+
this.acquireRuntimeOwnership();
|
|
234
|
+
this.assertRuntimeCanStart();
|
|
235
|
+
enrolling = true;
|
|
236
|
+
const ready = await joinApprovedMachine(approved, this.ports);
|
|
237
|
+
return { disposition: 'enrolled', userEmail: this.requireSelectedUser(),
|
|
238
|
+
userId: ready.userId, computerId: ready.computerId, deviceId: ready.deviceId };
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
try {
|
|
242
|
+
if (enrolling) {
|
|
243
|
+
await this.drainRuntime();
|
|
244
|
+
this.ownership.releaseRuntime(this.requireSelectedUser());
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
this.ownership.releaseRuntime('setup');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
191
251
|
}
|
|
192
252
|
resume() {
|
|
193
253
|
this.resolveUserForResume();
|
|
194
|
-
|
|
254
|
+
this.acquireRuntimeOwnership();
|
|
255
|
+
this.assertRuntimeCanStart();
|
|
256
|
+
return resumeMachine(this.ports, { services: this.options.services });
|
|
257
|
+
}
|
|
258
|
+
assertRuntimeCanStart() {
|
|
259
|
+
const deviceId = text(readJson(this.deviceFile)?.device_id);
|
|
260
|
+
if (!deviceId)
|
|
261
|
+
return;
|
|
262
|
+
assertRuntimeCanStart({ userRoot: this.paths().userRoot, deviceId,
|
|
263
|
+
...this.binding, userScope: this.requireSelectedUser() });
|
|
195
264
|
}
|
|
196
265
|
async recoverAuthorizationKey(input = {}) {
|
|
197
266
|
this.resolveUserForResume();
|
|
@@ -204,7 +273,7 @@ export class MachineHost {
|
|
|
204
273
|
machine,
|
|
205
274
|
appUrl: this.options.appUrl,
|
|
206
275
|
runtimeVersion: this.options.runtimeVersion,
|
|
207
|
-
|
|
276
|
+
...this.binding,
|
|
208
277
|
branch: this.options.branch ?? "main",
|
|
209
278
|
fallbackName: defaultMachineName(),
|
|
210
279
|
fallbackPlatform: `${platform()}-${release()}`,
|
|
@@ -226,6 +295,7 @@ export class MachineHost {
|
|
|
226
295
|
const deviceId = text(device?.device_id) || null;
|
|
227
296
|
if (!deviceId) {
|
|
228
297
|
return {
|
|
298
|
+
binding: this.binding,
|
|
229
299
|
user,
|
|
230
300
|
deviceId: null,
|
|
231
301
|
registration: "absent",
|
|
@@ -240,6 +310,8 @@ export class MachineHost {
|
|
|
240
310
|
}
|
|
241
311
|
const paths = this.paths(user);
|
|
242
312
|
const publicRecord = readJson(paths.publicRecord);
|
|
313
|
+
if (publicRecord)
|
|
314
|
+
this.assertRecordBinding(publicRecord);
|
|
243
315
|
const secretAuth = readJson(paths.secretAuth);
|
|
244
316
|
const installation = readJson(paths.installation);
|
|
245
317
|
const storedStatus = readJson(paths.status);
|
|
@@ -251,11 +323,12 @@ export class MachineHost {
|
|
|
251
323
|
? await inspectRuntimeCommands({
|
|
252
324
|
userRoot: paths.userRoot,
|
|
253
325
|
deviceId,
|
|
254
|
-
|
|
326
|
+
...this.binding,
|
|
255
327
|
userScope: user,
|
|
256
328
|
})
|
|
257
329
|
: { published: false, running: false, pid: null, port: null };
|
|
258
330
|
return {
|
|
331
|
+
binding: this.binding,
|
|
259
332
|
user,
|
|
260
333
|
deviceId,
|
|
261
334
|
registration,
|
|
@@ -282,6 +355,7 @@ export class MachineHost {
|
|
|
282
355
|
primitive: "download", stage: "files-add", status: "started", workspaceId,
|
|
283
356
|
});
|
|
284
357
|
try {
|
|
358
|
+
const { addFilesWorkspace } = await import("@amalgm/live/files");
|
|
285
359
|
const added = await addFilesWorkspace(workspaceId, destinationParent, this.groundHost.filesCommandPorts().add);
|
|
286
360
|
const result = { ...added, watch: this.groundHost.addedWorkspaceEvidence(added.workspaceId) };
|
|
287
361
|
emitFilesStage(this.stages.observe, {
|
|
@@ -318,7 +392,7 @@ export class MachineHost {
|
|
|
318
392
|
primitive: "register", stage: "durable-declaration", status: "started", path: requestedPath,
|
|
319
393
|
});
|
|
320
394
|
try {
|
|
321
|
-
const declared = await registerFilesWorkspace(requestedPath, this.groundHost.filesCommandPorts().register);
|
|
395
|
+
const declared = await (await import("@amalgm/live/files")).registerFilesWorkspace(requestedPath, this.groundHost.filesCommandPorts().register);
|
|
322
396
|
emitFilesStage(this.stages.observe, {
|
|
323
397
|
primitive: "register",
|
|
324
398
|
stage: "durable-declaration",
|
|
@@ -383,6 +457,10 @@ export class MachineHost {
|
|
|
383
457
|
return Object.freeze(adapter);
|
|
384
458
|
}
|
|
385
459
|
async close(options = {}) {
|
|
460
|
+
await this.drainRuntime(options);
|
|
461
|
+
this.ownership.close();
|
|
462
|
+
}
|
|
463
|
+
async drainRuntime(options = {}) {
|
|
386
464
|
const runtimes = [...this.running];
|
|
387
465
|
this.running.clear();
|
|
388
466
|
await this.groundHost?.close(options);
|
|
@@ -398,11 +476,20 @@ export class MachineHost {
|
|
|
398
476
|
nowIso: () => new Date().toISOString(),
|
|
399
477
|
randomHex: (bytes) => randomBytes(bytes).toString("hex"),
|
|
400
478
|
});
|
|
401
|
-
|
|
402
|
-
|
|
479
|
+
const previousBinding = existing?.runtimeBinding;
|
|
480
|
+
if (previousBinding && !runtimeBindingsMatch(previousBinding, this.binding)) {
|
|
481
|
+
throw new Error('This installation root belongs to another runtime binding.');
|
|
482
|
+
}
|
|
483
|
+
if (existing?.installationId && !previousBinding && this.binding.runtimeLabel !== 'main') {
|
|
484
|
+
throw new Error('An unbound existing installation must be adopted explicitly; use a fresh root for preview/local.');
|
|
485
|
+
}
|
|
486
|
+
if (!existing?.installationId || !previousBinding) {
|
|
487
|
+
atomicJson(manifestFile, { ...manifest, runtimeBinding: this.binding });
|
|
488
|
+
}
|
|
403
489
|
}
|
|
404
490
|
async openChannelsCommands() {
|
|
405
491
|
const access = runtimeCommandConnection(await this.runtimeCommandAddress());
|
|
492
|
+
const { createShellChannelsCommands } = await import("@amalgm/channels/cli");
|
|
406
493
|
return createShellChannelsCommands(access);
|
|
407
494
|
}
|
|
408
495
|
async runtimeCommandAddress() {
|
|
@@ -419,7 +506,7 @@ export class MachineHost {
|
|
|
419
506
|
return {
|
|
420
507
|
userRoot: this.paths().userRoot,
|
|
421
508
|
deviceId,
|
|
422
|
-
|
|
509
|
+
...this.binding,
|
|
423
510
|
userScope: userEmail,
|
|
424
511
|
};
|
|
425
512
|
}
|
|
@@ -440,7 +527,7 @@ export class MachineHost {
|
|
|
440
527
|
: {}),
|
|
441
528
|
...(this.options.authorizationPlatform
|
|
442
529
|
? { platform: this.options.authorizationPlatform }
|
|
443
|
-
:
|
|
530
|
+
: {}),
|
|
444
531
|
};
|
|
445
532
|
}
|
|
446
533
|
paths(email = this.requireSelectedUser()) {
|
|
@@ -494,6 +581,17 @@ export class MachineHost {
|
|
|
494
581
|
});
|
|
495
582
|
}
|
|
496
583
|
}
|
|
584
|
+
assertRecordBinding(record) {
|
|
585
|
+
const stored = runtimeBindingFromRecord(record);
|
|
586
|
+
if (!runtimeBindingsMatch(stored, this.binding)
|
|
587
|
+
|| (typeof record.app_url === 'string' && setupOrigin(record.app_url) !== setupOrigin(this.options.appUrl))) {
|
|
588
|
+
throw Object.assign(new Error('Stored machine belongs to another runtime binding or control plane.'), {
|
|
589
|
+
code: 'machine_binding_mismatch',
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
if (record.tunnel_url !== undefined)
|
|
593
|
+
runtimeWireUrl(this.binding, this.options.appUrl, text(record.tunnel_url), this.options.wireUrl);
|
|
594
|
+
}
|
|
497
595
|
async loadMachine() {
|
|
498
596
|
if (!this.selectedUserEmail)
|
|
499
597
|
this.resolveUserForResume();
|
|
@@ -506,12 +604,15 @@ export class MachineHost {
|
|
|
506
604
|
const status = readJson(paths.status);
|
|
507
605
|
if (!publicRecord || !secretAuth || !installation || !status)
|
|
508
606
|
return null;
|
|
607
|
+
this.assertRecordBinding(publicRecord);
|
|
509
608
|
return { publicRecord, secretAuth, installation, ready: status.ready === true };
|
|
510
609
|
}
|
|
511
610
|
async saveMachine(machine) {
|
|
611
|
+
this.assertRecordBinding(machine.publicRecord);
|
|
512
612
|
const recordEmail = normalizeEmail(machine.publicRecord.user_email);
|
|
513
613
|
if (recordEmail)
|
|
514
614
|
this.selectedUserEmail = recordEmail;
|
|
615
|
+
this.acquireRuntimeOwnership();
|
|
515
616
|
const paths = this.paths();
|
|
516
617
|
atomicJsonSet([
|
|
517
618
|
[paths.publicRecord, machine.publicRecord],
|
|
@@ -520,6 +621,11 @@ export class MachineHost {
|
|
|
520
621
|
[paths.status, { ready: machine.ready, updatedAt: new Date().toISOString() }],
|
|
521
622
|
]);
|
|
522
623
|
}
|
|
624
|
+
acquireRuntimeOwnership() {
|
|
625
|
+
const user = this.requireSelectedUser();
|
|
626
|
+
this.ownership.acquireRuntime(user);
|
|
627
|
+
admitRuntimeFormat(serviceIdentity({ ...this.binding, user, home: this.homedir, amalgmRoot: this.amalgmRoot }).serviceDir);
|
|
628
|
+
}
|
|
523
629
|
async startRuntime(machine) {
|
|
524
630
|
const record = mergeRecordAuth(machine.publicRecord, machine.secretAuth) ?? {};
|
|
525
631
|
const userId = text(record.user_id);
|
|
@@ -535,6 +641,9 @@ export class MachineHost {
|
|
|
535
641
|
const userRoot = this.paths(userEmail).userRoot;
|
|
536
642
|
const workspaceRoot = join(userRoot, "workspaces");
|
|
537
643
|
const runtimeToken = randomBytes(32).toString("base64url");
|
|
644
|
+
const runtimeIdentity = createRuntimeIdentity({
|
|
645
|
+
binding: this.binding, userScope: userEmail, deviceId, instanceId: randomUUID(),
|
|
646
|
+
});
|
|
538
647
|
const env = {
|
|
539
648
|
...process.env,
|
|
540
649
|
AMALGM_ROOT: this.amalgmRoot,
|
|
@@ -545,7 +654,8 @@ export class MachineHost {
|
|
|
545
654
|
AMALGM_RUNTIME_LABEL: this.options.runtimeLabel ?? "main",
|
|
546
655
|
AMALGM_BRANCH: this.options.branch ?? "main",
|
|
547
656
|
AMALGM_RUNTIME_TOKEN: runtimeToken,
|
|
548
|
-
|
|
657
|
+
AMALGM_LOCAL_SESSION: this.binding.localSession,
|
|
658
|
+
AMALGM_PROXY_URL: runtimeProxyOrigin(this.binding, this.options.appUrl, this.options.proxyUrl),
|
|
549
659
|
AMALGM_WORKSPACES_DIR: workspaceRoot,
|
|
550
660
|
};
|
|
551
661
|
const pty = createPtyHost({
|
|
@@ -554,37 +664,48 @@ export class MachineHost {
|
|
|
554
664
|
});
|
|
555
665
|
const services = new Set(machine.installation.services);
|
|
556
666
|
const serviceStateRoot = join(this.paths(userEmail).machineDir, "services");
|
|
557
|
-
const
|
|
558
|
-
|
|
667
|
+
const agentsSdk = services.has("agents") ? await import("@amalgm/agents") : null;
|
|
668
|
+
const agentsMcpSdk = agentsSdk ? await import("@amalgm/agents/mcp") : null;
|
|
669
|
+
const agentsHttpSdk = agentsSdk ? await import("@amalgm/agents/http") : null;
|
|
670
|
+
const toolsSdk = services.has("tools") ? await import("@amalgm/tools") : null;
|
|
671
|
+
const toolsHttpSdk = toolsSdk ? await import("@amalgm/tools/http") : null;
|
|
672
|
+
const includeApps = services.has("apps") || machine.installation.selection === "all";
|
|
673
|
+
const includeBrowser = services.has("browser") || machine.installation.selection === "all";
|
|
674
|
+
const appsSdk = toolsSdk && includeApps ? await import("@amalgm/apps") : null;
|
|
675
|
+
const browserSdk = toolsSdk && includeBrowser ? await import("@amalgm/browser") : null;
|
|
676
|
+
const browserMcpSdk = browserSdk ? await import("@amalgm/browser/mcp") : null;
|
|
677
|
+
const agentsService = agentsSdk
|
|
678
|
+
? new agentsSdk.Agents({ stateDir: join(serviceStateRoot, "agents") })
|
|
559
679
|
: null;
|
|
560
680
|
if (agentsService)
|
|
561
681
|
installBuiltinAgents(agentsService);
|
|
562
|
-
const agentsMcp = agentsService ?
|
|
563
|
-
const agentsRest = agentsService ?
|
|
682
|
+
const agentsMcp = agentsService ? agentsMcpSdk.createMcpServer({ agents: agentsService }) : null;
|
|
683
|
+
const agentsRest = agentsService ? agentsHttpSdk.createRestServer({ agents: agentsService }) : null;
|
|
564
684
|
const agentsAddress = agentsRest ? await agentsRest.listen(0, "127.0.0.1") : null;
|
|
565
685
|
const platformTools = services.has("tools")
|
|
566
|
-
? await platformToolDefinitions({
|
|
567
|
-
agents: agentsMcp
|
|
568
|
-
apps: { tools:
|
|
569
|
-
browser: { tools: createBrowserMcpTools({}) },
|
|
686
|
+
? await (await import("./platform-tools.js")).platformToolDefinitions({
|
|
687
|
+
...(agentsMcp ? { agents: agentsMcp } : {}),
|
|
688
|
+
...(appsSdk ? { apps: { tools: appsSdk.createMcpTools({}) } } : {}),
|
|
689
|
+
...(browserMcpSdk ? { browser: { tools: browserMcpSdk.createBrowserMcpTools({}) } } : {}),
|
|
690
|
+
chat: services.has("chat"),
|
|
570
691
|
products: {
|
|
571
|
-
automations: Boolean(this.options.platformProducts),
|
|
572
|
-
channels: Boolean(this.options.platformProducts),
|
|
692
|
+
automations: Boolean(this.options.platformProducts?.automationsBaseUrl),
|
|
693
|
+
channels: Boolean(this.options.platformProducts?.channelsBaseUrl),
|
|
573
694
|
},
|
|
574
695
|
})
|
|
575
696
|
: [];
|
|
576
697
|
const toolsMcp = services.has("tools")
|
|
577
|
-
?
|
|
698
|
+
? toolsSdk.createMcpServer({
|
|
578
699
|
databaseFile: join(serviceStateRoot, "tools", "tools.db"),
|
|
579
700
|
deploymentDir: join(userRoot, "toolbox"),
|
|
580
701
|
systemTools: [...platformTools],
|
|
581
702
|
})
|
|
582
703
|
: null;
|
|
583
|
-
const toolsManagement = toolsMcp ?
|
|
704
|
+
const toolsManagement = toolsMcp ? toolsSdk.createMcpServer({
|
|
584
705
|
toolbox: toolsMcp.toolbox,
|
|
585
706
|
mcp: { loadout: [], includeManagement: true },
|
|
586
707
|
}) : null;
|
|
587
|
-
const toolsHttp = toolsMcp ? createToolboxHttpServer({ toolbox: toolsMcp.toolbox }) : null;
|
|
708
|
+
const toolsHttp = toolsMcp ? toolsHttpSdk.createToolboxHttpServer({ toolbox: toolsMcp.toolbox }) : null;
|
|
588
709
|
const toolsAddress = toolsHttp ? await toolsHttp.listen(0, "127.0.0.1") : null;
|
|
589
710
|
if (services.has("chat") && (!agentsService || !toolsMcp)) {
|
|
590
711
|
throw new Error("The Chat release requires Agents and Tools");
|
|
@@ -600,7 +721,7 @@ export class MachineHost {
|
|
|
600
721
|
: {}),
|
|
601
722
|
...(this.options.authorizationPlatform
|
|
602
723
|
? { platform: this.options.authorizationPlatform }
|
|
603
|
-
:
|
|
724
|
+
: {}),
|
|
604
725
|
});
|
|
605
726
|
let gateway = null;
|
|
606
727
|
const gatewayPort = () => {
|
|
@@ -610,7 +731,7 @@ export class MachineHost {
|
|
|
610
731
|
return address.port;
|
|
611
732
|
};
|
|
612
733
|
const chat = services.has("chat") && agentsService && toolsMcp
|
|
613
|
-
? await createShellChatComposition({
|
|
734
|
+
? await (await import("./chat-composition.js")).createShellChatComposition({
|
|
614
735
|
ownerUserId: userId,
|
|
615
736
|
computerId,
|
|
616
737
|
stateDir: join(serviceStateRoot, "chat"),
|
|
@@ -625,13 +746,15 @@ export class MachineHost {
|
|
|
625
746
|
})
|
|
626
747
|
: null;
|
|
627
748
|
const products = this.options.platformProducts
|
|
628
|
-
? createPlatformProductHost({
|
|
749
|
+
? await (await import("./platform-product-host.js")).createPlatformProductHost({
|
|
629
750
|
...this.options.platformProducts,
|
|
630
751
|
authorization,
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
752
|
+
...(this.options.platformProducts.automationsBaseUrl ? {
|
|
753
|
+
processes: (await import("@amalgm/automations")).createNodeAutomationProcessHost({
|
|
754
|
+
defaultCwd: this.homedir,
|
|
755
|
+
environment: { ...process.env, HOME: this.homedir },
|
|
756
|
+
}),
|
|
757
|
+
} : {}),
|
|
635
758
|
...(toolsMcp ? { toolbox: toolsMcp.toolbox } : {}),
|
|
636
759
|
...(chat ? { actions: { 'chat.chat_agent_run': chatAgentAction(chat.agentRunner) } } : {}),
|
|
637
760
|
...(chat ? { inboundExecutor: {
|
|
@@ -647,23 +770,27 @@ export class MachineHost {
|
|
|
647
770
|
} } : {}),
|
|
648
771
|
})
|
|
649
772
|
: null;
|
|
650
|
-
const automationsMcp = products
|
|
651
|
-
? await connectOfficialMcpServer(createAutomationMcpServer(products.automations), { name: 'amalgm-automations', version: '0.2.5' })
|
|
773
|
+
const automationsMcp = products?.automations
|
|
774
|
+
? await (await import("./official-mcp-host.js")).connectOfficialMcpServer((await import("@amalgm/automations/mcp")).createAutomationMcpServer(products.automations), { name: 'amalgm-automations', version: '0.2.5' })
|
|
652
775
|
: null;
|
|
653
|
-
const channelsMcp = products
|
|
654
|
-
? await connectOfficialMcpServer(createChannelsMcpServer(products.channels), { name: 'amalgm-channels', version: '0.1.0' })
|
|
776
|
+
const channelsMcp = products?.channels
|
|
777
|
+
? await (await import("./official-mcp-host.js")).connectOfficialMcpServer((await import("@amalgm/channels/mcp")).createChannelsMcpServer(products.channels), { name: 'amalgm-channels', version: '0.1.0' })
|
|
655
778
|
: null;
|
|
656
|
-
const chatMcp = chat ? createChatMcpServer(chat.chat, { agentRunner: chat.agentRunner }) : null;
|
|
779
|
+
const chatMcp = chat ? (await import("@amalgm/chat/mcp")).createChatMcpServer(chat.chat, { agentRunner: chat.agentRunner }) : null;
|
|
657
780
|
let appsMcp = null;
|
|
658
781
|
const selectedAppsMcp = () => {
|
|
659
|
-
|
|
782
|
+
if (!appsSdk)
|
|
783
|
+
throw new Error("Apps is not selected in this installation.");
|
|
784
|
+
appsMcp ??= appsSdk.createMcpServer({ stateDir: join(serviceStateRoot, "apps") });
|
|
660
785
|
return appsMcp;
|
|
661
786
|
};
|
|
662
787
|
let browserMcp = null;
|
|
663
788
|
let browserService = null;
|
|
664
789
|
const selectedBrowserMcp = () => {
|
|
665
|
-
|
|
666
|
-
|
|
790
|
+
if (!browserSdk || !browserMcpSdk)
|
|
791
|
+
throw new Error("Browser is not selected in this installation.");
|
|
792
|
+
browserService ??= browserSdk.createBrowser({ root: join(serviceStateRoot, "browser") });
|
|
793
|
+
browserMcp ??= browserMcpSdk.createBrowserMcpServer(browserService);
|
|
667
794
|
return browserMcp;
|
|
668
795
|
};
|
|
669
796
|
const closeSelectedProducts = async () => {
|
|
@@ -678,6 +805,7 @@ export class MachineHost {
|
|
|
678
805
|
env,
|
|
679
806
|
exemptPrefixes: ["/chat/egress/"],
|
|
680
807
|
mounts: [
|
|
808
|
+
{ prefix: RUNTIME_IDENTITY_PATH, handle: async () => runtimeIdentity },
|
|
681
809
|
{
|
|
682
810
|
prefix: "/entities/register",
|
|
683
811
|
handle: (body) => this.journey(body, () => this.registerWorkspace(bodyText(body, "path"))),
|
|
@@ -696,11 +824,12 @@ export class MachineHost {
|
|
|
696
824
|
protocol: "mcp-json-rpc",
|
|
697
825
|
handle: (body) => toolsMcp.handle(body),
|
|
698
826
|
}] : []),
|
|
699
|
-
...(
|
|
827
|
+
...(appsSdk ? [{
|
|
700
828
|
prefix: "/mcp/apps",
|
|
701
829
|
protocol: "mcp-json-rpc",
|
|
702
830
|
handle: (body) => selectedAppsMcp().handle(body),
|
|
703
|
-
},
|
|
831
|
+
}] : []),
|
|
832
|
+
...(browserSdk ? [{
|
|
704
833
|
prefix: "/mcp/browser",
|
|
705
834
|
protocol: "mcp-json-rpc",
|
|
706
835
|
handle: (body) => selectedBrowserMcp().handle(body),
|
|
@@ -757,9 +886,10 @@ export class MachineHost {
|
|
|
757
886
|
await this.groundHost.activateRuntimeTunnel(address.port, runtimeToken);
|
|
758
887
|
products?.runner.start();
|
|
759
888
|
commandPublication = {
|
|
889
|
+
instanceId: runtimeIdentity.instanceId,
|
|
760
890
|
userRoot: this.paths(userEmail).userRoot,
|
|
761
891
|
deviceId,
|
|
762
|
-
|
|
892
|
+
...this.binding,
|
|
763
893
|
userScope: userEmail,
|
|
764
894
|
port: address.port,
|
|
765
895
|
token: runtimeToken,
|
|
@@ -779,19 +909,27 @@ export class MachineHost {
|
|
|
779
909
|
if (commandPublication)
|
|
780
910
|
unpublishRuntimeCommands(commandPublication);
|
|
781
911
|
await this.groundHost?.stopRuntimeTunnel();
|
|
782
|
-
pty.close();
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
912
|
+
const terminalsClosed = Promise.allSettled([pty.close()]);
|
|
913
|
+
try {
|
|
914
|
+
const closed = new Promise((resolve) => gateway.close(() => resolve()));
|
|
915
|
+
gateway.closeAllConnections();
|
|
916
|
+
await closed;
|
|
917
|
+
await chat?.close();
|
|
918
|
+
await products?.runner.close();
|
|
919
|
+
await automationsMcp?.close();
|
|
920
|
+
await channelsMcp?.close();
|
|
921
|
+
await agentsRest?.close();
|
|
922
|
+
await toolsHttp?.close();
|
|
923
|
+
await closeSelectedProducts();
|
|
924
|
+
agentsService?.close();
|
|
925
|
+
toolsMcp?.toolbox.close();
|
|
926
|
+
}
|
|
927
|
+
finally {
|
|
928
|
+
await terminalsClosed;
|
|
929
|
+
}
|
|
930
|
+
const [terminal] = await terminalsClosed;
|
|
931
|
+
if (terminal.status === "rejected")
|
|
932
|
+
throw terminal.reason;
|
|
795
933
|
},
|
|
796
934
|
};
|
|
797
935
|
this.running.add(running);
|
|
@@ -801,21 +939,31 @@ export class MachineHost {
|
|
|
801
939
|
if (commandPublication)
|
|
802
940
|
unpublishRuntimeCommands(commandPublication);
|
|
803
941
|
await this.groundHost?.stopRuntimeTunnel().catch(() => undefined);
|
|
804
|
-
pty.close();
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
942
|
+
const terminalsClosed = Promise.allSettled([pty.close()]);
|
|
943
|
+
for (const close of [
|
|
944
|
+
async () => {
|
|
945
|
+
if (!gateway.listening)
|
|
946
|
+
return;
|
|
947
|
+
const closed = new Promise((resolve) => gateway.close(() => resolve()));
|
|
948
|
+
gateway.closeAllConnections();
|
|
949
|
+
await closed;
|
|
950
|
+
},
|
|
951
|
+
() => chat?.close(),
|
|
952
|
+
() => products?.runner.close(),
|
|
953
|
+
() => automationsMcp?.close(),
|
|
954
|
+
() => channelsMcp?.close(),
|
|
955
|
+
() => agentsRest?.close(),
|
|
956
|
+
() => toolsHttp?.close(),
|
|
957
|
+
closeSelectedProducts,
|
|
958
|
+
() => agentsService?.close(),
|
|
959
|
+
() => toolsMcp?.toolbox.close(),
|
|
960
|
+
]) {
|
|
961
|
+
try {
|
|
962
|
+
await close();
|
|
963
|
+
}
|
|
964
|
+
catch { /* Preserve the startup failure after attempting every owner. */ }
|
|
809
965
|
}
|
|
810
|
-
await
|
|
811
|
-
await products?.runner.close().catch(() => undefined);
|
|
812
|
-
await automationsMcp?.close().catch(() => undefined);
|
|
813
|
-
await channelsMcp?.close().catch(() => undefined);
|
|
814
|
-
await agentsRest?.close().catch(() => undefined);
|
|
815
|
-
await toolsHttp?.close().catch(() => undefined);
|
|
816
|
-
await closeSelectedProducts().catch(() => undefined);
|
|
817
|
-
agentsService?.close();
|
|
818
|
-
toolsMcp?.toolbox.close();
|
|
966
|
+
await terminalsClosed;
|
|
819
967
|
throw error;
|
|
820
968
|
}
|
|
821
969
|
}
|