@fungi.computer/oyster 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/agent-computer-source.d.ts +42 -0
  2. package/dist/agent-computer-source.js +88 -0
  3. package/dist/agent-computers.d.ts +104 -0
  4. package/dist/agent-computers.js +227 -0
  5. package/dist/agents.d.ts +10 -0
  6. package/dist/agents.js +33 -0
  7. package/dist/app-builds.d.ts +1 -1
  8. package/dist/app-builds.js +16 -6
  9. package/dist/boot-capability.d.ts +2 -1
  10. package/dist/boot-capability.js +7 -5
  11. package/dist/browser/boot.d.ts +2 -2
  12. package/dist/browser/boot.js +10 -3
  13. package/dist/browser/browser.d.ts +1 -1
  14. package/dist/browser/browser.js +2 -1
  15. package/dist/browser/cdp-client.d.ts +2 -0
  16. package/dist/browser/errors.d.ts +2 -2
  17. package/dist/browser/errors.js +2 -2
  18. package/dist/browser/index.d.ts +3 -2
  19. package/dist/browser/index.js +3 -2
  20. package/dist/browser/page.js +11 -4
  21. package/dist/browser/sessions.d.ts +7 -4
  22. package/dist/browser/sessions.js +159 -0
  23. package/dist/browser/url-policy.d.ts +4 -2
  24. package/dist/browser/url-policy.js +4 -2
  25. package/dist/build/build-jobs.d.ts +3 -3
  26. package/dist/build/build-jobs.js +10 -5
  27. package/dist/build/candidate-cli.js +5 -3
  28. package/dist/build/candidate-cli.mjs +223 -5
  29. package/dist/build/candidate-diagnostic.d.ts +2 -0
  30. package/dist/build/candidate-diagnostic.js +16 -0
  31. package/dist/computer-control-port.d.ts +26 -0
  32. package/dist/computer-control-port.js +1 -0
  33. package/dist/host.d.ts +3 -0
  34. package/dist/machine-link/delivery.d.ts +1 -1
  35. package/dist/machine-link/delivery.js +2 -3
  36. package/dist/machine-link/host.d.ts +37 -6
  37. package/dist/machine-link/host.js +209 -14
  38. package/dist/machine-link/link.d.ts +13 -1
  39. package/dist/machine-link/link.js +20 -7
  40. package/dist/mcp.d.ts +144 -10
  41. package/dist/mcp.js +130 -13
  42. package/dist/page/computer-handles.d.ts +3 -3
  43. package/dist/page-script.js +1 -1
  44. package/dist/standalone/agent-computers.d.ts +29 -0
  45. package/dist/standalone/agent-computers.js +101 -0
  46. package/dist/standalone/agent-mail.d.ts +41 -18
  47. package/dist/standalone/agent-model-choice.d.ts +29 -0
  48. package/dist/standalone/agent-model-choice.js +72 -0
  49. package/dist/standalone/agents.d.ts +44 -4
  50. package/dist/standalone/agents.js +502 -46
  51. package/dist/standalone/app-set.d.ts +8 -1
  52. package/dist/standalone/app-set.js +46 -31
  53. package/dist/standalone/computer-control.d.ts +4 -0
  54. package/dist/standalone/computer-control.js +62 -0
  55. package/dist/standalone/computer-fleet.d.ts +1 -0
  56. package/dist/standalone/computer-fleet.js +1 -1
  57. package/dist/standalone/computer.d.ts +21 -2
  58. package/dist/standalone/computer.js +12 -0
  59. package/dist/standalone/install-apps-bin.d.ts +2 -0
  60. package/dist/standalone/install-apps-bin.js +3 -0
  61. package/dist/standalone/install-apps-cli.d.ts +3 -1
  62. package/dist/standalone/install-apps-cli.js +27 -17
  63. package/dist/standalone/machine-link.d.ts +10 -0
  64. package/dist/standalone/machine-link.js +6 -0
  65. package/dist/standalone/registry.d.ts +10 -1
  66. package/dist/standalone/registry.js +9 -0
  67. package/dist/standalone/worker.js +12 -0
  68. package/dist/standalone/workflow-binding.d.ts +2 -2
  69. package/dist/standalone/workflow-host.d.ts +11 -11
  70. package/dist/standalone/workflows.d.ts +22 -22
  71. package/dist/storage-frame.d.ts +3 -3
  72. package/dist/workers-git.d.ts +2 -0
  73. package/dist/workers-git.js +2 -0
  74. package/dist/workers.d.ts +8 -8
  75. package/dist/workers.js +9 -1
  76. package/docs/api/agents.md +66 -0
  77. package/docs/api/apps.md +104 -104
  78. package/docs/api/build-contract.md +35 -35
  79. package/docs/api/build.md +12 -12
  80. package/docs/api/host.md +61 -61
  81. package/docs/api/mcp.md +127 -13
  82. package/docs/api/root-data.md +1 -1
  83. package/docs/api/tenancy.md +1 -1
  84. package/docs/api/workers-git.md +3 -2
  85. package/docs/api/workers.md +81 -83
  86. package/package.json +26 -18
