@fungi.computer/oyster 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) 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/browser/boot.d.ts +2 -2
  8. package/dist/browser/boot.js +10 -3
  9. package/dist/browser/browser.d.ts +1 -1
  10. package/dist/browser/browser.js +2 -1
  11. package/dist/browser/cdp-client.d.ts +2 -0
  12. package/dist/browser/errors.d.ts +2 -2
  13. package/dist/browser/errors.js +2 -2
  14. package/dist/browser/index.d.ts +3 -2
  15. package/dist/browser/index.js +3 -2
  16. package/dist/browser/page.js +11 -4
  17. package/dist/browser/sessions.d.ts +7 -4
  18. package/dist/browser/sessions.js +159 -0
  19. package/dist/browser/url-policy.d.ts +4 -2
  20. package/dist/browser/url-policy.js +4 -2
  21. package/dist/computer-control-port.d.ts +26 -0
  22. package/dist/computer-control-port.js +1 -0
  23. package/dist/host.d.ts +3 -0
  24. package/dist/machine-link/delivery.d.ts +1 -1
  25. package/dist/machine-link/delivery.js +2 -3
  26. package/dist/machine-link/host.d.ts +37 -6
  27. package/dist/machine-link/host.js +209 -14
  28. package/dist/machine-link/link.d.ts +13 -1
  29. package/dist/machine-link/link.js +20 -7
  30. package/dist/mcp.d.ts +144 -10
  31. package/dist/mcp.js +130 -13
  32. package/dist/page/computer-handles.d.ts +3 -3
  33. package/dist/page-script.js +1 -1
  34. package/dist/standalone/agent-computers.d.ts +29 -0
  35. package/dist/standalone/agent-computers.js +101 -0
  36. package/dist/standalone/agent-mail.d.ts +41 -18
  37. package/dist/standalone/agent-model-choice.d.ts +26 -0
  38. package/dist/standalone/agent-model-choice.js +72 -0
  39. package/dist/standalone/agents.d.ts +44 -4
  40. package/dist/standalone/agents.js +502 -46
  41. package/dist/standalone/computer-control.d.ts +4 -0
  42. package/dist/standalone/computer-control.js +62 -0
  43. package/dist/standalone/computer-fleet.d.ts +1 -0
  44. package/dist/standalone/computer-fleet.js +1 -1
  45. package/dist/standalone/computer.d.ts +21 -2
  46. package/dist/standalone/computer.js +12 -0
  47. package/dist/standalone/machine-link.d.ts +10 -0
  48. package/dist/standalone/machine-link.js +6 -0
  49. package/dist/standalone/registry.d.ts +9 -0
  50. package/dist/standalone/registry.js +9 -0
  51. package/dist/standalone/worker.js +12 -0
  52. package/dist/standalone/workflow-binding.d.ts +2 -2
  53. package/dist/standalone/workflow-host.d.ts +11 -11
  54. package/dist/standalone/workflows.d.ts +22 -22
  55. package/dist/storage-frame.d.ts +3 -3
  56. package/dist/workers-git.d.ts +2 -0
  57. package/dist/workers-git.js +2 -0
  58. package/dist/workers.d.ts +8 -8
  59. package/docs/api/agents.md +66 -0
  60. package/docs/api/apps.md +104 -104
  61. package/docs/api/build-contract.md +35 -35
  62. package/docs/api/build.md +12 -12
  63. package/docs/api/host.md +61 -61
  64. package/docs/api/mcp.md +127 -13
  65. package/docs/api/root-data.md +1 -1
  66. package/docs/api/tenancy.md +1 -1
  67. package/docs/api/workers-git.md +3 -2
  68. package/docs/api/workers.md +81 -83
  69. package/package.json +25 -17
@@ -1,51 +1,126 @@
1
1
  import { Hono } from "hono";
2
2
  import { z } from "zod";
3
+ import * as Schema from "effect/Schema";
3
4
  import { correlationKey } from "@fungi.computer/cairn";
