@fungi.computer/oyster 0.6.0 → 0.8.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/README.md +133 -78
- package/dist/agent-archive-projection.d.ts +8 -0
- package/dist/agent-archive-projection.js +49 -0
- package/dist/agent-schedules.d.ts +61 -0
- package/dist/agent-schedules.js +19 -0
- package/dist/agents.d.ts +35 -14
- package/dist/agents.js +93 -144
- package/dist/app-builds.d.ts +7 -2
- package/dist/app-builds.js +19 -2
- package/dist/approval-capability.d.ts +1 -1
- package/dist/approval-capability.js +11 -16
- package/dist/approval-home.d.ts +2 -1
- package/dist/approval-home.js +5 -3
- package/dist/apps/catalog.js +1 -1
- package/dist/apps/gate.d.ts +4 -7
- package/dist/apps/gate.js +14 -29
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/installation.d.ts +4 -42
- package/dist/apps/installation.js +7 -75
- package/dist/apps/operations.js +0 -10
- package/dist/apps/permissions.d.ts +18 -8
- package/dist/apps/permissions.js +7 -1
- package/dist/auth.d.ts +1 -24
- package/dist/auth.js +9 -39
- package/dist/authorization.d.ts +307 -0
- package/dist/authorization.js +542 -0
- package/dist/boot-host-script.js +1 -1
- package/dist/build/build-jobs.d.ts +2 -2
- package/dist/build/candidate-builder.d.ts +2 -0
- package/dist/build/candidate-builder.js +5 -1
- package/dist/build/candidate-cli.js +3 -0
- package/dist/build/candidate-cli.mjs +70 -22
- package/dist/build/job-log.d.ts +23 -0
- package/dist/build/job-log.js +88 -0
- package/dist/build/local-build.d.ts +9 -1
- package/dist/build/local-build.js +11 -9
- package/dist/cloudflare.d.ts +4 -1
- package/dist/cloudflare.js +1 -1
- package/dist/files.js +9 -8
- package/dist/git-admission.d.ts +3 -3
- package/dist/git-admission.js +4 -6
- package/dist/git-door.d.ts +8 -7
- package/dist/git-door.js +18 -17
- package/dist/git.d.ts +1 -1
- package/dist/host.d.ts +33 -40
- package/dist/host.js +156 -275
- package/dist/installation-execution.js +14 -5
- package/dist/installation-operations.d.ts +4 -180
- package/dist/installation-operations.js +1 -5
- package/dist/installation-store-generation.d.ts +4 -1
- package/dist/installation-store-generation.js +59 -6
- package/dist/internal/delivery.js +0 -1
- package/dist/internal/host-gate.d.ts +3 -2
- package/dist/internal/host-gate.js +5 -18
- package/dist/internal/invocation-timing.d.ts +16 -0
- package/dist/internal/invocation-timing.js +23 -0
- package/dist/internal/registry-installations.js +1 -0
- package/dist/invocation.d.ts +15 -14
- package/dist/invocation.js +58 -26
- package/dist/keys.d.ts +2 -19
- package/dist/keys.js +1 -37
- package/dist/mcp.d.ts +15 -27
- package/dist/mcp.js +105 -155
- package/dist/page/agent-browser.d.ts +15 -0
- package/dist/page/agent-browser.js +162 -0
- package/dist/page/app-permissions-port.d.ts +3 -0
- package/dist/page/app-permissions-port.js +43 -0
- package/dist/page/app-permissions.d.ts +11 -0
- package/dist/page/app-permissions.js +296 -0
- package/dist/page/isolated-page-theme.d.ts +2 -0
- package/dist/page/isolated-page-theme.js +11 -0
- package/dist/page/mount.d.ts +2 -13
- package/dist/page/mount.js +7 -93
- package/dist/page-script.d.ts +0 -3
- package/dist/page-script.js +3 -4
- package/dist/standalone/agent-archive-owner.d.ts +42 -0
- package/dist/standalone/agent-archive-owner.js +116 -0
- package/dist/standalone/agent-archive-schedules.d.ts +6 -0
- package/dist/standalone/agent-archive-schedules.js +76 -0
- package/dist/standalone/agent-archive.d.ts +31 -0
- package/dist/standalone/agent-archive.js +67 -0
- package/dist/standalone/agent-computers.d.ts +7 -6
- package/dist/standalone/agent-computers.js +4 -7
- package/dist/standalone/agent-mail.d.ts +2 -2
- package/dist/standalone/agents.d.ts +186 -20
- package/dist/standalone/agents.js +251 -30
- package/dist/standalone/app-builds-binding.js +1 -1
- package/dist/standalone/app-set.js +2 -54
- package/dist/standalone/app-source.d.ts +3 -4
- package/dist/standalone/app-source.js +12 -9
- package/dist/standalone/builds.d.ts +2 -14
- package/dist/standalone/builds.js +15 -13
- package/dist/standalone/computer-fleet.d.ts +4 -4
- package/dist/standalone/computer-fleet.js +13 -42
- package/dist/standalone/computer.d.ts +4 -3
- package/dist/standalone/computer.js +52 -53
- package/dist/standalone/dashboard-approval.d.ts +10 -0
- package/dist/standalone/dashboard-approval.js +66 -0
- package/dist/standalone/machine-link.d.ts +4 -221
- package/dist/standalone/machine-link.js +21 -15
- package/dist/standalone/media.d.ts +1 -0
- package/dist/standalone/media.js +20 -14
- package/dist/standalone/owner-routes.d.ts +16 -0
- package/dist/standalone/owner-routes.js +21 -0
- package/dist/standalone/permission-resources.d.ts +5 -0
- package/dist/standalone/permission-resources.js +31 -0
- package/dist/standalone/registry.d.ts +7 -0
- package/dist/standalone/registry.js +3 -0
- package/dist/standalone/tenancy.d.ts +0 -3
- package/dist/standalone/tenancy.js +2 -5
- package/dist/standalone/worker-routes.d.ts +19 -0
- package/dist/standalone/worker-routes.js +9 -0
- package/dist/standalone/worker.d.ts +1 -1
- package/dist/standalone/worker.js +27 -35
- package/dist/standalone/workflow-binding.d.ts +10 -3
- package/dist/standalone/workflow-binding.js +22 -5
- package/dist/standalone/workflow-git-capture.js +2 -1
- package/dist/standalone/workflow-host.d.ts +8 -103
- package/dist/standalone/workflow-host.js +1 -1
- package/dist/standalone/workflow-node-capture.js +2 -2
- package/dist/standalone/workflow-root-authority.d.ts +1 -1
- package/dist/standalone/workflow-root-authority.js +1 -1
- package/dist/standalone/workflow-schedules.d.ts +31 -4
- package/dist/standalone/workflow-schedules.js +18 -5
- package/dist/standalone/workflow-webhooks.d.ts +4 -18
- package/dist/standalone/workflow-webhooks.js +5 -5
- package/dist/standalone/workflows.d.ts +53 -27
- package/dist/standalone/workflows.js +9 -3
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.d.ts +7 -6
- package/dist/testing/capabilities.js +3 -6
- package/dist/testing/installation.d.ts +0 -1
- package/dist/testing/installation.js +2 -5
- package/dist/workers-git.d.ts +12 -4
- package/dist/workers-git.js +38 -35
- package/dist/workers.d.ts +9 -1
- package/dist/workers.js +44 -8
- package/dist/workflows.d.ts +4 -0
- package/dist/workflows.js +2 -0
- package/docs/api/README.md +7 -0
- package/docs/api/agent-schedules.md +176 -0
- package/docs/api/agents.md +34 -13
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +323 -225
- package/docs/api/auth.md +0 -55
- package/docs/api/authorization.md +1300 -0
- package/docs/api/build.md +42 -38
- package/docs/api/cloudflare.md +3 -3
- package/docs/api/git.md +20 -11
- package/docs/api/host.md +64 -192
- package/docs/api/index.md +1 -1
- package/docs/api/invocation.md +14 -14
- package/docs/api/keys.md +1 -98
- package/docs/api/mcp.md +43 -40
- package/docs/api/modules.md +2 -0
- package/docs/api/page/mount.md +16 -19
- package/docs/api/workers-git.md +46 -12
- package/docs/api/workers.md +111 -222
- package/package.json +41 -22
- package/dist/page/computer-target.d.ts +0 -3
- package/dist/page/computer-target.js +0 -9
- package/dist/standalone/channel-session-door.d.ts +0 -4
- package/dist/standalone/channel-session-door.js +0 -72
|
@@ -2,6 +2,8 @@ import { createMachineEnrollment, type MachineEnrollment, type MachineLinkDevice
|
|
|
2
2
|
import { type LiveOysterKey, type OysterKeyStore } from "../keys.js";
|
|
3
3
|
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
4
4
|
import { DurableObject } from "cloudflare:workers";
|
|
5
|
+
import type { BlankEnv } from "hono/types";
|
|
6
|
+
import { type Routes } from "../authorization.js";
|
|
5
7
|
import { type MachineLinkAuthority, type MachineLinkFenceResult } from "../machine-link/index.js";
|
|
6
8
|
import type { PostEnvelope, PostDeliveryResult } from "@fungi.computer/carrier/post";
|
|
7
9
|
import type { MachineAgentOperation } from "@fungi.computer/seam/machine-link-protocol";
|
|
@@ -40,228 +42,9 @@ export declare function standaloneMachineJoinRoutes(options: {
|
|
|
40
42
|
live(keyId: string): Promise<LiveOysterKey | null>;
|
|
41
43
|
};
|
|
42
44
|
enroll(keyId: string, input: MachineEnrollment): Promise<StandaloneMachineJoinResult>;
|
|
43
|
-
}):
|
|
44
|
-
"/local/mesh/join": {
|
|
45
|
-
$post: {
|
|
46
|
-
input: {
|
|
47
|
-
json: {
|
|
48
|
-
requestId: string;
|
|
49
|
-
publicKey: string;
|
|
50
|
-
machineLabel: string;
|
|
51
|
-
agents: {
|
|
52
|
-
name: string;
|
|
53
|
-
}[];
|
|
54
|
-
};
|
|
55
|
-
};
|
|
56
|
-
output: null;
|
|
57
|
-
outputFormat: "body";
|
|
58
|
-
status: 403;
|
|
59
|
-
} | {
|
|
60
|
-
input: {
|
|
61
|
-
json: {
|
|
62
|
-
requestId: string;
|
|
63
|
-
publicKey: string;
|
|
64
|
-
machineLabel: string;
|
|
65
|
-
agents: {
|
|
66
|
-
name: string;
|
|
67
|
-
}[];
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
output: {
|
|
71
|
-
error: string;
|
|
72
|
-
};
|
|
73
|
-
outputFormat: "json";
|
|
74
|
-
status: 400;
|
|
75
|
-
} | {
|
|
76
|
-
input: {
|
|
77
|
-
json: {
|
|
78
|
-
requestId: string;
|
|
79
|
-
publicKey: string;
|
|
80
|
-
machineLabel: string;
|
|
81
|
-
agents: {
|
|
82
|
-
name: string;
|
|
83
|
-
}[];
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
output: null;
|
|
87
|
-
outputFormat: "body";
|
|
88
|
-
status: 401;
|
|
89
|
-
} | {
|
|
90
|
-
input: {
|
|
91
|
-
json: {
|
|
92
|
-
requestId: string;
|
|
93
|
-
publicKey: string;
|
|
94
|
-
machineLabel: string;
|
|
95
|
-
agents: {
|
|
96
|
-
name: string;
|
|
97
|
-
}[];
|
|
98
|
-
};
|
|
99
|
-
};
|
|
100
|
-
output: {
|
|
101
|
-
error: "request_conflict" | "capacity";
|
|
102
|
-
};
|
|
103
|
-
outputFormat: "json";
|
|
104
|
-
status: 409;
|
|
105
|
-
} | {
|
|
106
|
-
input: {
|
|
107
|
-
json: {
|
|
108
|
-
requestId: string;
|
|
109
|
-
publicKey: string;
|
|
110
|
-
machineLabel: string;
|
|
111
|
-
agents: {
|
|
112
|
-
name: string;
|
|
113
|
-
}[];
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
output: {
|
|
117
|
-
error: string;
|
|
118
|
-
};
|
|
119
|
-
outputFormat: "json";
|
|
120
|
-
status: 400;
|
|
121
|
-
} | {
|
|
122
|
-
input: {
|
|
123
|
-
json: {
|
|
124
|
-
requestId: string;
|
|
125
|
-
publicKey: string;
|
|
126
|
-
machineLabel: string;
|
|
127
|
-
agents: {
|
|
128
|
-
name: string;
|
|
129
|
-
}[];
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
|
-
output: {
|
|
133
|
-
deviceId: string;
|
|
134
|
-
agents: {
|
|
135
|
-
selection: number;
|
|
136
|
-
agentId: string;
|
|
137
|
-
}[];
|
|
138
|
-
kind: string;
|
|
139
|
-
scopeId: string;
|
|
140
|
-
};
|
|
141
|
-
outputFormat: "json";
|
|
142
|
-
status: 200;
|
|
143
|
-
};
|
|
144
|
-
};
|
|
145
|
-
}, "/", "/local/mesh/join">;
|
|
45
|
+
}): Routes<BlankEnv>;
|
|
146
46
|
/** Only join accepts an oyk credential; every subsequent dial uses Seam. */
|
|
147
|
-
export declare function standaloneMachineLinkRoutes(env: StandaloneMachineLinkEnv):
|
|
148
|
-
"/local/mesh/join": {
|
|
149
|
-
$post: {
|
|
150
|
-
input: {
|
|
151
|
-
json: {
|
|
152
|
-
requestId: string;
|
|
153
|
-
publicKey: string;
|
|
154
|
-
machineLabel: string;
|
|
155
|
-
agents: {
|
|
156
|
-
name: string;
|
|
157
|
-
}[];
|
|
158
|
-
};
|
|
159
|
-
};
|
|
160
|
-
output: null;
|
|
161
|
-
outputFormat: "body";
|
|
162
|
-
status: 403;
|
|
163
|
-
} | {
|
|
164
|
-
input: {
|
|
165
|
-
json: {
|
|
166
|
-
requestId: string;
|
|
167
|
-
publicKey: string;
|
|
168
|
-
machineLabel: string;
|
|
169
|
-
agents: {
|
|
170
|
-
name: string;
|
|
171
|
-
}[];
|
|
172
|
-
};
|
|
173
|
-
};
|
|
174
|
-
output: {
|
|
175
|
-
error: string;
|
|
176
|
-
};
|
|
177
|
-
outputFormat: "json";
|
|
178
|
-
status: 400;
|
|
179
|
-
} | {
|
|
180
|
-
input: {
|
|
181
|
-
json: {
|
|
182
|
-
requestId: string;
|
|
183
|
-
publicKey: string;
|
|
184
|
-
machineLabel: string;
|
|
185
|
-
agents: {
|
|
186
|
-
name: string;
|
|
187
|
-
}[];
|
|
188
|
-
};
|
|
189
|
-
};
|
|
190
|
-
output: null;
|
|
191
|
-
outputFormat: "body";
|
|
192
|
-
status: 401;
|
|
193
|
-
} | {
|
|
194
|
-
input: {
|
|
195
|
-
json: {
|
|
196
|
-
requestId: string;
|
|
197
|
-
publicKey: string;
|
|
198
|
-
machineLabel: string;
|
|
199
|
-
agents: {
|
|
200
|
-
name: string;
|
|
201
|
-
}[];
|
|
202
|
-
};
|
|
203
|
-
};
|
|
204
|
-
output: {
|
|
205
|
-
error: "request_conflict" | "capacity";
|
|
206
|
-
};
|
|
207
|
-
outputFormat: "json";
|
|
208
|
-
status: 409;
|
|
209
|
-
} | {
|
|
210
|
-
input: {
|
|
211
|
-
json: {
|
|
212
|
-
requestId: string;
|
|
213
|
-
publicKey: string;
|
|
214
|
-
machineLabel: string;
|
|
215
|
-
agents: {
|
|
216
|
-
name: string;
|
|
217
|
-
}[];
|
|
218
|
-
};
|
|
219
|
-
};
|
|
220
|
-
output: {
|
|
221
|
-
error: string;
|
|
222
|
-
};
|
|
223
|
-
outputFormat: "json";
|
|
224
|
-
status: 400;
|
|
225
|
-
} | {
|
|
226
|
-
input: {
|
|
227
|
-
json: {
|
|
228
|
-
requestId: string;
|
|
229
|
-
publicKey: string;
|
|
230
|
-
machineLabel: string;
|
|
231
|
-
agents: {
|
|
232
|
-
name: string;
|
|
233
|
-
}[];
|
|
234
|
-
};
|
|
235
|
-
};
|
|
236
|
-
output: {
|
|
237
|
-
deviceId: string;
|
|
238
|
-
agents: {
|
|
239
|
-
selection: number;
|
|
240
|
-
agentId: string;
|
|
241
|
-
}[];
|
|
242
|
-
kind: string;
|
|
243
|
-
scopeId: string;
|
|
244
|
-
};
|
|
245
|
-
outputFormat: "json";
|
|
246
|
-
status: 200;
|
|
247
|
-
};
|
|
248
|
-
};
|
|
249
|
-
} & {
|
|
250
|
-
"/api/teams/:teamId/local/mesh/:deviceId/connect": {
|
|
251
|
-
$get: {
|
|
252
|
-
input: {
|
|
253
|
-
param: {
|
|
254
|
-
teamId: string;
|
|
255
|
-
} & {
|
|
256
|
-
deviceId: string;
|
|
257
|
-
};
|
|
258
|
-
};
|
|
259
|
-
output: {};
|
|
260
|
-
outputFormat: string;
|
|
261
|
-
status: import("hono/utils/http-status").StatusCode;
|
|
262
|
-
};
|
|
263
|
-
};
|
|
264
|
-
}, "/", "/api/teams/:teamId/local/mesh/:deviceId/connect">;
|
|
47
|
+
export declare function standaloneMachineLinkRoutes(env: StandaloneMachineLinkEnv): Routes<BlankEnv>;
|
|
265
48
|
/** One device's link; directory, credentials and owner state remain elsewhere. */
|
|
266
49
|
export declare class MachineLinkDO extends DurableObject<StandaloneMachineLinkEnv> {
|
|
267
50
|
private readonly link;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { machineAgentCodecs, machineAgentReservations, } from "@fungi.computer/shiitake/delegation";
|
|
2
2
|
import { createMachineEnrollment, MachineEnrollmentRefused, MachineLinkRefused, } from "../machine-link/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import {} from "../keys.js";
|
|
4
4
|
import { agentIds, createAgentDirectory, } from "@fungi.computer/shiitake/fleet";
|
|
5
5
|
import { DurableObject } from "cloudflare:workers";
|
|
6
|
-
import {
|
|
6
|
+
import { allowsMachineEnrollment, door, routes, } from "../authorization.js";
|
|
7
7
|
import { bodyLimit } from "hono/body-limit";
|
|
8
|
-
import { validator } from "hono/validator";
|
|
9
8
|
import { machineEnrollmentSchema, machineLinkName, } from "../machine-link/index.js";
|
|
10
9
|
import { createMachineLink } from "../machine-link/workers.js";
|
|
11
10
|
import { deliverUndeliverableMail } from "../machine-link/delivery.js";
|
|
@@ -123,12 +122,19 @@ export function standaloneMachineLinkAuthority(env) {
|
|
|
123
122
|
export function standaloneMachineJoinRoutes(options) {
|
|
124
123
|
const { scope } = options;
|
|
125
124
|
const teamId = scope.durableName("agents");
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
return routes().post("/local/mesh/join", door("an oyk key whose only grant is machine.enroll, verified here"), async (context) => {
|
|
126
|
+
let body;
|
|
127
|
+
try {
|
|
128
|
+
body = await context.req.json();
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (!(error instanceof SyntaxError))
|
|
132
|
+
throw error;
|
|
133
|
+
return context.json({ error: "invalid_enrollment" }, 400);
|
|
134
|
+
}
|
|
135
|
+
const enrollment = machineEnrollmentSchema.safeParse(body);
|
|
136
|
+
if (!enrollment.success)
|
|
137
|
+
return context.json({ error: "invalid_enrollment" }, 400);
|
|
132
138
|
if (new URL(context.req.url).origin !== scope.origin)
|
|
133
139
|
return context.body(null, 403);
|
|
134
140
|
const ownerId = await options.ownerId();
|
|
@@ -142,9 +148,9 @@ export function standaloneMachineJoinRoutes(options) {
|
|
|
142
148
|
if (verified.kind !== "ok")
|
|
143
149
|
return context.body(null, 401);
|
|
144
150
|
const live = await keys.live(verified.value.keyId);
|
|
145
|
-
if (live?.owner !== ownerId || !
|
|
151
|
+
if (live?.owner !== ownerId || !allowsMachineEnrollment(live))
|
|
146
152
|
return context.body(null, 403);
|
|
147
|
-
const joined = await options.enroll(live.keyId,
|
|
153
|
+
const joined = await options.enroll(live.keyId, enrollment.data);
|
|
148
154
|
// Domain refusals cross RPC as data, not an instanceof claim about a
|
|
149
155
|
// platform-reconstructed Error.
|
|
150
156
|
if (joined.kind === "refused") {
|
|
@@ -155,7 +161,7 @@ export function standaloneMachineJoinRoutes(options) {
|
|
|
155
161
|
return context.body(null, 403);
|
|
156
162
|
}
|
|
157
163
|
return context.json({ kind: "joined", scopeId: teamId, ...joined.value }, 200);
|
|
158
|
-
});
|
|
164
|
+
}, { middleware: [bodyLimit({ maxSize: 512 * 1024 })] });
|
|
159
165
|
}
|
|
160
166
|
/** Only join accepts an oyk credential; every subsequent dial uses Seam. */
|
|
161
167
|
export function standaloneMachineLinkRoutes(env) {
|
|
@@ -166,7 +172,7 @@ export function standaloneMachineLinkRoutes(env) {
|
|
|
166
172
|
ownerId: async () => (await liveOwner(env))?.actor.memberId ?? null,
|
|
167
173
|
keys: () => env.KEYS.getByName(scope.durableName("keys")),
|
|
168
174
|
enroll: (keyId, input) => env.AGENT_DIRECTORY.getByName(teamId).enrollMachine(keyId, input),
|
|
169
|
-
}).get("/api/teams/:teamId/local/mesh/:deviceId/connect", async (context) => {
|
|
175
|
+
}).get("/api/teams/:teamId/local/mesh/:deviceId/connect", door("a live enrolled device; its link verifies Seam on every dial"), async (context) => {
|
|
170
176
|
const id = {
|
|
171
177
|
teamId: context.req.param("teamId"),
|
|
172
178
|
deviceId: context.req.param("deviceId"),
|
|
@@ -366,7 +372,7 @@ export function createStandaloneMachineEnrollment(options) {
|
|
|
366
372
|
const key = await options.liveKey(keyId);
|
|
367
373
|
if (memberId === null ||
|
|
368
374
|
key?.owner !== memberId ||
|
|
369
|
-
!
|
|
375
|
+
!allowsMachineEnrollment(key))
|
|
370
376
|
throw new MachineLinkRefused("actor_not_allowed");
|
|
371
377
|
// This wrapper is per call: concurrent joins never share a mutable key
|
|
372
378
|
// slot. createMachineEnrollment invokes it only around its synchronous
|
|
@@ -398,7 +404,7 @@ export function createStandaloneMachineEnrollment(options) {
|
|
|
398
404
|
authorizeMember: async (member) => {
|
|
399
405
|
await configured.authorizeMember(member);
|
|
400
406
|
const current = await options.liveKey(keyId);
|
|
401
|
-
if (current?.owner !== member || !
|
|
407
|
+
if (current?.owner !== member || !allowsMachineEnrollment(current))
|
|
402
408
|
throw new MachineLinkRefused("actor_not_allowed");
|
|
403
409
|
},
|
|
404
410
|
}).enroll({ memberId, clientId: "oyster-key", loginId: key.keyId }, input);
|
|
@@ -41,6 +41,7 @@ export declare const mediaFrameMethods: {
|
|
|
41
41
|
aspectRatio?: "16:9" | "4:3" | "1:1" | "3:4" | "9:16" | "21:9" | "9:21" | undefined;
|
|
42
42
|
references?: {
|
|
43
43
|
path: string;
|
|
44
|
+
computerId?: string | undefined;
|
|
44
45
|
role?: "reference" | "first-frame" | "last-frame" | undefined;
|
|
45
46
|
}[] | undefined;
|
|
46
47
|
generateAudio?: boolean | undefined;
|
package/dist/standalone/media.js
CHANGED
|
@@ -2,30 +2,31 @@ import { z } from "zod";
|
|
|
2
2
|
import { MAX_REFERENCE_BYTES, MediaSettingsError, } from "@fungi.computer/darkroom";
|
|
3
3
|
import { AppInstanceError } from "../index.js";
|
|
4
4
|
import { readMediaFailure } from "@fungi.computer/darkroom/browser";
|
|
5
|
-
import { appMediaGenerationResultSchema } from "@fungi.computer/darkroom/app-media-contracts";
|
|
5
|
+
import { appMediaGenerationResultSchema, mediaModelsSchema, } from "@fungi.computer/darkroom/app-media-contracts";
|
|
6
6
|
import { mediaSlot } from "../media.js";
|
|
7
|
-
import {
|
|
7
|
+
import { computer, routes as routeTable } from "../authorization.js";
|
|
8
8
|
import * as Schema from "effect/Schema";
|
|
9
9
|
import { pendingSchema } from "@fungi.computer/agar/backend-definition";
|
|
10
10
|
import { mediaGenerationDescriptionSchema, mediaGenerationJsonRequestSchema, mediaGenerationReferenceSchema, } from "@fungi.computer/agar/media-json";
|
|
11
|
-
import { createMediaService, mediaPendingSchema, mediaResultSchema, mediaBindingCallSchema, mediaBindingRequest, parseMediaBindingCall, readMediaBindingReply, } from "@fungi.computer/darkroom/server";
|
|
11
|
+
import { createMediaService, estimateMediaModel, mediaPendingSchema, mediaResultSchema, mediaBindingCallSchema, mediaBindingRequest, parseMediaBindingCall, readMediaBindingReply, } from "@fungi.computer/darkroom/server";
|
|
12
12
|
import { createWorkersAiMediaProvider, createImagesReferenceFitter, } from "@fungi.computer/darkroom/cloudflare";
|
|
13
13
|
import { oysterPrincipalId, } from "../host.js";
|
|
14
14
|
import { normalizeFilePath } from "../files.js";
|
|
15
15
|
/** Media capability using Darkroom execution and the existing workspace FileSource. */
|
|
16
16
|
export function workersMedia(env, workspace) {
|
|
17
17
|
const { computerId } = workspace;
|
|
18
|
-
|
|
18
|
+
// Media reads and writes the Computer's workspace: a key needs `computer.use`.
|
|
19
|
+
const routes = routeTable();
|
|
19
20
|
const service = (principal) => standaloneMediaService(env, oysterPrincipalId(principal), workspace);
|
|
20
|
-
routes.get("/describe", async (context) => {
|
|
21
|
-
const response = await service(
|
|
21
|
+
routes.get("/describe", computer(), async (context, { principal }) => {
|
|
22
|
+
const response = await service(principal).request("http://media/models");
|
|
22
23
|
return context.json(Schema.decodeUnknownSync(mediaGenerationDescriptionSchema)({
|
|
23
24
|
computerId,
|
|
24
25
|
canGenerate: true,
|
|
25
26
|
models: await response.json(),
|
|
26
27
|
}));
|
|
27
28
|
});
|
|
28
|
-
routes.post("/generate", async (context) => {
|
|
29
|
+
routes.post("/generate", computer(), async (context, { principal }) => {
|
|
29
30
|
const input = Schema.decodeUnknownOption(Schema.fromJsonString(mediaGenerationJsonRequestSchema), { onExcessProperty: "error" })(await context.req.text());
|
|
30
31
|
if (input._tag === "None")
|
|
31
32
|
return context.json(invalidRequest());
|
|
@@ -33,13 +34,13 @@ export function workersMedia(env, workspace) {
|
|
|
33
34
|
if (path === null || path === "/")
|
|
34
35
|
return context.json(invalidRequest());
|
|
35
36
|
const references = input.value.settings.references?.map((reference) => ({
|
|
36
|
-
...reference,
|
|
37
37
|
computerId,
|
|
38
|
+
...reference,
|
|
38
39
|
path: normalizeFilePath(reference.path),
|
|
39
40
|
}));
|
|
40
41
|
if (references?.some((reference) => reference.path === null || reference.path === "/"))
|
|
41
42
|
return context.json(invalidRequest());
|
|
42
|
-
const response = await service(
|
|
43
|
+
const response = await service(principal).request("http://media/generate", {
|
|
43
44
|
method: "POST",
|
|
44
45
|
signal: context.req.raw.signal,
|
|
45
46
|
headers: {
|
|
@@ -88,7 +89,7 @@ export function workersMedia(env, workspace) {
|
|
|
88
89
|
},
|
|
89
90
|
});
|
|
90
91
|
});
|
|
91
|
-
routes.post("/cancel-attempt", async (context) => {
|
|
92
|
+
routes.post("/cancel-attempt", computer(), async (context) => {
|
|
92
93
|
const reference = Schema.decodeUnknownOption(Schema.fromJsonString(mediaGenerationReferenceSchema), { onExcessProperty: "error" })(await context.req.text());
|
|
93
94
|
if (reference._tag === "None" || reference.value.computerId !== computerId)
|
|
94
95
|
return context.json({ error: "Invalid workspace media reference" }, 400);
|
|
@@ -138,7 +139,7 @@ function standaloneMediaService(env, actorId, workspace, owner = "owner", librar
|
|
|
138
139
|
? destination
|
|
139
140
|
: {
|
|
140
141
|
...destination,
|
|
141
|
-
readReference: async (reference) => {
|
|
142
|
+
readReference: async (reference, model) => {
|
|
142
143
|
if (reference.computerId !== computerId)
|
|
143
144
|
throw new MediaSettingsError("reference Computer is not selected");
|
|
144
145
|
const bytes = await files.read(workspacePath(reference.path));
|
|
@@ -147,7 +148,7 @@ function standaloneMediaService(env, actorId, workspace, owner = "owner", librar
|
|
|
147
148
|
bytes.byteLength > MAX_REFERENCE_BYTES)
|
|
148
149
|
throw new MediaSettingsError("reference image is unavailable or exceeds the byte limit");
|
|
149
150
|
return {
|
|
150
|
-
...(await fit(bytes)),
|
|
151
|
+
...(await fit(bytes, model)),
|
|
151
152
|
filename: reference.path.split("/").at(-1) ?? "reference",
|
|
152
153
|
};
|
|
153
154
|
},
|
|
@@ -201,8 +202,13 @@ export async function dispatchMediaBinding(env, frame, props, method, args, work
|
|
|
201
202
|
const input = parsed.call;
|
|
202
203
|
const service = standaloneMediaService(env, actorId, workspace, frame.facts.selection.owner.id, input.operation === "generate" ? input.input.libraryPath : undefined);
|
|
203
204
|
let response;
|
|
204
|
-
if (input.operation === "models")
|
|
205
|
-
|
|
205
|
+
if (input.operation === "models") {
|
|
206
|
+
const catalog = await service.request("/models");
|
|
207
|
+
response = Response.json(mediaModelsSchema.parse(await catalog.json()).map((model) => ({
|
|
208
|
+
...model,
|
|
209
|
+
priceEstimate: estimateMediaModel(model),
|
|
210
|
+
})));
|
|
211
|
+
}
|
|
206
212
|
else {
|
|
207
213
|
const body = mediaBindingRequest(input.input, workspace.computerId);
|
|
208
214
|
const generated = await service.request("/generate", {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The standalone root's owner routes: App source seeding, builds, the Computer
|
|
3
|
+
* fleet, Agent Computer attachment and Workflow webhook controls. The Worker
|
|
4
|
+
* and the authorization law both compose them from this one function.
|
|
5
|
+
*/
|
|
6
|
+
import type { OysterEnv, OysterHost } from "../host.js";
|
|
7
|
+
import type { Routes } from "../authorization.js";
|
|
8
|
+
import type { TenantScope } from "../tenant.js";
|
|
9
|
+
import type { StandaloneAgentsEnv } from "./agents.js";
|
|
10
|
+
import { type AppSourceBindingEnv } from "./app-source.js";
|
|
11
|
+
import type { StandaloneComputerEnv } from "./computer.js";
|
|
12
|
+
import type { StandaloneWorkflowEnv } from "./workflows.js";
|
|
13
|
+
/** The bindings the standalone owner routes reach, resolved per request. */
|
|
14
|
+
export type StandaloneOwnerRoutesEnv = AppSourceBindingEnv & Pick<StandaloneComputerEnv, "REGISTRY"> & Pick<StandaloneAgentsEnv, "AGENT_DIRECTORY"> & Pick<StandaloneWorkflowEnv, "WORKFLOW_RUNS">;
|
|
15
|
+
/** Every owner route the standalone root adds to its tenant host. */
|
|
16
|
+
export declare function standaloneOwnerRoutes(env: StandaloneOwnerRoutesEnv, scope: TenantScope, hostFor: () => Promise<Pick<OysterHost, "installations">>): Routes<OysterEnv>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { agentDirectoryForScope } from "./agent-directory.js";
|
|
2
|
+
import { standaloneAgentComputerRoutes } from "./agent-computers.js";
|
|
3
|
+
import { appSourceOwnerRoutes, } from "./app-source.js";
|
|
4
|
+
import { standaloneBuildRoutes } from "./builds.js";
|
|
5
|
+
import { standaloneComputerFleetRoutes } from "./computer-fleet.js";
|
|
6
|
+
import { standaloneWorkflowWebhookControls } from "./workflow-webhooks.js";
|
|
7
|
+
/** Every owner route the standalone root adds to its tenant host. */
|
|
8
|
+
export function standaloneOwnerRoutes(env, scope, hostFor) {
|
|
9
|
+
// A tenant's registry owns its installations, builds and Computer fleet.
|
|
10
|
+
const registry = () => env.REGISTRY.getByName(scope.durableName("registry"));
|
|
11
|
+
const directory = () => agentDirectoryForScope(env, scope);
|
|
12
|
+
return appSourceOwnerRoutes(env, scope)
|
|
13
|
+
.include(standaloneBuildRoutes(registry))
|
|
14
|
+
.include(standaloneComputerFleetRoutes(registry))
|
|
15
|
+
.include(standaloneAgentComputerRoutes({
|
|
16
|
+
attach: (principal, command) => directory().attachComputer(command, principal, scope.tenantId),
|
|
17
|
+
detach: (principal, command) => directory().detachComputer(command, principal, scope.tenantId),
|
|
18
|
+
read: (principal, agentId) => directory().readAgentComputers(agentId, principal, scope.tenantId),
|
|
19
|
+
}))
|
|
20
|
+
.include(standaloneWorkflowWebhookControls(env, hostFor));
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { OysterHost } from "../host.js";
|
|
2
|
+
import type { OysterAgents } from "../agents.js";
|
|
3
|
+
import type { StandaloneRegistryRpc } from "./registry.js";
|
|
4
|
+
/** The existing owner picker includes its configured Computer as well as the fleet. */
|
|
5
|
+
export declare function standalonePermissionResources(registry: Pick<StandaloneRegistryRpc, "computerFleet">, agents: Pick<OysterAgents, "list">): NonNullable<OysterHost["permissionResources"]>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HTTPException } from "hono/http-exception";
|
|
2
|
+
import { workbenchComputerDirectorySchema } from "@fungi.computer/nacre/workbench-directory";
|
|
3
|
+
import { oysterPrincipalIsOwner } from "../authorization.js";
|
|
4
|
+
/** The existing owner picker includes its configured Computer as well as the fleet. */
|
|
5
|
+
export function standalonePermissionResources(registry, agents) {
|
|
6
|
+
return async (principal) => {
|
|
7
|
+
if (!oysterPrincipalIsOwner(principal))
|
|
8
|
+
throw new HTTPException(403, { message: "Owner only" });
|
|
9
|
+
const resources = (await agents.list()).map((agent) => ({
|
|
10
|
+
kind: "agent",
|
|
11
|
+
id: agent.agentId,
|
|
12
|
+
label: agent.name,
|
|
13
|
+
}));
|
|
14
|
+
// Follow the native picker cursor, as inventory.ids() does for inventory.
|
|
15
|
+
// A transport failure refuses the read; it is never an empty directory.
|
|
16
|
+
let offset = 0;
|
|
17
|
+
while (offset !== null) {
|
|
18
|
+
const page = workbenchComputerDirectorySchema.parse(await registry.computerFleet(principal.actor.memberId, {
|
|
19
|
+
operation: "choices",
|
|
20
|
+
page: { offset, limit: 100 },
|
|
21
|
+
}));
|
|
22
|
+
resources.push(...page.computers.map((computer) => ({
|
|
23
|
+
kind: "computer",
|
|
24
|
+
id: computer.computerId,
|
|
25
|
+
label: computer.name,
|
|
26
|
+
})));
|
|
27
|
+
offset = page.nextOffset;
|
|
28
|
+
}
|
|
29
|
+
return resources;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -179,6 +179,13 @@ export declare abstract class StandaloneRegistry extends OysterRegistry implemen
|
|
|
179
179
|
appId: import("@fungi.computer/puffball").AppId;
|
|
180
180
|
} | null>;
|
|
181
181
|
readBuild(jobId: string): Promise<OysterBuildView | null>;
|
|
182
|
+
readBuildLog(jobId: string): Promise<{
|
|
183
|
+
readonly encoding: "base64url";
|
|
184
|
+
readonly output: string;
|
|
185
|
+
readonly truncated: {
|
|
186
|
+
readonly droppedBytes: number;
|
|
187
|
+
} | null;
|
|
188
|
+
} | null>;
|
|
182
189
|
/** A job ID alone never grants another installation access to its status. */
|
|
183
190
|
readInstallationBuild(installationId: string, jobId: string): Promise<OysterBuildView | null>;
|
|
184
191
|
/** Repository status belongs to the same captured Git owner as registration. */
|
|
@@ -372,6 +372,9 @@ export class StandaloneRegistry extends OysterRegistry {
|
|
|
372
372
|
async readBuild(jobId) {
|
|
373
373
|
return (await this.#appBuilds()).read(jobId);
|
|
374
374
|
}
|
|
375
|
+
async readBuildLog(jobId) {
|
|
376
|
+
return (await this.#appBuilds()).readLog(jobId);
|
|
377
|
+
}
|
|
375
378
|
/** A job ID alone never grants another installation access to its status. */
|
|
376
379
|
async readInstallationBuild(installationId, jobId) {
|
|
377
380
|
const builds = await this.#appBuilds();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { TenancyRegistryDO } from "../tenancy/cloudflare.js";
|
|
2
2
|
import { type Identity, type Tenant, type ReadonlyTenancy } from "../tenancy/index.js";
|
|
3
|
-
import { type OysterKeySessionResolver } from "../auth.js";
|
|
4
3
|
import type { OysterGitWorkerEnv } from "../workers-git.js";
|
|
5
4
|
import { type OysterTenancy, type TenantId } from "../tenant.js";
|
|
6
5
|
/** Trusted standalone provisioning inputs; credentials never enter the seed. */
|
|
@@ -29,8 +28,6 @@ export declare class StandaloneTenants extends TenancyRegistryDO {
|
|
|
29
28
|
/** The same trusted bindings are used by HTTP, MCP and cold root readers. */
|
|
30
29
|
export type StandaloneTenancyEnv = Pick<OysterGitWorkerEnv, "OYSTER_ORIGIN" | "OYSTER_OWNER_NAME" | "OYSTER_OWNER_KEY" | "KEYS"> & Readonly<{
|
|
31
30
|
TENANTS: DurableObjectNamespace<StandaloneTenants>;
|
|
32
|
-
/** Optional host-owned named-key browser sessions; Oyster mints none. */
|
|
33
|
-
OYSTER_KEY_SESSIONS?: OysterKeySessionResolver;
|
|
34
31
|
}>;
|
|
35
32
|
/** A live registry-owned owner; the configured label is never its member ID. */
|
|
36
33
|
export type StandaloneOwner = Readonly<{
|
|
@@ -2,7 +2,7 @@ import { TenancyRegistryDO, admittedRegistry, durableObjectRegistry, } from "../
|
|
|
2
2
|
import { createTenancy, } from "../tenancy/index.js";
|
|
3
3
|
import { ownerKey } from "../tenancy/owner-key.js";
|
|
4
4
|
import { hosts } from "../tenancy/resolve.js";
|
|
5
|
-
import {
|
|
5
|
+
import { ownerOrKeyAuth } from "../auth.js";
|
|
6
6
|
import { HTTPException } from "hono/http-exception";
|
|
7
7
|
import { defaultTenant, tenantScope, } from "../tenant.js";
|
|
8
8
|
import * as Schema from "effect/Schema";
|
|
@@ -174,10 +174,7 @@ async function composeTenancy(env) {
|
|
|
174
174
|
if (owner === null)
|
|
175
175
|
return { kind: "refused", status: 403 };
|
|
176
176
|
const keys = () => env.KEYS.getByName(scope.durableName("keys"));
|
|
177
|
-
const
|
|
178
|
-
const auth = env.OYSTER_KEY_SESSIONS === undefined
|
|
179
|
-
? namedKeyAuth
|
|
180
|
-
: keySessionAuth(namedKeyAuth, owner.actor.memberId, keys, env.OYSTER_KEY_SESSIONS);
|
|
177
|
+
const auth = ownerOrKeyAuth(env.OYSTER_OWNER_KEY, owner.actor.memberId, keys);
|
|
181
178
|
const admission = await auth.authenticate(request);
|
|
182
179
|
// This entry admits only named keys; the owner key has its own entry.
|
|
183
180
|
return admission.kind === "admitted" &&
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The standalone Worker's top-level table, before any tenant is selected:
|
|
3
|
+
* device enrollment and links, signed Workflow webhook delivery, then the
|
|
4
|
+
* selected tenant's own declared host table for every other path. The Worker
|
|
5
|
+
* and the authorization law both compose it here.
|
|
6
|
+
*/
|
|
7
|
+
import type { BlankEnv } from "hono/types";
|
|
8
|
+
import { type Routes } from "../authorization.js";
|
|
9
|
+
/** What the Worker-top doors forward to. */
|
|
10
|
+
export type StandaloneWorkerRoutePorts = Readonly<{
|
|
11
|
+
/** The machine-link doors' own table, served for this request. */
|
|
12
|
+
machineLink: (request: Request) => Promise<Response>;
|
|
13
|
+
/** Signed Workflow webhook delivery, verified by the library's run owner. */
|
|
14
|
+
workflowWebhooks: Routes<BlankEnv>;
|
|
15
|
+
/** The selected tenant's declared host table. */
|
|
16
|
+
tenant: (request: Request) => Promise<Response>;
|
|
17
|
+
}>;
|
|
18
|
+
/** The Worker-top table; `serveDoors` serves it, so no route admits a principal. */
|
|
19
|
+
export declare function standaloneWorkerRoutes(ports: StandaloneWorkerRoutePorts): Routes<BlankEnv>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { door, routes } from "../authorization.js";
|
|
2
|
+
/** The Worker-top table; `serveDoors` serves it, so no route admits a principal. */
|
|
3
|
+
export function standaloneWorkerRoutes(ports) {
|
|
4
|
+
return routes()
|
|
5
|
+
.all("/local/mesh/join", door("machine enrollment; the join route verifies its key"), (context) => ports.machineLink(context.req.raw))
|
|
6
|
+
.all("/api/teams/:teamId/local/mesh/:deviceId/connect", door("a machine link; the device's link verifies Seam"), (context) => ports.machineLink(context.req.raw))
|
|
7
|
+
.include(ports.workflowWebhooks)
|
|
8
|
+
.all("*", door("the selected tenant's own declared host table"), (context) => ports.tenant(context.req.raw));
|
|
9
|
+
}
|
|
@@ -17,7 +17,7 @@ export { StandaloneTenants } from "./tenancy.js";
|
|
|
17
17
|
import { type StandaloneMachineLinkBindings } from "./machine-link.js";
|
|
18
18
|
export { MachineLinkDO } from "./machine-link.js";
|
|
19
19
|
import { type OysterComputerEgressProps } from "./computer-egress.js";
|
|
20
|
-
import {
|
|
20
|
+
import type { AppSourceBindingEnv } from "./app-source.js";
|
|
21
21
|
export { StandaloneGraphWorkflow } from "./workflows.js";
|
|
22
22
|
export { MediaOperationDO } from "@fungi.computer/darkroom/cloudflare";
|
|
23
23
|
/** Every binding the standalone Bonsai root receives. */
|