@@ -60,6 +60,13 @@ export type InstalledStarterApp = Readonly<{
60
60
  release: Schema.Json;
61
61
  timings: StarterAppTimings;
62
62
  }>;
63
+ export type StarterAppSetResult = Readonly<{
64
+ installed: readonly InstalledStarterApp[];
65
+ failed: readonly Readonly<{
66
+ installationId: string;
67
+ reason: string;
68
+ }>[];
69
+ }>;
63
70
  /**
64
71
  * Builds the installer runs at once by default. A build's compiler may take a
65
72
  * 4 GB heap, so two fit the platform's 12 GiB Computer (standard-4) with room
@@ -91,4 +98,4 @@ export declare function installStandaloneAppSet(options: Readonly<{
91
98
  /** The idle wait between build status reads; the real timer by default. */
92
99
  idle?: (ms: number, signal: AbortSignal) => Promise<void>;
93
100
  waitMs?: number;
94
- }>): Promise<readonly InstalledStarterApp[]>;
101
+ }>): Promise<StarterAppSetResult>;
@@ -72,7 +72,13 @@ const publishedSchema = Schema.Struct({
72
72
  });
73
73
  const buildSchema = Schema.Struct({
74
74
  jobId: Schema.String,
75
- status: Schema.Literals(["running", "succeeded", "failed", "revoked"]),
75
+ status: Schema.Literals([
76
+ "running",
77
+ "succeeded",
78
+ "failed",
79
+ "interrupted",
80
+ "revoked",
81
+ ]),
76
82
  detail: Schema.NullOr(Schema.String),
77
83
  /** Installed exactly as the host encoded it. */
78
84
  release: Schema.NullOr(Schema.Json),
@@ -271,10 +277,15 @@ export async function installStandaloneAppSet(options) {
271
277
  }
272
278
  };
273
279
  const installed = new Map();
274
- /** Set by the first failure, so no further seed or build starts. */
275
- const stop = { requested: false };
276
- /** Every failure, in the order it happened; the first is the one thrown. */
277
- const failures = [];
280
+ const failures = new Map();
281
+ const fail = (app, cause) => {
282
+ const reason = cause instanceof Error ? cause.message : String(cause);
283
+ failures.set(app.installationId, reason);
284
+ options.report(`Failed ${app.installationId}: ${reason}`);
285
+ };
286
+ const reportFailure = ({ app, cause, }) => {
287
+ fail(app, cause);
288
+ };
278
289
  const seededQueue = [];
279
290
  const waiting = [];
280
291
  const seeding = { finished: false };
@@ -318,21 +329,19 @@ export async function installStandaloneAppSet(options) {
318
329
  const seedAll = async () => {
319
330
  try {
320
331
  for (const app of standaloneAppSet) {
321
- if (stop.requested)
322
- break;
323
- const seeded = await seedApp(app);
324
- const waiter = waiting.shift();
325
- if (waiter === undefined)
326
- seededQueue.push(seeded);
327
- else
328
- waiter(seeded);
332
+ try {
333
+ const seeded = await seedApp(app);
334
+ const waiter = waiting.shift();
335
+ if (waiter === undefined)
336
+ seededQueue.push(seeded);
337
+ else
338
+ waiter(seeded);
339
+ }
340
+ catch (cause) {
341
+ reportFailure({ app, cause });
342
+ }
329
343
  }
330
344
  }
331
- catch (cause) {
332
- stop.requested = true;
333
- failures.push(cause);
334
- throw cause;
335
- }
336
345
  finally {
337
346
  seeding.finished = true;
338
347
  for (const waiter of waiting.splice(0))
@@ -341,7 +350,7 @@ export async function installStandaloneAppSet(options) {
341
350
  };
342
351
  // `concurrency` builds at once, each installing as its build succeeds.
343
352
  const slot = async () => {
344
- for (let next = await takeSeeded(); next !== undefined && !stop.requested; next = await takeSeeded()) {
353
+ for (let next = await takeSeeded(); next !== undefined; next = await takeSeeded()) {
345
354
  const name = next.app.installationId;
346
355
  try {
347
356
  const buildStarted = performance.now();
@@ -416,22 +425,28 @@ export async function installStandaloneAppSet(options) {
416
425
  });
417
426
  }
418
427
  catch (cause) {
419
- stop.requested = true;
420
- failures.push(cause);
421
- throw cause;
428
+ reportFailure({ app: next.app, cause });
422
429
  }
423
430
  }
424
431
  };
425
- // Wait for every seed and build to settle before reporting: nothing of this
426
- // call may still run, or install, after it rejects.
427
- await Promise.allSettled([
432
+ // Wait for every seed and build to settle before returning the full outcome.
433
+ const settled = await Promise.allSettled([
428
434
  seedAll(),
429
435
  ...Array.from({ length: concurrency }, slot),
430
436
  ]);
431
- if (failures.length > 0)
432
- throw failures[0];
433
- return standaloneAppSet.flatMap((app) => {
434
- const result = installed.get(app.installationId);
435
- return result === undefined ? [] : [result];
436
- });
437
+ for (const outcome of settled)
438
+ if (outcome.status === "rejected")
439
+ throw outcome.reason;
440
+ return {
441
+ installed: standaloneAppSet.flatMap((app) => {
442
+ const result = installed.get(app.installationId);
443
+ return result === undefined ? [] : [result];
444
+ }),
445
+ failed: standaloneAppSet.flatMap((app) => {
446
+ const reason = failures.get(app.installationId);
447
+ return reason === undefined
448
+ ? []
449
+ : [{ installationId: app.installationId, reason }];
450
+ }),
451
+ };
437
452
  }
@@ -0,0 +1,4 @@
1
+ import type { ComputerControlPort } from "../mcp.js";
2
+ import type { StandaloneRegistryRpc } from "./registry.js";
3
+ /** MCP control speaks to the same owner-admitted durable fleet as HTTP. */
4
+ export declare function standaloneComputerControl(registry: Pick<StandaloneRegistryRpc, "computerFleet">, memberId: string): ComputerControlPort;
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ import { computerStateSchema, } from "@fungi.computer/computer/fleet";
3
+ import { teamComputerDirectorySchema } from "@fungi.computer/nacre/workbench";
4
+ const inventoryComputer = z.strictObject({
5
+ computerId: z.string(),
6
+ name: z.string(),
7
+ createdAt: z.number(),
8
+ updatedAt: z.number(),
9
+ });
10
+ const inventoryPage = z.strictObject({
11
+ computers: z.array(inventoryComputer),
12
+ nextOffset: z.number().int().nonnegative().nullable(),
13
+ });
14
+ const inventoryError = z.strictObject({
15
+ error: z.string(),
16
+ });
17
+ /** MCP control speaks to the same owner-admitted durable fleet as HTTP. */
18
+ export function standaloneComputerControl(registry, memberId) {
19
+ const fleet = (request) => registry.computerFleet(memberId, request);
20
+ const available = (result) => {
21
+ if (result === null || inventoryError.safeParse(result).success)
22
+ throw new Error("Computer fleet refused the request");
23
+ return result;
24
+ };
25
+ return {
26
+ list: async (page) => teamComputerDirectorySchema.parse(inventoryPage.parse(available(await fleet({ operation: "list", page })))),
27
+ state: async (computerId) => computerStateSchema.parse(available(await fleet({ operation: "state", computerId }))),
28
+ start: async (computerId) => computerStateSchema.parse(available(await fleet({ operation: "start", computerId }))),
29
+ create: async (input) => {
30
+ let result;
31
+ try {
32
+ result = await fleet({ operation: "create", request: input });
33
+ }
34
+ catch {
35
+ // Creation may have persisted before the RPC failed. Surface its
36
+ // identity so the caller can retry, never silently mint another one.
37
+ return { kind: "uncertain" };
38
+ }
39
+ if (result === null)
40
+ return { kind: "denied" };
41
+ const error = inventoryError.safeParse(result);
42
+ if (error.success) {
43
+ switch (error.data.error) {
44
+ case "owner_required":
45
+ return { kind: "denied" };
46
+ case "name_taken":
47
+ case "request_conflict":
48
+ return { kind: "conflict" };
49
+ case "partial_init":
50
+ return { kind: "uncertain" };
51
+ default:
52
+ return { kind: "invalid" };
53
+ }
54
+ }
55
+ const computer = inventoryComputer.parse(result);
56
+ return {
57
+ kind: "created",
58
+ computer: { computerId: computer.computerId, name: computer.name },
59
+ };
60
+ },
61
+ };
62
+ }
@@ -23,6 +23,7 @@ export declare const standaloneFleetRequestSchema: z.ZodDiscriminatedUnion<[z.Zo
23
23
  resolve: "resolve";
24
24
  select: "select";
25
25
  start: "start";
26
+ state: "state";
26
27
  }>;
27
28
  computerId: z.ZodString;
28
29
  }, z.core.$strict>, z.ZodObject<{
@@ -15,7 +15,7 @@ export const standaloneFleetRequestSchema = z.discriminatedUnion("operation", [
15
15
  page: computerPageSchema,
16
16
  }),
17
17
  z.strictObject({
18
- operation: z.enum(["read", "resolve", "start", "select"]),
18
+ operation: z.enum(["read", "resolve", "state", "start", "select"]),
19
19
  computerId: fleetComputerId,
20
20
  }),
21
21
  z.strictObject({
@@ -4,7 +4,8 @@ import { type PreviewSelector } from "./preview.js";
4
4
  import { type StandaloneTenancyEnv } from "./tenancy.js";
5
5
  import * as z from "zod";
6
6
  import { computerProcessSchema, computerToolResultSchema, readOutputSchema } from "@fungi.computer/computer-contract";
7
- import { ComputerDO as NativeComputerDO, type AttachedBackend, type ComputerEnv } from "@fungi.computer/computer";
7
+ import { ComputerDO as NativeComputerDO, type AttachedBackend, type ComputerEnv, type ComputerFilesRpc } from "@fungi.computer/computer";
8
+ import { patchInputSchema, patchOutputSchema } from "@fungi.computer/knapsack/files-patch";
8
9
  import { listOutputSchema, writeOutputSchema, mkdirOutputSchema, computerPortsOutputSchema, computerPortOutputSchema, computerPortRemovalOutputSchema, processListOutputSchema, processKillOutputSchema, computerOperationResultSchema, type ComputerExecutionHostHooks, type ComputerValue } from "@fungi.computer/computer-contract";
9
10
  import { type WorkflowGitCredential, type WorkflowGitPassGrant } from "@fungi.computer/braid/runtime";
10
11
  import { type ComputerAction, type ComputerGrant } from "@fungi.computer/computer/fleet";
@@ -66,8 +67,13 @@ export type StandaloneWorkflowGitIssuer = Readonly<{
66
67
  export type ComputerFetch = {
67
68
  fetch(request: Request): Promise<Response>;
68
69
  };
70
+ /** The additional native doors an attached Agent may use on this Computer. */
71
+ export type StandaloneAgentComputerRpc = ComputerFilesRpc & Readonly<{
72
+ tool(authority: ComputerGrant, capabilityId: string, input: ComputerValue): Promise<StandaloneComputerResult>;
73
+ filePatch(authority: ComputerGrant, input: z.output<typeof patchInputSchema>): Promise<z.output<typeof patchOutputSchema>>;
74
+ }>;
69
75
  /** Durable workspace and existing backings, with fleet checks at native acceptance. */
70
- export declare class ComputerDO extends NativeComputerDO implements ComputerFetch {
76
+ export declare class ComputerDO extends NativeComputerDO implements ComputerFetch, StandaloneAgentComputerRpc {
71
77
  #private;
72
78
  private readonly previewBindings;
73
79
  constructor(state: DurableObjectState, previewBindings: StandaloneComputerOwnerEnv);
@@ -77,6 +83,17 @@ export declare class ComputerDO extends NativeComputerDO implements ComputerFetc
77
83
  startHostedComputer(authority: ComputerGrant): Promise<{
78
84
  state: "running";
79
85
  }>;
86
+ /** Observe an admitted fleet Computer without starting its backend. */
87
+ readHostedComputerState(authority: ComputerGrant): Promise<{
88
+ state: "queued";
89
+ position: number;
90
+ } | {
91
+ state: "running" | "starting";
92
+ position?: never;
93
+ } | {
94
+ state: "running" | "sleeping";
95
+ position?: never;
96
+ }>;
80
97
  /** A read-only preflight over the maintained hosted-route selector. */
81
98
  buildBackend(): OysterBuildBackend;
82
99
  /**
@@ -120,6 +137,8 @@ export declare class ComputerDO extends NativeComputerDO implements ComputerFetc
120
137
  /** Binary files share the same native filesystem as commands and PTYs. */
121
138
  fileRead(authority: ComputerGrant, path: string): Promise<Uint8Array | null>;
122
139
  fileWrite(authority: ComputerGrant, path: string, bytes: Uint8Array): Promise<void>;
140
+ /** Patch is handled by the Computer's pinned native engine, not by the Agent. */
141
+ filePatch(authority: ComputerGrant, input: z.output<typeof patchInputSchema>): Promise<z.output<typeof patchOutputSchema>>;
123
142
  fileReplace(authority: ComputerGrant, path: string, expectedContent: string, content: string): Promise<{
124
143
  path: string;
125
144
  bytes: number;
@@ -11,6 +11,7 @@ import { Base64 } from "effect/encoding";
11
11
  import * as z from "zod";
12
12
  import { ComputerPortConflictError, computerByteReadReplySchema, computerProcessSchema, installationProcessScope, processStartInputSchema, processStartToolInputSchema, computerCapabilities, computerToolResultSchema, readOutputSchema, } from "@fungi.computer/computer-contract";
13
13
  import { ComputerDO as NativeComputerDO, ComputerOperationConflictError, } from "@fungi.computer/computer";
14
+ import { patchInputSchema, patchOutputSchema, } from "@fungi.computer/knapsack/files-patch";
14
15
  import { ComputerFileConflict, computerExecutionRequestSchema, computerValueSchema, computerPortInputSchema, listInputSchema, listOutputSchema, readBytesInputSchema, writeInputSchema, replaceInputSchema, writeOutputSchema, mkdirOutputSchema, computerPortsOutputSchema, computerPortOutputSchema, computerPortRemovalOutputSchema, processListOutputSchema, processKillOutputSchema, computerOperationResultSchema, mintComputerRequestId, processIdInputSchema, } from "@fungi.computer/computer-contract";
15
16
  import { workflowGitExecutionEnvironment, workflowGitPassGrantSchema, } from "@fungi.computer/braid/runtime";
16
17
  import { checkComputerGrant, computerActionSchema, computerGrantSchema, createFleetBacking, FleetGrantRefused, FleetLifecycle, } from "@fungi.computer/computer/fleet";
@@ -173,6 +174,11 @@ export class ComputerDO extends NativeComputerDO {
173
174
  await run();
174
175
  });
175
176
  }
177
+ /** Observe an admitted fleet Computer without starting its backend. */
178
+ readHostedComputerState(authority) {
179
+ this.#accept(authority, "start");
180
+ return this.#fleetLifecycle().readComputerState();
181
+ }
176
182
  #previewGrant(ownerId) {
177
183
  return {
178
184
  computerId: this.#id(),
@@ -455,6 +461,12 @@ export class ComputerDO extends NativeComputerDO {
455
461
  this.#accept(authority, "files.writeBytes");
456
462
  });
457
463
  }
464
+ /** Patch is handled by the Computer's pinned native engine, not by the Agent. */
465
+ async filePatch(authority, input) {
466
+ return this.applyPatchAfterAcceptance(input, () => {
467
+ this.#accept(authority, "files.edit");
468
+ });
469
+ }
458
470
  async fileReplace(authority, path, expectedContent, content) {
459
471
  try {
460
472
  const result = await this.replaceFileIfUnchangedAfterAcceptance({ path, expectedContent, content }, () => {
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runInstallAppsCli } from "./install-apps-cli.js";
3
+ await runInstallAppsCli();
@@ -1,2 +1,4 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import { installStandaloneAppSet } from "./app-set.js";
3
+ export declare function installApps(args: readonly string[], install?: typeof installStandaloneAppSet): Promise<void>;
4
+ export declare function runInstallAppsCli(args?: readonly string[]): Promise<void>;
@@ -76,7 +76,7 @@ function timingTable(results) {
76
76
  ];
77
77
  return [header, ...rows].map((row) => row.join("\t")).join("\n");
78
78
  }
79
- async function installApps(args) {
79
+ export async function installApps(args, install = installStandaloneAppSet) {
80
80
  const { values } = parseArgs({
81
81
  args: [...args],
82
82
  options: {
@@ -121,7 +121,7 @@ async function installApps(args) {
121
121
  source = { kind: "checkout", directory, revision: `git:${revision}` };
122
122
  }
123
123
  const started = performance.now();
124
- const results = await installStandaloneAppSet({
124
+ const { installed, failed } = await install({
125
125
  source,
126
126
  url,
127
127
  key,
@@ -131,33 +131,43 @@ async function installApps(args) {
131
131
  },
132
132
  });
133
133
  const elapsed = performance.now() - started;
134
- process.stdout.write(`\n${timingTable(results)}\n`);
135
- process.stdout.write(`\nInstalled ${String(results.length)} Apps in ${seconds(elapsed)}. Open ${url}/ to use Tourtois and the installed Apps.\n`);
134
+ process.stdout.write(`\n${timingTable(installed)}\n`);
135
+ process.stdout.write("\nApp\tResult\n");
136
+ for (const app of installed)
137
+ process.stdout.write(`${app.installationId}\tinstalled\n`);
138
+ for (const app of failed)
139
+ process.stdout.write(`${app.installationId}\tfailed: ${app.reason}\n`);
140
+ process.stdout.write(`\nInstalled ${String(installed.length)} Apps; failed ${String(failed.length)} in ${seconds(elapsed)}.${installed.length > 0 ? ` Open ${url}/ to use the installed Apps.` : ""}\n`);
136
141
  if (values.timings !== undefined)
137
142
  await writeFile(values.timings, JSON.stringify({
138
143
  concurrency,
139
144
  elapsedMs: elapsed,
140
- apps: results.map(({ installationId, jobId, timings }) => ({
145
+ apps: installed.map(({ installationId, jobId, timings }) => ({
141
146
  installationId,
142
147
  jobId,
143
148
  timings,
144
149
  })),
150
+ failed,
145
151
  }, null, 2) + "\n");
152
+ if (failed.length > 0)
153
+ process.exitCode = 1;
146
154
  }
147
155
  finally {
148
156
  await rm(scratch, { recursive: true, force: true });
149
157
  }
150
158
  }
151
- const [command, ...rest] = process.argv.slice(2);
152
- if (command === "--help" || command === "-h") {
153
- process.stdout.write(usage + "\n" + installReleaseUsage + "\n");
154
- }
155
- else if (command !== "install-apps" && command !== "install") {
156
- process.stderr.write(`${usage}\n`);
157
- process.exitCode = 2;
159
+ export async function runInstallAppsCli(args = process.argv.slice(2)) {
160
+ const [command, ...rest] = args;
161
+ if (command === "--help" || command === "-h") {
162
+ process.stdout.write(usage + "\n" + installReleaseUsage + "\n");
163
+ }
164
+ else if (command !== "install-apps" && command !== "install") {
165
+ process.stderr.write(`${usage}\n`);
166
+ process.exitCode = 2;
167
+ }
168
+ else
169
+ await (command === "install" ? installReleaseCommand(rest) : installApps(rest)).catch((cause) => {
170
+ process.stderr.write(`${cause instanceof Error ? cause.message : String(cause)}\n`);
171
+ process.exitCode = 1;
172
+ });
158
173
  }
159
- else
160
- await (command === "install" ? installReleaseCommand(rest) : installApps(rest)).catch((cause) => {
161
- process.stderr.write(`${cause instanceof Error ? cause.message : String(cause)}\n`);
162
- process.exitCode = 1;
163
- });
@@ -265,6 +265,7 @@ export declare function standaloneMachineLinkRoutes(env: StandaloneMachineLinkEn
265
265
  /** One device's link; directory, credentials and owner state remain elsewhere. */
266
266
  export declare class MachineLinkDO extends DurableObject<StandaloneMachineLinkEnv> {
267
267
  private readonly link;
268
+ alarm(): Promise<void>;
268
269
  fetch(request: Request): Promise<Response>;
269
270
  deliver(id: MachineLinkIdentity, caller: MachineLinkCaller, agentId: string, envelope: PostEnvelope): Promise<PostDeliveryResult>;
270
271
  operation(id: MachineLinkIdentity, caller: MachineLinkCaller, agentId: string, operation: MachineAgentOperation): Promise<import("effect/Schema").Json>;
@@ -276,6 +277,15 @@ export declare class MachineLinkDO extends DurableObject<StandaloneMachineLinkEn
276
277
  readErasure(): Promise<{
277
278
  erased: boolean;
278
279
  }>;
280
+ deadLetters(id: MachineLinkIdentity, afterKey?: string): Promise<Readonly<{
281
+ letters: readonly Readonly<{
282
+ key: string;
283
+ reason: "refused" | "exhausted" | "expired";
284
+ envelope: PostEnvelope;
285
+ attempts: number;
286
+ }>[];
287
+ nextCursor: string | undefined;
288
+ }>>;
279
289
  webSocketMessage(socket: WebSocket, message: string | ArrayBuffer): Promise<void>;
280
290
  webSocketClose(socket: WebSocket, code: number, reason: string): void;
281
291
  webSocketError(socket: WebSocket): void;
@@ -176,6 +176,9 @@ export class MachineLinkDO extends DurableObject {
176
176
  console.error(operation, cause);
177
177
  },
178
178
  });
179
+ alarm() {
180
+ return this.link.alarm();
181
+ }
179
182
  fetch(request) {
180
183
  return this.link.fetch(request);
181
184
  }
@@ -197,6 +200,9 @@ export class MachineLinkDO extends DurableObject {
197
200
  readErasure() {
198
201
  return this.link.readErasure();
199
202
  }
203
+ deadLetters(id, afterKey) {
204
+ return this.link.deadLetters(id, afterKey);
205
+ }
200
206
  webSocketMessage(socket, message) {
201
207
  return this.link.webSocketMessage(socket, message);
202
208
  }
@@ -61,6 +61,15 @@ export declare abstract class StandaloneRegistry extends OysterRegistry implemen
61
61
  }>[];
62
62
  readonly nextOffset: number | null;
63
63
  } | ({
64
+ state: "queued";
65
+ position: number;
66
+ } & Disposable) | ({
67
+ state: "running" | "starting";
68
+ position?: never | undefined;
69
+ } & Disposable) | ({
70
+ state: "running" | "sleeping";
71
+ position?: never | undefined;
72
+ } & Disposable) | ({
64
73
  state: "running";
65
74
  } & Disposable) | {
66
75
  error: string;
@@ -178,7 +187,7 @@ export declare abstract class StandaloneRegistry extends OysterRegistry implemen
178
187
  appId: string;
179
188
  releaseId: string;
180
189
  sourceRevision: string;
181
- status: "running" | "succeeded" | "failed" | "revoked";
190
+ status: "running" | "succeeded" | "failed" | "interrupted" | "revoked";
182
191
  detail: string | null;
183
192
  release: Release | null;
184
193
  installation: import("../app-builds.js").OysterBuildInstallation | null;
@@ -129,6 +129,15 @@ export class StandaloneRegistry extends OysterRegistry {
129
129
  await this.#fleetStorage.put("selected-computer", input.computerId);
130
130
  return computer;
131
131
  }
132
+ case "state":
133
+ if (this.#computers.read(input.computerId) === null)
134
+ return null;
135
+ return this.#env.COMPUTER.getByName(input.computerId).readHostedComputerState({
136
+ computerId: input.computerId,
137
+ actions: new Set(["start"]),
138
+ expiresAt: Date.now() + 60_000,
139
+ provenance: { actor: memberId },
140
+ });
132
141
  case "start":
133
142
  if (this.#computers.read(input.computerId) === null)
134
143
  return null;
@@ -5,6 +5,8 @@
5
5
  */
6
6
  import { createOysterGitWorker, createOysterGitMcpOwner, oysterGitRoot, } from "../workers-git.js";
7
7
  import { defaultTenant, tenantScope } from "../tenant.js";
8
+ import { oysterPrincipalIsOwner } from "../host.js";
9
+ import { standaloneComputerControl } from "./computer-control.js";
8
10
  import { onBoxVmIngress } from "@fungi.computer/computer";
9
11
  import { WorkerEntrypoint } from "cloudflare:workers";
10
12
  import { installationWorkerEntrypoint } from "../installation-execution.js";
@@ -28,6 +30,8 @@ import { Hono } from "hono";
28
30
  import { HTTPException } from "hono/http-exception";
29
31
  import { standaloneBuildRoutes } from "./builds.js";
30
32
  import { standaloneComputerFleetRoutes, standaloneComputerSelection, } from "./computer-fleet.js";
33
+ import { standaloneAgentComputerRoutes } from "./agent-computers.js";
34
+ import { agentDirectoryForScope } from "./agent-directory.js";
31
35
  import { serveOysterComputerEgress, } from "./computer-egress.js";
32
36
  import { appSourceOwnerRoutes, } from "./app-source.js";
33
37
  export { StandaloneGraphWorkflow } from "./workflows.js";
@@ -65,6 +69,9 @@ const standaloneRoot = (selectComputer) => {
65
69
  homeInstallationId: (env) => env.OYSTER_HOME_INSTALLATION,
66
70
  agents: standaloneAgents,
67
71
  workflows: standaloneWorkflowControl,
72
+ computerControl: (env, scope) => (principal) => oysterPrincipalIsOwner(principal)
73
+ ? standaloneComputerControl(tenantRegistry(env, scope), principal.actor.memberId)
74
+ : null,
68
75
  workspace: async (env, keysLive, scope, request) => standaloneWorkspace(env, keysLive, await selectComputer(tenantRegistry(env, scope), request)),
69
76
  capabilities: (env, workspace, keysLive) => [
70
77
  ...standaloneComputerCapabilities(env, workspace, keysLive),
@@ -97,6 +104,11 @@ const standaloneRoot = (selectComputer) => {
97
104
  ownerRoutes: (env, scope) => appSourceOwnerRoutes(env, scope)
98
105
  .route("/", standaloneBuildRoutes(() => tenantRegistry(env, scope)))
99
106
  .route("/", standaloneComputerFleetRoutes(() => tenantRegistry(env, scope)))
107
+ .route("/", standaloneAgentComputerRoutes({
108
+ attach: (principal, command) => agentDirectoryForScope(env, scope).attachComputer(command, principal, scope.tenantId),
109
+ detach: (principal, command) => agentDirectoryForScope(env, scope).detachComputer(command, principal, scope.tenantId),
110
+ read: (principal, agentId) => agentDirectoryForScope(env, scope).readAgentComputers(agentId, principal, scope.tenantId),
111
+ }))
100
112
  .route("/", standaloneWorkflowWebhookControls(env, () => root.hostFor(env, scope))),
101
113
  });
102
114
  return root;
@@ -72,10 +72,10 @@ export declare const workflowFrameMethods: {
72
72
  readonly input: {
73
73
  readonly [x: string]: S.Json;
74
74
  };
75
+ readonly requestId: string;
75
76
  readonly bindings: Record<string, string | {
76
77
  kind: "team-source";
77
78
  }>;
78
- readonly requestId: string;
79
79
  readonly definition: unknown;
80
80
  readonly delivery?: {
81
81
  readonly agentId: string;
@@ -86,10 +86,10 @@ export declare const workflowFrameMethods: {
86
86
  readonly input: {
87
87
  readonly [x: string]: S.Json;
88
88
  };
89
+ readonly requestId: string;
89
90
  readonly bindings: Record<string, string | {
90
91
  kind: "team-source";
91
92
  }>;
92
- readonly requestId: string;
93
93
  readonly definition: S.Json;
94
94
  readonly published: {
95
95
  workflowId: string;