4
- import { createMachineLinkPostChannel, MachineLinkPostUnavailable, machineLinkAudience, } from "@fungi.computer/seam/machine-link-protocol";
5
+ import { createMachineLinkPostChannel, MachineLinkPostUnavailable, machineLinkAudience, machineLinkProtocolVersion, } from "@fungi.computer/seam/machine-link-protocol";
5
6
  import { importSeamVerifiers, seamHeader, verifySeamRequest, } from "@fungi.computer/seam";
6
- import { createLocalBackendReplayStore } from "@fungi.computer/computer";
7
+ import { createPost, definePostRoute, PostDeliveryResultSchema, } from "@fungi.computer/carrier/post";
8
+ import { BirdDogMailSchema } from "@fungi.computer/bird-dog/effect";
9
+ import { machineAgentOperationSchema } from "@fungi.computer/seam/machine-link-protocol";
10
+ import { Watchdog, } from "@fungi.computer/watchdog";
7
11
  import { createMachineLinkSockets } from "./presence.js";
8
12
  const attachmentSchema = z.strictObject({
9
13
  deviceId: z.string().min(1).max(256),
10
14
  fenceEpoch: z.int().nonnegative(),
11
15
  });
12
16
  const maximumInFlight = 32;
17
+ const custodyDeadlineMs = 7 * 24 * 60 * 60 * 1_000;
18
+ const recipientOffline = {
19
+ status: "accepted",
20
+ receipt: {
21
+ status: "held",
22
+ reason: "recipient_offline",
23
+ message: "waiting: recipient offline",
24
+ },
25
+ };
26
+ // Carrier persists the nested application receipt, not the Post result wrapper.
27
+ const custodyReceipt = (status, receipt) => Schema.decodeUnknownSync(PostDeliveryResultSchema)({ status, receipt });
28
+ const previousCustodyReceipt = (existing) => {
29
+ if (existing.state === "delivered")
30
+ return existing.receipt === undefined
31
+ ? "accepted"
32
+ : custodyReceipt("accepted", existing.receipt);
33
+ if (existing.state === "dead" && existing.receipt !== undefined)
34
+ return custodyReceipt("refused", existing.receipt);
35
+ return recipientOffline;
36
+ };
37
+ const custodyAddress = Schema.Struct({
38
+ scopeId: Schema.String,
39
+ agentId: Schema.String,
40
+ });
41
+ const machineAgentEvent = Schema.Struct({
42
+ actorAgentId: Schema.String,
43
+ operation: machineAgentOperationSchema,
44
+ });
45
+ const machineMailEnvelope = Schema.Struct({
46
+ key: Schema.String,
47
+ kind: Schema.Literal("agentMail"),
48
+ address: custodyAddress,
49
+ event: BirdDogMailSchema,
50
+ });
51
+ const machineAgentEnvelope = Schema.Struct({
52
+ key: Schema.String,
53
+ kind: Schema.Literal("machineAgent"),
54
+ address: custodyAddress,
55
+ event: machineAgentEvent,
56
+ });
13
57
  /**
14
58
  * Native device socket I/O. Pins/member authority and mail admission are supplied
15
- * by their existing owners. Native attachments survive hibernation; pending
16
- * Post I/O does not claim durable custody and is retried by Carrier.
59
+ * by their existing owners. Native attachments survive hibernation; Carrier
60
+ * owns durable custody and per-Agent ordering across socket lifetimes.
17
61
  */
