@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.
- package/dist/agent-computer-source.d.ts +42 -0
- package/dist/agent-computer-source.js +88 -0
- package/dist/agent-computers.d.ts +104 -0
- package/dist/agent-computers.js +227 -0
- package/dist/agents.d.ts +10 -0
- package/dist/agents.js +33 -0
- package/dist/browser/boot.d.ts +2 -2
- package/dist/browser/boot.js +10 -3
- package/dist/browser/browser.d.ts +1 -1
- package/dist/browser/browser.js +2 -1
- package/dist/browser/cdp-client.d.ts +2 -0
- package/dist/browser/errors.d.ts +2 -2
- package/dist/browser/errors.js +2 -2
- package/dist/browser/index.d.ts +3 -2
- package/dist/browser/index.js +3 -2
- package/dist/browser/page.js +11 -4
- package/dist/browser/sessions.d.ts +7 -4
- package/dist/browser/sessions.js +159 -0
- package/dist/browser/url-policy.d.ts +4 -2
- package/dist/browser/url-policy.js +4 -2
- package/dist/computer-control-port.d.ts +26 -0
- package/dist/computer-control-port.js +1 -0
- package/dist/host.d.ts +3 -0
- package/dist/machine-link/delivery.d.ts +1 -1
- package/dist/machine-link/delivery.js +2 -3
- package/dist/machine-link/host.d.ts +37 -6
- package/dist/machine-link/host.js +209 -14
- package/dist/machine-link/link.d.ts +13 -1
- package/dist/machine-link/link.js +20 -7
- package/dist/mcp.d.ts +144 -10
- package/dist/mcp.js +130 -13
- package/dist/page/computer-handles.d.ts +3 -3
- package/dist/page-script.js +1 -1
- package/dist/standalone/agent-computers.d.ts +29 -0
- package/dist/standalone/agent-computers.js +101 -0
- package/dist/standalone/agent-mail.d.ts +41 -18
- package/dist/standalone/agent-model-choice.d.ts +26 -0
- package/dist/standalone/agent-model-choice.js +72 -0
- package/dist/standalone/agents.d.ts +44 -4
- package/dist/standalone/agents.js +502 -46
- package/dist/standalone/computer-control.d.ts +4 -0
- package/dist/standalone/computer-control.js +62 -0
- package/dist/standalone/computer-fleet.d.ts +1 -0
- package/dist/standalone/computer-fleet.js +1 -1
- package/dist/standalone/computer.d.ts +21 -2
- package/dist/standalone/computer.js +12 -0
- package/dist/standalone/machine-link.d.ts +10 -0
- package/dist/standalone/machine-link.js +6 -0
- package/dist/standalone/registry.d.ts +9 -0
- package/dist/standalone/registry.js +9 -0
- package/dist/standalone/worker.js +12 -0
- package/dist/standalone/workflow-binding.d.ts +2 -2
- package/dist/standalone/workflow-host.d.ts +11 -11
- package/dist/standalone/workflows.d.ts +22 -22
- package/dist/storage-frame.d.ts +3 -3
- package/dist/workers-git.d.ts +2 -0
- package/dist/workers-git.js +2 -0
- package/dist/workers.d.ts +8 -8
- package/docs/api/agents.md +66 -0
- package/docs/api/apps.md +104 -104
- package/docs/api/build-contract.md +35 -35
- package/docs/api/build.md +12 -12
- package/docs/api/host.md +61 -61
- package/docs/api/mcp.md +127 -13
- package/docs/api/root-data.md +1 -1
- package/docs/api/tenancy.md +1 -1
- package/docs/api/workers-git.md +3 -2
- package/docs/api/workers.md +81 -83
- 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 {
|
|
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;
|
|
16
|
-
*
|
|
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
|
-
|
|
23
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
48
|
-
|
|
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.
|
|
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
|
|
90
|
-
*
|
|
91
|
-
* has no access. Grants name
|
|
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.
|
|
102
|
-
* Agent's App tools
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* still re-admits the key,
|
|
106
|
-
*
|
|
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>;
|