18
62
  export class MachineLinkHost {
19
63
  context;
64
+ owner;
20
65
  options;
21
66
  channels = new Map();
22
- replay;
23
- routes;
67
+ replayRuntime;
68
+ router;
69
+ custodyRoutes;
70
+ deviceId;
71
+ custodyRuntime;
72
+ receipts = new Map();
24
73
  constructor(context, owner, options) {
25
74
  this.context = context;
75
+ this.owner = owner;
26
76
  this.options = options;
27
- this.replay = createLocalBackendReplayStore(owner);
77
+ this.custodyRoutes = {
78
+ agentMail: definePostRoute({
79
+ addressKind: "machineLinkAgent",
80
+ deadlineMs: custodyDeadlineMs,
81
+ address: custodyAddress,
82
+ event: BirdDogMailSchema,
83
+ name: ({ agentId }) => agentId,
84
+ namespace: {
85
+ getByName: () => ({
86
+ deliver: (envelope) => this.deliverToSocket(envelope),
87
+ }),
88
+ },
89
+ }),
90
+ };
28
91
  context.setWebSocketAutoResponse(new WebSocketRequestResponsePair("ping", "pong"));
29
- this.routes = new Hono().get("/api/teams/:teamId/local/mesh/:deviceId/connect", async (context) => {
92
+ this.router = new Hono().get("/api/teams/:teamId/local/mesh/:deviceId/connect", async (context) => {
30
93
  if (context.req.param("teamId") !== options.teamId)
31
94
  return context.body(null, 403);
32
95
  const request = context.req.raw;
33
- if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket")
34
- return context.body(null, 426);
35
96
  const deviceId = context.req.param("deviceId");
36
97
  try {
37
98
  const device = await options.authorize(deviceId);
38
- const keyId = await verifySeamRequest(await importSeamVerifiers({ [deviceId]: device.publicKey }), machineLinkAudience(options.teamId, deviceId), request.method, request.url, request.headers.get(seamHeader), null, this.replay);
99
+ const keyId = await verifySeamRequest(await importSeamVerifiers({ [deviceId]: device.publicKey }), machineLinkAudience(options.teamId, deviceId), request.method, request.url, request.headers.get(seamHeader), null, await this.replayStore());
39
100
  if (keyId !== deviceId)
40
101
  return context.body(null, 403);
102
+ // Seam verifies the signed path and query before we decide whether
103
+ // this client speaks the one supported wire contract.
104
+ const versions = new URL(request.url).searchParams.getAll("version");
105
+ if (versions.length !== 1 ||
106
+ versions[0] !== machineLinkProtocolVersion)
107
+ return context.body("upgrade fungi", 426, {
108
+ "x-fungi-machine-link-action": "upgrade fungi",
109
+ });
110
+ if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket")
111
+ return context.body(null, 426);
41
112
  const pair = new WebSocketPair();
42
113
  pair[1].serializeAttachment({
43
114
  deviceId,
44
115
  fenceEpoch: device.fenceEpoch,
45
116
  });
46
117
  createMachineLinkSockets(this.context, deviceId).accept(pair[1]);
47
- await this.publishDirectory(pair[1], deviceId);
48
- return new Response(null, { status: 101, webSocket: pair[0] });
118
+ const response = new Response(null, {
119
+ status: 101,
120
+ webSocket: pair[0],
121
+ });
122
+ this.context.waitUntil(this.connected(pair[1], deviceId));
123
+ return response;
49
124
  }
50
125
  catch (cause) {
51
126
  this.options.report("machine-link.dial", cause);
@@ -54,9 +129,21 @@ export class MachineLinkHost {
54
129
  });
55
130
  }
56
131
  fetch(request) {
57
- return Promise.resolve(this.routes.fetch(request));
132
+ return Promise.resolve(this.router.fetch(request));
133
+ }
134
+ replayStore() {
135
+ this.replayRuntime ??= this.options.replay();
136
+ return this.replayRuntime;
137
+ }
138
+ /** Start the fresh socket and then hand durable rows to its live channel. */
139
+ async connected(socket, deviceId) {
140
+ this.deviceId = deviceId;
141
+ await this.publishDirectory(socket, deviceId);
142
+ await this.releaseCustody();
143
+ await this.drainCustody();
58
144
  }
59
145
  async publishDirectory(socket, deviceId) {
146
+ this.deviceId = deviceId;
60
147
  // Ordered before the addressed operation, but its metadata ACK is not a
61
148
  // prerequisite for work admission (including the deliberately lost ACK).
62
149
  const agents = await this.options.directory();
@@ -109,19 +196,127 @@ export class MachineLinkHost {
109
196
  return channel;
110
197
  }
111
198
  async deliver(deviceId, envelope) {
199
+ this.deviceId = deviceId;
112
200
  await this.options.authorize(deviceId);
113
201
  const links = createMachineLinkSockets(this.context, deviceId);
114
202
  const socket = this.context
115
203
  .getWebSockets(`machine-link:${deviceId}`)
116
204
  .find((candidate) => links.isCurrent(candidate));
205
+ const typedEnvelope = Schema.decodeUnknownSync(Schema.Union([machineMailEnvelope, machineAgentEnvelope]))(envelope);
206
+ if (typedEnvelope.kind === "machineAgent")
207
+ return await this.deliverLiveOperation(deviceId, socket, typedEnvelope);
208
+ return await this.deliverCustodyMail(deviceId, socket, typedEnvelope);
209
+ }
210
+ async deliverLiveOperation(deviceId, socket, envelope) {
117
211
  if (socket === undefined)
118
212
  throw new MachineLinkPostUnavailable({ reason: "closed" });
119
213
  await this.publishDirectory(socket, deviceId);
120
214
  return await this.channel(socket, deviceId).deliver(envelope);
121
215
  }
216
+ async deliverCustodyMail(deviceId, socket, envelope) {
217
+ const { post } = await this.custody();
218
+ const existing = post.read(envelope.key);
219
+ if (existing !== undefined) {
220
+ this.owner.transactionSync(() => {
221
+ post.send(envelope);
222
+ });
223
+ return previousCustodyReceipt(existing);
224
+ }
225
+ this.owner.transactionSync(() => {
226
+ post.send(envelope);
227
+ });
228
+ await this.options.scheduleWake(post.nextWake() ?? Date.now());
229
+ if (socket === undefined)
230
+ return recipientOffline;
231
+ await this.publishDirectory(socket, deviceId);
232
+ await this.drainCustody();
233
+ const receipt = this.receipts.get(envelope.key);
234
+ this.receipts.delete(envelope.key);
235
+ return receipt ?? recipientOffline;
236
+ }
237
+ /** Run due Carrier work; expired rows remain visible as Carrier dead letters. */
238
+ async alarm() {
239
+ await this.drainCustody();
240
+ }
241
+ /** Expose terminal custody rows so S9 can stage its own disposition reply. */
242
+ async deadLetters(afterKey) {
243
+ const { post } = await this.custody();
244
+ return post.deadLetters(64, afterKey);
245
+ }
246
+ async deliverToSocket(envelope) {
247
+ const deviceId = this.deviceId;
248
+ if (deviceId === undefined)
249
+ throw new MachineLinkPostUnavailable({ reason: "closed" });
250
+ const links = createMachineLinkSockets(this.context, deviceId);
251
+ const socket = this.context
252
+ .getWebSockets(`machine-link:${deviceId}`)
253
+ .find((candidate) => links.isCurrent(candidate));
254
+ if (socket === undefined)
255
+ throw new MachineLinkPostUnavailable({ reason: "closed" });
256
+ const result = await this.channel(socket, deviceId).deliver(envelope);
257
+ this.receipts.set(envelope.key, result);
258
+ return result;
259
+ }
260
+ async custody() {
261
+ this.custodyRuntime ??= this.createCustody();
262
+ return await this.custodyRuntime;
263
+ }
264
+ async createCustody() {
265
+ const postRef = { current: undefined };
266
+ const watchdog = await Watchdog.make({
267
+ owner: this.owner,
268
+ isAlive: () => Promise.resolve(false),
269
+ execute: (job) => {
270
+ const post = postRef.current;
271
+ if (post === undefined)
272
+ throw new Error("Carrier Post runtime is not initialized");
273
+ return post.execute(job);
274
+ },
275
+ wake: {
276
+ recompute: async () => {
277
+ const at = postRef.current?.nextWake();
278
+ if (at !== null && at !== undefined)
279
+ await this.options.scheduleWake(at);
280
+ },
281
+ },
282
+ });
283
+ const post = createPost({
284
+ routes: this.custodyRoutes,
285
+ owner: this.owner,
286
+ transactional: watchdog.transactional,
287
+ readJob: watchdog.read,
288
+ now: Date.now,
289
+ scheduleWake: this.options.scheduleWake,
290
+ });
291
+ postRef.current = post;
292
+ return { post, watchdog };
293
+ }
294
+ async releaseCustody() {
295
+ const { post } = await this.custody();
296
+ let cursor;
297
+ do {
298
+ cursor = await post.releaseNow(64, cursor);
299
+ } while (cursor !== undefined);
300
+ }
301
+ async drainCustody() {
302
+ const { post, watchdog } = await this.custody();
303
+ let cursor;
304
+ do {
305
+ cursor = await post.enqueueDue(64, cursor);
306
+ } while (cursor !== undefined);
307
+ for (let count = 0; count < 64; count += 1) {
308
+ const tick = await watchdog.tick();
309
+ if (tick.status !== "settled")
310
+ break;
311
+ }
312
+ const at = post.nextWake();
313
+ if (at !== null)
314
+ await this.options.scheduleWake(at);
315
+ }
122
316
  async message(socket, message) {
123
317
  try {
124
318
  const { deviceId } = attachmentSchema.parse(socket.deserializeAttachment());
319
+ this.deviceId = deviceId;
125
320
  if (!createMachineLinkSockets(this.context, deviceId).isCurrent(socket)) {
126
321
  socket.close(4001, "replaced");
127
322
  return;
@@ -5,7 +5,7 @@ import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
5
5
  import { type MachineLinkAuthority, type MachineLinkCaller, type MachineLinkFenceFact, type MachineLinkFenceResult, type MachineLinkIdentity } from "./authority.js";
6
6
  /** The Durable Object state a machine link needs from its host class. */
7
7
  export type MachineLinkState = Pick<DurableObjectState, "getWebSockets" | "acceptWebSocket" | "setWebSocketAutoResponse" | "waitUntil"> & Readonly<{
8
- storage: WatchdogSqliteOwner & Pick<DurableObjectStorage, "get" | "put">;
8
+ storage: WatchdogSqliteOwner & Pick<DurableObjectStorage, "get" | "put" | "setAlarm">;
9
9
  }>;
10
10
  /** The host's own storage erasure, so readback matches its other owners. */
11
11
  export type MachineLinkStorageErasure<Readback> = Readonly<{
@@ -41,6 +41,18 @@ export declare function createMachineLink<Readback>(state: MachineLinkState, opt
41
41
  preflightErasure: (teamId: string, deviceId: string) => Promise<void>;
42
42
  eraseForTeam(teamId: string, deviceId: string): Promise<Readback>;
43
43
  readErasure: () => Promise<Readback>;
44
+ /** Carrier terminal rows, including expired custody, remain observable for S9. */
45
+ deadLetters(input: MachineLinkIdentity, afterKey?: string): Promise<Readonly<{
46
+ letters: readonly Readonly<{
47
+ key: string;
48
+ reason: "refused" | "exhausted" | "expired";
49
+ envelope: PostEnvelope;
50
+ attempts: number;
51
+ }>[];
52
+ nextCursor: string | undefined;
53
+ }>>;
54
+ /** Reconcile durable Carrier custody after a DO alarm or physical restart. */
55
+ alarm(): Promise<void>;
44
56
  webSocketMessage(socket: WebSocket, message: string | ArrayBuffer): Promise<void>;
45
57
  webSocketClose(socket: WebSocket, code: number, reason: string): void;
46
58
  webSocketError(socket: WebSocket): void;
@@ -6,7 +6,6 @@ import { agentMailAddressSchema, machineAgentOperationSchema, MachineLinkPostUna
6
6
  import { machineLinkFenceFactSchema, machineLinkIdentitySchema, machineLinkName, MachineLinkRefused, } from "./authority.js";
7
7
  import { MachineLinkHost } from "./host.js";
8
8
  const pinKey = "machine-link:identity";
9
- const maximumMailBytes = 256 * 1024;
10
9
  /**
11
10
  * One device's link inside its own Durable Object. The host's DO class
12
11
  * delegates every entry point here. Identity is pinned on the first live
@@ -16,6 +15,11 @@ const maximumMailBytes = 256 * 1024;
16
15
  export function createMachineLink(state, options) {
17
16
  const { authority } = options;
18
17
  let host;
18
+ let replayStore;
19
+ const openReplayStore = () => {
20
+ replayStore ??= import("@fungi.computer/computer").then(({ createLocalBackendReplayStore }) => createLocalBackendReplayStore(state.storage));
21
+ return replayStore;
22
+ };
19
23
  const match = (identity) => {
20
24
  if (!options.addressed(identity))
21
25
  throw new MachineLinkRefused("actor_not_allowed");
@@ -98,9 +102,7 @@ export function createMachineLink(state, options) {
98
102
  const target = await checkedTarget(identity, caller, address.agentId, null);
99
103
  if (envelope.key !== body.key ||
100
104
  address.scopeId !== target.teamId ||
101
- address.agentId !== body.to.agentId ||
102
- new TextEncoder().encode(JSON.stringify(body)).byteLength >
103
- maximumMailBytes)
105
+ address.agentId !== body.to.agentId)
104
106
  return "refused";
105
107
  return await authority.deliverHome(identity, caller, target, envelope);
106
108
  };
@@ -113,8 +115,10 @@ export function createMachineLink(state, options) {
113
115
  throw new MachineLinkRefused("actor_not_allowed");
114
116
  return await current(identity);
115
117
  },
118
+ replay: openReplayStore,
116
119
  directory: () => authority.directory(identity),
117
120
  receive: (_device, envelope) => receive(identity, envelope),
121
+ scheduleWake: (at) => state.storage.setAlarm(at),
118
122
  report: options.report,
119
123
  }));
120
124
  };
@@ -151,9 +155,7 @@ export function createMachineLink(state, options) {
151
155
  address.scopeId !== facts.teamId ||
152
156
  address.agentId !== facts.targetAgentId ||
153
157
  body.to.agentId !== facts.targetAgentId ||
154
- body.from.agentId !== facts.callerAgentId ||
155
- new TextEncoder().encode(JSON.stringify(body)).byteLength >
156
- maximumMailBytes)
158
+ body.from.agentId !== facts.callerAgentId)
157
159
  return "refused";
158
160
  return await hostFor(device).deliver(device.deviceId, envelope);
159
161
  },
@@ -203,6 +205,17 @@ export function createMachineLink(state, options) {
203
205
  return await options.erasure.erase();
204
206
  },
205
207
  readErasure: () => options.erasure.read(),
208
+ /** Carrier terminal rows, including expired custody, remain observable for S9. */
209
+ async deadLetters(input, afterKey) {
210
+ const device = await current(input);
211
+ return await hostFor(device).deadLetters(afterKey);
212
+ },
213
+ /** Reconcile durable Carrier custody after a DO alarm or physical restart. */
214
+ async alarm() {
215
+ const identity = await savedIdentity();
216
+ if (identity !== null)
217
+ await hostFor(identity).alarm();
218
+ },
206
219
  async webSocketMessage(socket, message) {
207
220
  const identity = await savedIdentity();
208
221
  if (identity === null) {
package/dist/mcp.d.ts CHANGED
@@ -1,7 +1,12 @@
1
+ import { z } from "zod";
1
2
  import { type AppToolPolicy } from "./apps/tools.js";
2
3
  import type { DeclaredOperations, Installation } from "./apps/installation.js";
3
4
  import { type CapabilitySource, type Grants, type ModuleRegistration } from "@fungi.computer/mycelium";
4
5
  import { appToolSchemas } from "@fungi.computer/forage";
6
+ import { computerPageSchema } from "@fungi.computer/computer/fleet/inventory-wire";
7
+ import type { InvocationContext } from "@fungi.computer/mycelium";
8
+ import type { ComputerControlPort } from "./computer-control-port.js";
9
+ export type { ComputerControlPort } from "./computer-control-port.js";
5
10
  import type { OysterHost, OysterPrincipal } from "./host.js";
6
11
  import type { TenantPrincipal } from "./tenant.js";
7
12
  import { type VerifiedRelease } from "./release.js";
@@ -12,6 +17,135 @@ export type OysterMcpCatalogSnapshot = Readonly<{
12
17
  /** An omitted module has no grant. */
13
18
  grants: Grants;
14
19
  }>;
20
+ declare const fleetComputerId: z.ZodObject<{
21
+ computerId: z.ZodString;
22
+ }, z.core.$strict>;
23
+ declare const fleetCreate: z.ZodObject<{
24
+ name: z.ZodDefault<z.ZodNullable<z.ZodString>>;
25
+ requestId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
26
+ }, z.core.$strict>;
27
+ declare const fleetCreation: z.ZodDiscriminatedUnion<[z.ZodObject<{
28
+ kind: z.ZodLiteral<"created">;
29
+ requestId: z.ZodString;
30
+ computer: z.ZodObject<{
31
+ computerId: z.ZodString;
32
+ name: z.ZodString;
33
+ }, z.core.$strict>;
34
+ }, z.core.$strict>, z.ZodObject<{
35
+ kind: z.ZodEnum<{
36
+ denied: "denied";
37
+ uncertain: "uncertain";
38
+ conflict: "conflict";
39
+ invalid: "invalid";
40
+ }>;
41
+ requestId: z.ZodString;
42
+ }, z.core.$strict>], "kind">;
43
+ /** One set of schemas and operations for MCP and direct broker invocation. */
44
+ export declare function computerControlSpecs(port: ComputerControlPort): {
45
+ list: {
46
+ replay: "safe";
47
+ access: "read";
48
+ description: string;
49
+ input: z.ZodObject<{
50
+ limit: z.ZodDefault<z.ZodNumber>;
51
+ offset: z.ZodDefault<z.ZodNumber>;
52
+ }, z.core.$strict>;
53
+ output: z.ZodObject<{
54
+ computers: z.ZodArray<z.ZodObject<{
55
+ computerId: z.ZodString;
56
+ name: z.ZodString;
57
+ createdAt: z.ZodNumber;
58
+ updatedAt: z.ZodNumber;
59
+ }, z.core.$strict>>;
60
+ nextOffset: z.ZodNullable<z.ZodNumber>;
61
+ }, z.core.$strict>;
62
+ /** Return an admitted inventory page to MCP or the retained broker. */
63
+ execute: (input: z.output<typeof computerPageSchema>, context: InvocationContext) => Promise<{
64
+ computers: {
65
+ computerId: string;
66
+ name: string;
67
+ createdAt: number;
68
+ updatedAt: number;
69
+ }[];
70
+ nextOffset: number | null;
71
+ }>;
72
+ };
73
+ state: {
74
+ replay: "safe";
75
+ access: "read";
76
+ description: string;
77
+ input: z.ZodObject<{
78
+ computerId: z.ZodString;
79
+ }, z.core.$strict>;
80
+ output: z.ZodDiscriminatedUnion<[z.ZodObject<{
81
+ state: z.ZodEnum<{
82
+ running: "running";
83
+ sleeping: "sleeping";
84
+ starting: "starting";
85
+ }>;
86
+ }, z.core.$strict>, z.ZodObject<{
87
+ state: z.ZodLiteral<"queued">;
88
+ position: z.ZodNumber;
89
+ }, z.core.$strict>], "state">;
90
+ /** Return liveness without waking the runtime. */
91
+ execute: (input: z.output<typeof fleetComputerId>, context: InvocationContext) => Promise<{
92
+ state: "running" | "sleeping" | "starting";
93
+ } | {
94
+ state: "queued";
95
+ position: number;
96
+ }>;
97
+ };
98
+ start: {
99
+ replay: "unsafe";
100
+ description: string;
101
+ input: z.ZodObject<{
102
+ computerId: z.ZodString;
103
+ }, z.core.$strict>;
104
+ output: z.ZodDiscriminatedUnion<[z.ZodObject<{
105
+ state: z.ZodEnum<{
106
+ running: "running";
107
+ sleeping: "sleeping";
108
+ starting: "starting";
109
+ }>;
110
+ }, z.core.$strict>, z.ZodObject<{
111
+ state: z.ZodLiteral<"queued">;
112
+ position: z.ZodNumber;
113
+ }, z.core.$strict>], "state">;
114
+ /** Admit and start this Computer. */
115
+ execute: (input: z.output<typeof fleetComputerId>, context: InvocationContext) => Promise<{
116
+ state: "running" | "sleeping" | "starting";
117
+ } | {
118
+ state: "queued";
119
+ position: number;
120
+ }>;
121
+ };
122
+ create: {
123
+ replay: "unsafe";
124
+ description: string;
125
+ input: z.ZodObject<{
126
+ name: z.ZodDefault<z.ZodNullable<z.ZodString>>;
127
+ requestId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
128
+ }, z.core.$strict>;
129
+ output: z.ZodDiscriminatedUnion<[z.ZodObject<{
130
+ kind: z.ZodLiteral<"created">;
131
+ requestId: z.ZodString;
132
+ computer: z.ZodObject<{
133
+ computerId: z.ZodString;
134
+ name: z.ZodString;
135
+ }, z.core.$strict>;
136
+ }, z.core.$strict>, z.ZodObject<{
137
+ kind: z.ZodEnum<{
138
+ denied: "denied";
139
+ uncertain: "uncertain";
140
+ conflict: "conflict";
141
+ invalid: "invalid";
142
+ }>;
143
+ requestId: z.ZodString;
144
+ }, z.core.$strict>], "kind">;
145
+ /** Mint one identity or preserve the requestId for an uncertain retry. */
146
+ execute: (input: z.output<typeof fleetCreate>, context: InvocationContext) => Promise<z.output<typeof fleetCreation>>;
147
+ };
148
+ };
15
149
  /** An installation ID as a guest binding name: `spawn-git` becomes `SPAWN_GIT`. */
16
150
  export declare function appBindingName(installationId: string): string;
17
151
  /** Why an App method call from the MCP catalog was refused. */
@@ -86,9 +220,9 @@ export declare function oysterAppMethods(host: Pick<OysterHost, "installations"
86
220
  }>;
87
221
  /**
88
222
  * Capture the host's fixed Computer for one caller as a Knapsack source.
89
- * The host decides whether the caller holds Computer access; the Computer
90
- * owner accepts every call. Null when the host has no Computer or the caller
91
- * has no access. Grants name exactly the one captured module.
223
+ * The host decides whether the caller holds Computer access; the ComputerDO
224
+ * still checks its host-issued grant on every call. Null when the host has no
225
+ * Computer or the caller has no access. Grants name the captured module.
92
226
  */
93
227
  export declare function oysterComputerTools(host: Pick<OysterHost, "computerId" | "computer">, principal: OysterPrincipal): Promise<(CapabilitySource & {
94
228
  readonly grants: Grants;
@@ -98,11 +232,11 @@ export declare function oysterComputerTools(host: Pick<OysterHost, "computerId"
98
232
  * methods, plus the host's Computer when the caller holds Computer access.
99
233
  * A key sees exactly the installations and levels its own grants admit, and
100
234
  * the Computer only with its own `computer.use` grant; each App retains its
101
- * own caller checks regardless. The owner's stored write approval of an
102
- * Agent's App tools is that principal's confirmation for App writes, so a
103
- * headless client can write; the shared MCP server asks the client to
104
- * approve every other write (an owner's own calls, the Computer). Each call
105
- * still re-admits the key, rechecks that same stored approval, and passes
106
- * the App's gate.
235
+ * own caller checks regardless. App writes use the host's approval record;
236
+ * stored approval of an Agent's App tools can permit headless App writes.
237
+ * The shared MCP server elicits consent for explicitly confirmed operations,
238
+ * including every advertised Workspace Computer call, even reads. Each call
239
+ * still re-admits the key, checks current App approval and passes the App's
240
+ * gate.
107
241
  */
108
- export declare function oysterMcpCatalogLoad(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "computer" | "capabilities" | "appToolPolicy" | "keys" | "workflows">): (principal: TenantPrincipal) => Promise<OysterMcpCatalogSnapshot>;
242
+ export declare function oysterMcpCatalogLoad(host: Pick<OysterHost, "installations" | "instances" | "artifacts" | "guestLimits" | "backgroundOriginKey" | "computerId" | "computer" | "computerControl" | "capabilities" | "appToolPolicy" | "keys" | "workflows">): (principal: TenantPrincipal) => Promise<OysterMcpCatalogSnapshot>;