@fungi.computer/oyster 0.5.0 → 0.7.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 +155 -63
- package/dist/agent-archive-projection.d.ts +8 -0
- package/dist/agent-archive-projection.js +49 -0
- package/dist/agent-schedules.d.ts +50 -0
- package/dist/agent-schedules.js +17 -0
- package/dist/agents.d.ts +35 -14
- package/dist/agents.js +97 -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.js +9 -1
- 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 +67 -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 +2 -0
- package/dist/build/local-build.js +11 -9
- package/dist/channels/binding.d.ts +8 -1
- package/dist/channels/binding.js +12 -0
- package/dist/channels/conversations.d.ts +16 -5
- package/dist/channels/visitor-channel.d.ts +53 -0
- package/dist/channels/visitor-channel.js +124 -0
- package/dist/channels/visitor-edge.d.ts +115 -0
- package/dist/channels/visitor-edge.js +582 -0
- package/dist/channels/visitor-request.d.ts +7 -0
- package/dist/channels/visitor-request.js +17 -0
- package/dist/channels/visitor-stream-slots.d.ts +2 -0
- package/dist/channels/visitor-stream-slots.js +33 -0
- package/dist/channels/visitors.d.ts +47 -0
- package/dist/channels/visitors.js +181 -0
- package/dist/cloudflare.d.ts +11 -2
- package/dist/cloudflare.js +27 -1
- package/dist/{page/computer-target.d.ts → computer-target.d.ts} +2 -0
- package/dist/{page/computer-target.js → computer-target.js} +3 -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 +38 -42
- package/dist/host.js +182 -239
- package/dist/installation-execution.js +10 -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 +9 -6
- package/dist/internal/host-gate.js +19 -25
- 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 +19 -11
- package/dist/invocation.js +171 -96
- 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/capabilities.js +1 -1
- package/dist/page/child-selection.d.ts +27 -0
- package/dist/page/child-selection.js +38 -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 +8 -94
- package/dist/page-script.d.ts +0 -3
- package/dist/page-script.js +3 -4
- package/dist/release.d.ts +13 -0
- package/dist/release.js +10 -0
- package/dist/request-origin.d.ts +4 -0
- package/dist/request-origin.js +10 -0
- 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 +157 -20
- package/dist/standalone/agents.js +242 -38
- 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 +20 -43
- 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.js +3 -1
- 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 +8 -3
- package/dist/standalone/workflow-binding.js +14 -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 +14 -5
- package/dist/standalone/workflow-schedules.js +11 -5
- package/dist/standalone/workflow-webhooks.d.ts +4 -18
- package/dist/standalone/workflow-webhooks.js +5 -5
- package/dist/standalone/workflows.d.ts +36 -28
- package/dist/standalone/workflows.js +6 -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/turnstile.d.ts +27 -0
- package/dist/turnstile.js +51 -0
- package/dist/workers-git.d.ts +12 -4
- package/dist/workers-git.js +58 -36
- package/dist/workers.d.ts +9 -1
- package/dist/workers.js +59 -20
- package/dist/workflows.d.ts +2 -0
- package/dist/workflows.js +1 -0
- package/docs/api/README.md +7 -0
- package/docs/api/agent-schedules.md +175 -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/authorization.md +1300 -0
- package/docs/api/build-contract.md +2 -2
- package/docs/api/build.md +40 -37
- package/docs/api/cloudflare.md +27 -2
- package/docs/api/git.md +20 -11
- package/docs/api/host.md +80 -199
- package/docs/api/index.md +1 -1
- package/docs/api/invocation.md +15 -11
- 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 +125 -235
- package/docs/site/agent-quickstart.mdx +6 -2
- package/examples/standalone/wrangler.celld.jsonc +66 -0
- package/package.json +64 -21
- package/dist/standalone/channel-session-door.d.ts +0 -4
- package/dist/standalone/channel-session-door.js +0 -42
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { normalizeModel, streamModel } from "@fungi.computer/mule/effect";
|
|
2
|
-
import { observeProviderStatus } from "@fungi.computer/mule/effect";
|
|
1
|
+
import { isRetryableProviderRejection, normalizeModel, observeProviderStatus, streamModel, } from "@fungi.computer/mule/effect";
|
|
3
2
|
import { standaloneTenancy, } from "./tenancy.js";
|
|
4
3
|
import { AcceptedSchema } from "@fungi.computer/shiitake/client";
|
|
5
4
|
import { createShiitakeClient } from "@fungi.computer/shiitake/client";
|
|
6
|
-
import {
|
|
5
|
+
import { ShiitakeError } from "@fungi.computer/shiitake";
|
|
6
|
+
import { AgentArchiveRefused, AgentCreateRefused, } from "@fungi.computer/shiitake/agent-control";
|
|
7
7
|
import { AgentDelegation, AgentDisclosureRefused, machineAgentCodecs, } from "@fungi.computer/shiitake/delegation";
|
|
8
8
|
import { FungiMessageMetaSchema } from "@fungi.computer/agar/agents";
|
|
9
9
|
import { agentMail } from "./agent-mail.js";
|
|
10
|
-
import { createChannelConversations, } from "../channels/conversations.js";
|
|
11
|
-
import { channelSessionDoor } from "./channel-session-door.js";
|
|
12
10
|
import { createStandaloneMachineEnrollment, standaloneMachineAgentHome, standaloneMachineLinkAuthority, } from "./machine-link.js";
|
|
13
11
|
import { createMachineLinkFences, MachineEnrollmentRefused, MachineLinkRefused, createMachineAgentClient, } from "../machine-link/index.js";
|
|
14
12
|
import { memberInboxItemSchema } from "@fungi.computer/puffball/inbox";
|
|
@@ -29,6 +27,8 @@ import { homeContract } from "@fungi.computer/shiitake-extensions/home";
|
|
|
29
27
|
import { DurableObject } from "cloudflare:workers";
|
|
30
28
|
import { ShiitakeDO } from "@fungi.computer/shiitake/cloudflare";
|
|
31
29
|
import { agentStoreKeeping } from "./agent-store.js";
|
|
30
|
+
import { agentArchiveOwner } from "./agent-archive-owner.js";
|
|
31
|
+
import { agentArchiveProjection } from "../agent-archive-projection.js";
|
|
32
32
|
import { standaloneBrowserSource } from "./browser.js";
|
|
33
33
|
import { configuredProviderEgress, providerFetch, } from "@fungi.computer/veil/gatekeeper";
|
|
34
34
|
import { createAssistantMessageEventStream } from "@earendil-works/pi-ai/utils/event-stream";
|
|
@@ -39,13 +39,17 @@ import * as Schema from "effect/Schema";
|
|
|
39
39
|
import { z } from "zod";
|
|
40
40
|
import {} from "../cloudflare.js";
|
|
41
41
|
import { AppInstanceError } from "../index.js";
|
|
42
|
+
import { agentScheduleBindingArgs, appAgentScheduleListInputSchema, agentSchedulePageSchema, agentScheduleControlReceiptSchema, } from "../agent-schedules.js";
|
|
43
|
+
import { readAgentSchedulePage, controlAgentSchedule, agentScheduleTargetSchema, } from "@fungi.computer/shiitake-extensions/schedules";
|
|
42
44
|
import { createAgentAvatarStore, } from "../agent-avatars.js";
|
|
43
45
|
import { defineIdFamily } from "@fungi.computer/cairn";
|
|
44
46
|
import { agentIds, createAgentDirectory, createFleetSessions, commandSettlementSchema, DirectoryError, } from "@fungi.computer/shiitake/fleet";
|
|
45
47
|
import { appToolPolicySchema } from "../apps/index.js";
|
|
46
|
-
import {
|
|
48
|
+
import { oysterPrincipalId } from "../host.js";
|
|
49
|
+
import { oysterPrincipalIsOwner } from "../authorization.js";
|
|
47
50
|
import { defaultTenant, tenantIdSchema, tenantScope, } from "../tenant.js";
|
|
48
51
|
import { createCodemodeSandbox } from "@fungi.computer/mycelium/workers";
|
|
52
|
+
import { forageDiscoveryInstructions } from "@fungi.computer/mycelium/effect";
|
|
49
53
|
import { maxAgentMcpServers, } from "../agents.js";
|
|
50
54
|
/** Own transport evidence and exact-message decisions for one model lease. */
|
|
51
55
|
function observedModels(model, defaults) {
|
|
@@ -53,10 +57,10 @@ function observedModels(model, defaults) {
|
|
|
53
57
|
acquire: ({ signal }) => {
|
|
54
58
|
const retryableMessages = new WeakSet();
|
|
55
59
|
const safeFailures = new WeakMap();
|
|
56
|
-
const failure = (message,
|
|
60
|
+
const failure = (message, rejection) => {
|
|
57
61
|
// Pi can include raw response bodies in errorMessage. No provider
|
|
58
62
|
// prose enters a Session, retry journal, model context or diagnostics.
|
|
59
|
-
const safe = `Model request failed (${code}). Retry the request.`;
|
|
63
|
+
const safe = `Model request failed (${rejection?.code ?? "provider_rejected"}). Retry the request.`;
|
|
60
64
|
const terminal = {
|
|
61
65
|
role: "assistant",
|
|
62
66
|
content: [],
|
|
@@ -69,7 +73,7 @@ function observedModels(model, defaults) {
|
|
|
69
73
|
timestamp: message.timestamp,
|
|
70
74
|
};
|
|
71
75
|
safeFailures.set(terminal, safe);
|
|
72
|
-
if (
|
|
76
|
+
if (isRetryableProviderRejection(rejection))
|
|
73
77
|
retryableMessages.add(terminal);
|
|
74
78
|
return terminal;
|
|
75
79
|
};
|
|
@@ -92,7 +96,7 @@ function observedModels(model, defaults) {
|
|
|
92
96
|
});
|
|
93
97
|
for await (const event of response) {
|
|
94
98
|
if (event.type === "error") {
|
|
95
|
-
const terminal = failure(event.error, observed.rejection()
|
|
99
|
+
const terminal = failure(event.error, observed.rejection(event.error));
|
|
96
100
|
events.push({ ...event, error: terminal });
|
|
97
101
|
events.end(terminal);
|
|
98
102
|
return;
|
|
@@ -100,7 +104,7 @@ function observedModels(model, defaults) {
|
|
|
100
104
|
if (event.type === "done" &&
|
|
101
105
|
(event.message.stopReason === "error" ||
|
|
102
106
|
event.message.stopReason === "aborted")) {
|
|
103
|
-
const terminal = failure(event.message, observed.rejection()
|
|
107
|
+
const terminal = failure(event.message, observed.rejection(event.message));
|
|
104
108
|
events.push({ ...event, message: terminal });
|
|
105
109
|
events.end(terminal);
|
|
106
110
|
return;
|
|
@@ -109,7 +113,7 @@ function observedModels(model, defaults) {
|
|
|
109
113
|
}
|
|
110
114
|
const result = await response.result();
|
|
111
115
|
events.end(result.stopReason === "error" || result.stopReason === "aborted"
|
|
112
|
-
? failure(result, observed.rejection()
|
|
116
|
+
? failure(result, observed.rejection(result))
|
|
113
117
|
: result);
|
|
114
118
|
}
|
|
115
119
|
catch {
|
|
@@ -136,7 +140,7 @@ function observedModels(model, defaults) {
|
|
|
136
140
|
},
|
|
137
141
|
stopReason: signal.aborted ? "aborted" : "error",
|
|
138
142
|
timestamp: Date.now(),
|
|
139
|
-
}, "provider_unavailable");
|
|
143
|
+
}, { code: "provider_unavailable" });
|
|
140
144
|
events.push({
|
|
141
145
|
type: "error",
|
|
142
146
|
reason: signal.aborted ? "aborted" : "error",
|
|
@@ -220,6 +224,7 @@ const toMcpServer = (stored) => ({
|
|
|
220
224
|
hasBearer: stored.bearer !== null,
|
|
221
225
|
});
|
|
222
226
|
const instructions = [
|
|
227
|
+
forageDiscoveryInstructions,
|
|
223
228
|
"Attached Computers are independent env.computers.<name> modules, including their Files and execute operations; no App is needed. Discover their exact operations before calling.",
|
|
224
229
|
"You are an agent on a personal Oyster: a small, self-hosted home for Apps.",
|
|
225
230
|
"Your tool is execute: it runs JavaScript in an isolated sandbox. Each installed App is an env.<APP> module; dotted method names are nested properties, e.g. `return await env.BONSAI.repos.list({})`.",
|
|
@@ -230,21 +235,29 @@ const instructions = [
|
|
|
230
235
|
].join("\n");
|
|
231
236
|
/** One agent: Shiitake's Cloudflare host over this Durable Object; its root supplies `hostFor`. */
|
|
232
237
|
export class StandaloneAgentBase extends ShiitakeDO {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
238
|
+
archiveControlOwner;
|
|
239
|
+
archiveControl() {
|
|
240
|
+
return (this.archiveControlOwner ??= agentArchiveOwner({
|
|
241
|
+
storage: this.ctx.storage,
|
|
242
|
+
agentId: () => this.ctx.id.name,
|
|
243
|
+
agent: () => this.shiitake(),
|
|
244
|
+
scheduledExtensions: true,
|
|
245
|
+
refreshOutstanding: (agentId) => this.delegation().refreshOutstanding((target, sessionId, commandId) => this.machineClient().then((client) => client.settlement(target, sessionId, commandId, agentId))),
|
|
246
|
+
record: async (agentId, archived, revision) => {
|
|
247
|
+
const pin = await this.policyDirectory();
|
|
248
|
+
if (pin === null)
|
|
249
|
+
return false;
|
|
250
|
+
return this.env.AGENT_DIRECTORY.getByName(pin.directoryName).recordArchive(agentId, archived, revision);
|
|
251
|
+
},
|
|
252
|
+
wake: () => this.wakeRuntime(),
|
|
253
|
+
waitUntil: (task) => {
|
|
254
|
+
this.ctx.waitUntil(task);
|
|
255
|
+
},
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
/** Owner-local atomic transition. The caller checks human/creator authority. */
|
|
259
|
+
setArchived(archived) {
|
|
260
|
+
return this.archiveControl().setArchived(archived);
|
|
248
261
|
}
|
|
249
262
|
/** Each installed Release's verified catalog surface, built once per Release for this Agent's lifetime. */
|
|
250
263
|
#appSurfaces;
|
|
@@ -389,17 +402,40 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
389
402
|
}
|
|
390
403
|
}
|
|
391
404
|
controlDirectory() {
|
|
392
|
-
const list = async (signal) => {
|
|
405
|
+
const list = async (signal, options) => {
|
|
393
406
|
signal.throwIfAborted();
|
|
394
407
|
const pin = await this.policyDirectory();
|
|
395
408
|
if (pin === null)
|
|
396
409
|
throw new Error("Agent directory is unavailable");
|
|
397
|
-
const
|
|
398
|
-
|
|
410
|
+
const directory = this.env.AGENT_DIRECTORY.getByName(pin.directoryName);
|
|
411
|
+
const agents = await directory.list(options);
|
|
412
|
+
if (this.ctx.id.name === undefined ||
|
|
413
|
+
!(await directory.has(this.ctx.id.name)))
|
|
399
414
|
throw new Error("Agent is outside this scope");
|
|
400
415
|
signal.throwIfAborted();
|
|
401
416
|
return agents;
|
|
402
417
|
};
|
|
418
|
+
const changeArchive = async (targetId, archived, signal) => {
|
|
419
|
+
await list(signal);
|
|
420
|
+
const caller = this.ctx.id.name;
|
|
421
|
+
const pin = await this.policyDirectory();
|
|
422
|
+
if (caller === undefined || pin === null)
|
|
423
|
+
throw new AgentArchiveRefused("unreadable", "Agent scope is unavailable");
|
|
424
|
+
if (targetId === caller)
|
|
425
|
+
throw new AgentArchiveRefused("self", "An Agent cannot archive or restore itself");
|
|
426
|
+
const directory = this.env.AGENT_DIRECTORY.getByName(pin.directoryName);
|
|
427
|
+
if ((await directory.createdBy(targetId)) !== caller)
|
|
428
|
+
throw new AgentArchiveRefused("not_creator", "An Agent can change only one it created");
|
|
429
|
+
const target = this.env.AGENTS.getByName(targetId);
|
|
430
|
+
const targetPin = await target.policyDirectory();
|
|
431
|
+
if (targetPin?.directoryName !== pin.directoryName ||
|
|
432
|
+
targetPin.tenantId !== pin.tenantId)
|
|
433
|
+
throw new AgentArchiveRefused("unreadable", "Agent archive scope does not match");
|
|
434
|
+
signal.throwIfAborted();
|
|
435
|
+
const result = await target.setArchived(archived);
|
|
436
|
+
if ("error" in result)
|
|
437
|
+
throw new AgentArchiveRefused(result.error, result.message ?? `Agent archive refused: ${result.error}`);
|
|
438
|
+
};
|
|
403
439
|
return {
|
|
404
440
|
list,
|
|
405
441
|
create: async (input, signal) => {
|
|
@@ -420,10 +456,17 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
420
456
|
},
|
|
421
457
|
send: (input, sessionId, signal, invocation) => this.delegation().send(input, sessionId, signal, invocation),
|
|
422
458
|
recall: (input, sessionId, signal) => this.delegation().recall(input, sessionId, signal),
|
|
459
|
+
archive: (input, signal) => changeArchive(input.agentId, true, signal),
|
|
460
|
+
unarchive: (input, signal) => changeArchive(input.agentId, false, signal),
|
|
423
461
|
open: async (agentId, signal) => {
|
|
424
462
|
const admit = async () => {
|
|
425
|
-
if (!(await list(signal)).some((agent) => agent.agentId === agentId))
|
|
463
|
+
if (!(await list(signal)).some((agent) => agent.agentId === agentId)) {
|
|
464
|
+
const pin = await this.policyDirectory();
|
|
465
|
+
if (pin !== null &&
|
|
466
|
+
(await this.env.AGENT_DIRECTORY.getByName(pin.directoryName).archived(agentId)))
|
|
467
|
+
throw new ShiitakeError("contract", "archived", "Agent is archived");
|
|
426
468
|
throw new Error("Agent is outside this scope");
|
|
469
|
+
}
|
|
427
470
|
};
|
|
428
471
|
await admit();
|
|
429
472
|
const agent = createShiitakeClient({
|
|
@@ -454,7 +497,19 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
454
497
|
? null
|
|
455
498
|
: { scopeId: pin.directoryName, agentId };
|
|
456
499
|
},
|
|
457
|
-
|
|
500
|
+
// Delegation custody and settlement still see archived identities;
|
|
501
|
+
// Agent-control discovery keeps its active-only default. New sends
|
|
502
|
+
// reach the recipient's admission door and get contract/archived.
|
|
503
|
+
directory: () => {
|
|
504
|
+
const control = this.controlDirectory();
|
|
505
|
+
return {
|
|
506
|
+
...control,
|
|
507
|
+
list: async (signal) => [
|
|
508
|
+
...(await control.list(signal)),
|
|
509
|
+
...(await control.list(signal, { archived: true })),
|
|
510
|
+
],
|
|
511
|
+
};
|
|
512
|
+
},
|
|
458
513
|
deliver: (mail) => this.mail().deliver(mail),
|
|
459
514
|
prepare: () => Promise.resolve(),
|
|
460
515
|
stageAcceptedNotice: (mail, scopeId) => {
|
|
@@ -632,6 +687,28 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
632
687
|
? null
|
|
633
688
|
: Schema.decodeUnknownSync(policyDirectorySchema)(stored);
|
|
634
689
|
}
|
|
690
|
+
/** Borrow the native owner only after matching its real initialization pin. */
|
|
691
|
+
async scheduleOwner(expected, agentId) {
|
|
692
|
+
const directory = Schema.decodeUnknownSync(policyDirectorySchema)(expected);
|
|
693
|
+
const pin = await this.policyDirectory();
|
|
694
|
+
if (agentId !== this.ctx.id.name ||
|
|
695
|
+
pin?.directoryName !== directory.directoryName ||
|
|
696
|
+
pin.tenantId !== directory.tenantId)
|
|
697
|
+
throw new AppInstanceError("revoked");
|
|
698
|
+
return this.shiitake();
|
|
699
|
+
}
|
|
700
|
+
/** One admitted Agent's bounded native schedule page; this is not a directory scan. */
|
|
701
|
+
async readSchedules(directory, input) {
|
|
702
|
+
const { agentId, ...page } = appAgentScheduleListInputSchema.parse(input);
|
|
703
|
+
const owner = await this.scheduleOwner(directory, agentId);
|
|
704
|
+
return readAgentSchedulePage(agentId, owner, page);
|
|
705
|
+
}
|
|
706
|
+
/** Manage authority returns native identity/state only, never schedule content. */
|
|
707
|
+
async controlSchedule(directory, input, action) {
|
|
708
|
+
const target = agentScheduleTargetSchema.parse(input);
|
|
709
|
+
const owner = await this.scheduleOwner(directory, target.agentId);
|
|
710
|
+
return controlAgentSchedule(owner, target, action);
|
|
711
|
+
}
|
|
635
712
|
/** Listeners from live codemode acquisitions, told when the server list changes. */
|
|
636
713
|
mcpInvalidations = new Set();
|
|
637
714
|
async storedMcpServers() {
|
|
@@ -697,7 +774,13 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
697
774
|
meta: { schema: FungiMessageMetaSchema },
|
|
698
775
|
...this.mail().host,
|
|
699
776
|
instructions: {
|
|
700
|
-
load: () =>
|
|
777
|
+
load: async () => {
|
|
778
|
+
const { appApprovalInstructions } = await import("../mcp.js");
|
|
779
|
+
return {
|
|
780
|
+
revision: "oyster-7",
|
|
781
|
+
text: instructions + "\n" + appApprovalInstructions,
|
|
782
|
+
};
|
|
783
|
+
},
|
|
701
784
|
},
|
|
702
785
|
tools: {
|
|
703
786
|
sources: [
|
|
@@ -777,7 +860,16 @@ export class StandaloneAgentBase extends ShiitakeDO {
|
|
|
777
860
|
grants: [...extensions.grants, homeGrant],
|
|
778
861
|
},
|
|
779
862
|
...keeping,
|
|
780
|
-
|
|
863
|
+
admission: {
|
|
864
|
+
assertOpen: () => {
|
|
865
|
+
this.archiveControl().assertOpen();
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
hostWork: [
|
|
869
|
+
...this.mail().host.hostWork,
|
|
870
|
+
...keeping.hostWork,
|
|
871
|
+
this.archiveControl().work,
|
|
872
|
+
],
|
|
781
873
|
};
|
|
782
874
|
}
|
|
783
875
|
/** Send an installation-admitted message through fleet Session operations. */
|
|
@@ -841,6 +933,27 @@ const visitRow = Schema.Struct({
|
|
|
841
933
|
const counterRow = Schema.Struct({ value: Schema.Number });
|
|
842
934
|
/** Names this Oyster's agents and remembers which Sessions the member opened. */
|
|
843
935
|
export class AgentDirectory extends DurableObject {
|
|
936
|
+
archiveProjectionOwner;
|
|
937
|
+
archiveProjection() {
|
|
938
|
+
return (this.archiveProjectionOwner ??= agentArchiveProjection(this.ctx.storage.sql));
|
|
939
|
+
}
|
|
940
|
+
/** Apply the Agent DO's revisioned fact; an older repair cannot undo a newer one. */
|
|
941
|
+
recordArchive(agentId, archived, revision) {
|
|
942
|
+
const projection = this.archiveProjection();
|
|
943
|
+
return this.ctx.storage.transactionSync(() => {
|
|
944
|
+
if (!this.has(agentId))
|
|
945
|
+
return false;
|
|
946
|
+
const result = projection.record(agentId, archived, revision);
|
|
947
|
+
if (result === "advanced")
|
|
948
|
+
this.bump();
|
|
949
|
+
// A replay after projection but before the Agent's ack still succeeds.
|
|
950
|
+
return result !== "stale";
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
/** Directory's bounded-stale projection, never the Agent's flag authority. */
|
|
954
|
+
archived(agentId) {
|
|
955
|
+
return this.archiveProjection().archived(agentId);
|
|
956
|
+
}
|
|
844
957
|
computers;
|
|
845
958
|
computerNames = new Map();
|
|
846
959
|
directory;
|
|
@@ -1172,10 +1285,14 @@ export class AgentDirectory extends DurableObject {
|
|
|
1172
1285
|
provenance: { actor: oysterPrincipalId(owner), via: captured.agentId },
|
|
1173
1286
|
};
|
|
1174
1287
|
}
|
|
1175
|
-
list() {
|
|
1288
|
+
list(options) {
|
|
1289
|
+
const archived = this.archiveProjection().archivedIds();
|
|
1176
1290
|
return this.directory
|
|
1177
1291
|
.list(this.grant())
|
|
1178
1292
|
.toReversed()
|
|
1293
|
+
.filter(({ agentId }) => options?.archived === true
|
|
1294
|
+
? archived.has(agentId)
|
|
1295
|
+
: !archived.has(agentId))
|
|
1179
1296
|
.map(({ agentId, name, createdBy }) => ({
|
|
1180
1297
|
agentId,
|
|
1181
1298
|
createdBy,
|
|
@@ -1191,6 +1308,13 @@ export class AgentDirectory extends DurableObject {
|
|
|
1191
1308
|
readCreator(agentId) {
|
|
1192
1309
|
return this.directory.readCreator(this.grant(), agentId);
|
|
1193
1310
|
}
|
|
1311
|
+
/** Immutable creating Agent, distinct from the human credential owner. */
|
|
1312
|
+
createdBy(agentId) {
|
|
1313
|
+
const id = agentIds.agent.parse(agentId);
|
|
1314
|
+
return id.ok
|
|
1315
|
+
? (this.directory.read(this.grant(), id.value)?.createdBy ?? null)
|
|
1316
|
+
: null;
|
|
1317
|
+
}
|
|
1194
1318
|
/** Owner-only rename; refuses a name already held by another Agent. */
|
|
1195
1319
|
rename(agentId, name, principal) {
|
|
1196
1320
|
if (!oysterPrincipalIsOwner(principal))
|
|
@@ -1453,7 +1577,19 @@ export function standaloneAgents(env, scope) {
|
|
|
1453
1577
|
sessionOperations: everyOperation,
|
|
1454
1578
|
});
|
|
1455
1579
|
return {
|
|
1456
|
-
list: async () => (await directory().list()).map(toAgent),
|
|
1580
|
+
list: async (options) => (await directory().list(options)).map(toAgent),
|
|
1581
|
+
setArchived: async (agentId, archived, principal) => {
|
|
1582
|
+
if (!oysterPrincipalIsOwner(principal))
|
|
1583
|
+
return { error: "not_granted" };
|
|
1584
|
+
if (!(await directory().has(agentId)))
|
|
1585
|
+
return { error: "not_found" };
|
|
1586
|
+
const agent = env.AGENTS.getByName(agentId);
|
|
1587
|
+
const pin = await agent.policyDirectory();
|
|
1588
|
+
if (pin?.directoryName !== directoryName ||
|
|
1589
|
+
pin.tenantId !== (scope?.tenantId ?? defaultTenant))
|
|
1590
|
+
return { error: "not_found" };
|
|
1591
|
+
return agent.setArchived(archived);
|
|
1592
|
+
},
|
|
1457
1593
|
create: async (name, requestId, principal, actor, avatar) => {
|
|
1458
1594
|
const entry = await directory().create(name, requestId, principal, actor, avatar);
|
|
1459
1595
|
await env.AGENTS.getByName(entry.agentId).usePolicyDirectory({
|
|
@@ -1624,10 +1760,47 @@ export const agentFrameMethods = {
|
|
|
1624
1760
|
},
|
|
1625
1761
|
},
|
|
1626
1762
|
AGENTS: {
|
|
1763
|
+
schedules: {
|
|
1764
|
+
decodeArgs: (raw) => agentScheduleBindingArgs.schedules.parse(raw),
|
|
1765
|
+
decodeResult: Schema.decodeUnknownSync(agentSchedulePageSchema, {
|
|
1766
|
+
onExcessProperty: "error",
|
|
1767
|
+
}),
|
|
1768
|
+
requiredOperations: () => ["schedules"],
|
|
1769
|
+
requestedResource: (raw) => ({
|
|
1770
|
+
kind: "agent",
|
|
1771
|
+
id: agentScheduleBindingArgs.schedules.parse(raw)[0].agentId,
|
|
1772
|
+
}),
|
|
1773
|
+
},
|
|
1774
|
+
pauseSchedule: {
|
|
1775
|
+
decodeArgs: (raw) => agentScheduleBindingArgs.pauseSchedule.parse(raw),
|
|
1776
|
+
decodeResult: Schema.decodeUnknownSync(agentScheduleControlReceiptSchema, {
|
|
1777
|
+
onExcessProperty: "error",
|
|
1778
|
+
}),
|
|
1779
|
+
requiredOperations: () => ["pauseSchedule"],
|
|
1780
|
+
requestedResource: (raw) => ({
|
|
1781
|
+
kind: "agent",
|
|
1782
|
+
id: agentScheduleBindingArgs.pauseSchedule.parse(raw)[0].agentId,
|
|
1783
|
+
}),
|
|
1784
|
+
},
|
|
1785
|
+
deleteSchedule: {
|
|
1786
|
+
decodeArgs: (raw) => agentScheduleBindingArgs.deleteSchedule.parse(raw),
|
|
1787
|
+
decodeResult: Schema.decodeUnknownSync(agentScheduleControlReceiptSchema, {
|
|
1788
|
+
onExcessProperty: "error",
|
|
1789
|
+
}),
|
|
1790
|
+
requiredOperations: () => ["deleteSchedule"],
|
|
1791
|
+
requestedResource: (raw) => ({
|
|
1792
|
+
kind: "agent",
|
|
1793
|
+
id: agentScheduleBindingArgs.deleteSchedule.parse(raw)[0].agentId,
|
|
1794
|
+
}),
|
|
1795
|
+
},
|
|
1627
1796
|
send: {
|
|
1628
1797
|
decodeArgs: (raw) => agentBindingArgs.AGENTS.send.parse(raw),
|
|
1629
1798
|
decodeResult: decodeAgentAccepted,
|
|
1630
1799
|
requiredOperations: () => ["send"],
|
|
1800
|
+
requestedResource: (raw) => ({
|
|
1801
|
+
kind: "agent",
|
|
1802
|
+
id: agentBindingArgs.AGENTS.send.parse(raw)[0].agentId,
|
|
1803
|
+
}),
|
|
1631
1804
|
},
|
|
1632
1805
|
},
|
|
1633
1806
|
};
|
|
@@ -1669,9 +1842,40 @@ export async function dispatchAgentBinding(env, scope, frame, props, method, arg
|
|
|
1669
1842
|
});
|
|
1670
1843
|
result = { status: accepted.status, commandId: accepted.commandId };
|
|
1671
1844
|
}
|
|
1845
|
+
else if (props.binding === "AGENTS" &&
|
|
1846
|
+
(method === "schedules" ||
|
|
1847
|
+
method === "pauseSchedule" ||
|
|
1848
|
+
method === "deleteSchedule")) {
|
|
1849
|
+
const [input] = agentScheduleBindingArgs[method].parse(args);
|
|
1850
|
+
if (!(await directory.has(input.agentId)))
|
|
1851
|
+
throw new Error("No such agent");
|
|
1852
|
+
const home = await directory.agentHome(input.agentId);
|
|
1853
|
+
if (home?.kind === "machine")
|
|
1854
|
+
throw new MachineLinkRefused("actor_not_allowed");
|
|
1855
|
+
if (home === null)
|
|
1856
|
+
throw new AppInstanceError("revoked");
|
|
1857
|
+
frame.signal.throwIfAborted();
|
|
1858
|
+
const agent = env.AGENTS.getByName(input.agentId);
|
|
1859
|
+
const pin = {
|
|
1860
|
+
directoryName: scope.durableName("agents"),
|
|
1861
|
+
tenantId: scope.tenantId,
|
|
1862
|
+
};
|
|
1863
|
+
if (method === "schedules") {
|
|
1864
|
+
result = await agent.readSchedules(pin, agentScheduleBindingArgs.schedules.parse(args)[0]);
|
|
1865
|
+
}
|
|
1866
|
+
else {
|
|
1867
|
+
result = await agent.controlSchedule(pin, agentScheduleBindingArgs[method].parse(args)[0], method === "pauseSchedule" ? "pause" : "delete");
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1672
1870
|
else {
|
|
1673
1871
|
throw new AppInstanceError("revoked");
|
|
1674
1872
|
}
|
|
1675
1873
|
frame.signal.throwIfAborted();
|
|
1676
|
-
return Schema.decodeUnknownSync(Schema.Union([
|
|
1874
|
+
return Schema.decodeUnknownSync(Schema.Union([
|
|
1875
|
+
inboxPageSchema,
|
|
1876
|
+
inboxVisitSchema,
|
|
1877
|
+
AcceptedSchema,
|
|
1878
|
+
agentSchedulePageSchema,
|
|
1879
|
+
agentScheduleControlReceiptSchema,
|
|
1880
|
+
]), { onExcessProperty: "error" })(result);
|
|
1677
1881
|
}
|
|
@@ -9,7 +9,7 @@ import { z } from "zod";
|
|
|
9
9
|
import { buildStartFailureReply } from "./builds.js";
|
|
10
10
|
import * as Schema from "effect/Schema";
|
|
11
11
|
import { AppInstanceError } from "../index.js";
|
|
12
|
-
import { installOrSelect, verifyRelease
|
|
12
|
+
import { installOrSelect, verifyRelease } from "../host.js";
|
|
13
13
|
import { repositoryWorkflowSourceSchema } from "@fungi.computer/workflows-wire";
|
|
14
14
|
import { installationIdSchema } from "../apps/index.js";
|
|
15
15
|
import { appIdSchema, releaseSchema } from "@fungi.computer/puffball/effect";
|
|
@@ -9,8 +9,7 @@ import { publishSource } from "../pack.js";
|
|
|
9
9
|
import { appBuildRecipe, readBuildManifest } from "../build/workspace-build.js";
|
|
10
10
|
import { parseSourceRevision } from "@fungi.computer/puffball";
|
|
11
11
|
import { releaseSchema } from "@fungi.computer/puffball/effect";
|
|
12
|
-
import {
|
|
13
|
-
import { appPermissionPageSchema } from "../apps/permissions.js";
|
|
12
|
+
import { approveChosenDashboard } from "./dashboard-approval.js";
|
|
14
13
|
import { installOnOyster, InstallationObservationError, } from "../installation-client.js";
|
|
15
14
|
export const standaloneAppSet = [
|
|
16
15
|
{
|
|
@@ -141,57 +140,6 @@ export async function installStandaloneAppSet(options) {
|
|
|
141
140
|
init.signal = signal;
|
|
142
141
|
return fetch(new URL(path, options.url), init);
|
|
143
142
|
};
|
|
144
|
-
const approveChosenDashboard = async (installed, generation) => {
|
|
145
|
-
const name = installed.installationId;
|
|
146
|
-
const path = `/api/installations/${encodeURIComponent(name)}/grants`;
|
|
147
|
-
const projectionResponse = await owner("GET", path);
|
|
148
|
-
if (!projectionResponse.ok)
|
|
149
|
-
throw new Error(`${name} approval read failed (${String(projectionResponse.status)})`);
|
|
150
|
-
const projection = appPermissionPageSchema.parse(await projectionResponse.json());
|
|
151
|
-
const { permissions } = projection;
|
|
152
|
-
const digest = installed.release.artifact.artifactDigest;
|
|
153
|
-
if (projection.storeGeneration !== generation ||
|
|
154
|
-
projection.installationId !== name ||
|
|
155
|
-
projection.artifactDigest !== digest ||
|
|
156
|
-
permissions.declaredOperations.artifactDigest !== digest ||
|
|
157
|
-
permissions.authority.authorityVersion !==
|
|
158
|
-
installed.authority.authorityVersion ||
|
|
159
|
-
permissions.authority.declaredOperationsDigest !==
|
|
160
|
-
installed.authority.declaredOperationsDigest ||
|
|
161
|
-
permissions.declaredOperations.digest !==
|
|
162
|
-
installed.authority.declaredOperationsDigest)
|
|
163
|
-
throw new Error(`${name} approval snapshot changed`);
|
|
164
|
-
const shell = permissions.declaredOperations.capabilities.find((row) => row.capability === "team.workbenchHost");
|
|
165
|
-
if (shell === undefined)
|
|
166
|
-
return;
|
|
167
|
-
const command = grantCommandSchema.parse({
|
|
168
|
-
kind: "change-grants",
|
|
169
|
-
changes: [
|
|
170
|
-
{
|
|
171
|
-
capability: shell.capability,
|
|
172
|
-
grant: { operations: shell.operations, resources: "tenant-scope" },
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
});
|
|
176
|
-
const saved = await owner("PUT", path, Schema.decodeUnknownSync(Schema.Json)({
|
|
177
|
-
storeGeneration: projection.storeGeneration,
|
|
178
|
-
authorityVersion: installed.authority.authorityVersion,
|
|
179
|
-
artifactDigest: digest,
|
|
180
|
-
command,
|
|
181
|
-
}));
|
|
182
|
-
if (!saved.ok)
|
|
183
|
-
throw new Error(`${name} approval failed (${String(saved.status)})`);
|
|
184
|
-
const receipt = installationSchema.parse(await saved.json());
|
|
185
|
-
if (receipt.installationId !== name ||
|
|
186
|
-
receipt.instanceId !== installed.instanceId ||
|
|
187
|
-
receipt.release.artifact.artifactDigest !== digest ||
|
|
188
|
-
receipt.authority.authorityVersion !==
|
|
189
|
-
installed.authority.authorityVersion + 1 ||
|
|
190
|
-
!receipt.authority.approvedGrants.some((grant) => grant.capability === "team.workbenchHost" &&
|
|
191
|
-
grant.resources === "tenant-scope" &&
|
|
192
|
-
shell.operations.every((operation) => grant.operations.includes(operation))))
|
|
193
|
-
throw new Error(`${name} approval incomplete`);
|
|
194
|
-
};
|
|
195
143
|
const idle = options.idle ??
|
|
196
144
|
((ms, signal) => sleep(ms, undefined, { signal }));
|
|
197
145
|
/** Start one build and wait for its terminal outcome within the build
|
|
@@ -392,7 +340,7 @@ export async function installStandaloneAppSet(options) {
|
|
|
392
340
|
if (record.status !== "enabled")
|
|
393
341
|
throw new Error(`${name} fresh installation is not enabled`);
|
|
394
342
|
if (next.app.dashboard === true)
|
|
395
|
-
await approveChosenDashboard(
|
|
343
|
+
await approveChosenDashboard(operation, owner);
|
|
396
344
|
}
|
|
397
345
|
options.report(`${operation.creation === "replacement" ? "Replaced" : "Installed"} ${name} from its build ${build.jobId}`);
|
|
398
346
|
// Report-only UI check: never blocks, but the owner sees it here.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Routes } from "../authorization.js";
|
|
2
2
|
import type { TenantScope } from "../tenant.js";
|
|
3
3
|
import type { OysterBindingResult } from "../workers.js";
|
|
4
4
|
import type { OysterEnv } from "../host.js";
|
|
@@ -34,8 +34,7 @@ export declare const appSourceFrameMethods: {
|
|
|
34
34
|
* re-checked here (never trusted from a stale captured frame). */
|
|
35
35
|
export declare function dispatchAppSourceBinding(env: AppSourceBindingEnv, scope: TenantScope, frame: InvocationFrame, method: string, args: readonly unknown[]): Promise<OysterBindingResult>;
|
|
36
36
|
/** Operator-only: seed one App's editable source template (`install-apps.mjs`).
|
|
37
|
-
*
|
|
38
|
-
* path before this handler runs (`host.ts`'s `oysterPrincipalIsOwner` check).
|
|
37
|
+
* Each route declares the owner requirement (`authorization.ts`).
|
|
39
38
|
* The operator `git push`es the projected source to
|
|
40
39
|
* `/api/apps/:appId/source.git`, then claims that head here. */
|
|
41
|
-
export declare function appSourceOwnerRoutes(env: AppSourceBindingEnv, scope: TenantScope):
|
|
40
|
+
export declare function appSourceOwnerRoutes(env: AppSourceBindingEnv, scope: TenantScope): Routes<OysterEnv>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { owner, routes as routeTable } from "../authorization.js";
|
|
2
2
|
import { bodyLimit } from "hono/body-limit";
|
|
3
3
|
import * as Schema from "effect/Schema";
|
|
4
4
|
import { AppInstanceError } from "../index.js";
|
|
@@ -32,18 +32,14 @@ export async function dispatchAppSourceBinding(env, scope, frame, method, args)
|
|
|
32
32
|
return Schema.decodeUnknownSync(appForkResultSchema)(result);
|
|
33
33
|
}
|
|
34
34
|
/** Operator-only: seed one App's editable source template (`install-apps.mjs`).
|
|
35
|
-
*
|
|
36
|
-
* path before this handler runs (`host.ts`'s `oysterPrincipalIsOwner` check).
|
|
35
|
+
* Each route declares the owner requirement (`authorization.ts`).
|
|
37
36
|
* The operator `git push`es the projected source to
|
|
38
37
|
* `/api/apps/:appId/source.git`, then claims that head here. */
|
|
39
38
|
export function appSourceOwnerRoutes(env, scope) {
|
|
40
|
-
const routes =
|
|
39
|
+
const routes = routeTable();
|
|
41
40
|
const registry = () => env.REGISTRY.getByName(scope.durableName("registry"));
|
|
42
|
-
routes.on(["GET", "POST"], "/api/apps/:appId/source.git/:suffix{info/refs|git-upload-pack|git-receive-pack}", (context) => registry().exchangeTemplateSource(context.req.param("appId"), context.req.param("suffix"), context.req.raw));
|
|
43
|
-
routes.put("/api/apps/:appId/source",
|
|
44
|
-
maxSize: TEMPLATE_SEED_CLAIM_MAX_BYTES,
|
|
45
|
-
onError: (context) => context.json({ kind: "invalid" }, 413),
|
|
46
|
-
}), async (context) => {
|
|
41
|
+
routes.on(["GET", "POST"], "/api/apps/:appId/source.git/:suffix{info/refs|git-upload-pack|git-receive-pack}", owner("direct-or-app"), (context) => registry().exchangeTemplateSource(context.req.param("appId"), context.req.param("suffix"), context.req.raw));
|
|
42
|
+
routes.put("/api/apps/:appId/source", owner("direct-or-app"), async (context) => {
|
|
47
43
|
const claim = parseTemplateSeedClaim(await context.req.text());
|
|
48
44
|
if (claim === null)
|
|
49
45
|
return context.json({ kind: "invalid" }, 400);
|
|
@@ -54,6 +50,13 @@ export function appSourceOwnerRoutes(env, scope) {
|
|
|
54
50
|
? 409
|
|
55
51
|
: 503;
|
|
56
52
|
return Response.json(result, { status });
|
|
53
|
+
}, {
|
|
54
|
+
middleware: [
|
|
55
|
+
bodyLimit({
|
|
56
|
+
maxSize: TEMPLATE_SEED_CLAIM_MAX_BYTES,
|
|
57
|
+
onError: (context) => context.json({ kind: "invalid" }, 413),
|
|
58
|
+
}),
|
|
59
|
+
],
|
|
57
60
|
});
|
|
58
61
|
return routes;
|
|
59
62
|
}
|
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Owner routes for App builds on the standalone's Computer. They run after
|
|
3
|
-
* the host's owner guard; the build owner is the registry.
|
|
4
|
-
*
|
|
5
|
-
* Repository Apps register through APPS.register from their committed root
|
|
6
|
-
* metadata. Publishing an App's source (PUT /api/apps/:appId/source) instead
|
|
7
|
-
* makes its template the build source at the returned seed revision.
|
|
8
|
-
*
|
|
9
|
-
* - `POST /api/builds` starts a build of that source at one exact commit.
|
|
10
|
-
* - `GET /api/builds/:jobId` reads it; a finished build carries its Release,
|
|
11
|
-
* which the owner installs or selects through the installation routes.
|
|
12
|
-
*/
|
|
13
|
-
import { Hono } from "hono";
|
|
14
1
|
import { type OysterBuildStartReason } from "../app-builds.js";
|
|
15
2
|
import { type OysterEnv } from "../host.js";
|
|
3
|
+
import { type Routes } from "../authorization.js";
|
|
16
4
|
import type { StandaloneRegistry } from "./registry.js";
|
|
17
5
|
/** Both HTTP and APPS expose the same closed preflight reason. */
|
|
18
6
|
export declare function buildStartFailureReply(reason: OysterBuildStartReason | "unavailable"): {
|
|
@@ -24,4 +12,4 @@ export declare function buildStartFailureReply(reason: OysterBuildStartReason |
|
|
|
24
12
|
};
|
|
25
13
|
/** The build routes over the tenant registry that owns builds, resolved per
|
|
26
14
|
* request so mounting the routes reaches no registry. */
|
|
27
|
-
export declare function standaloneBuildRoutes(registryFor: () =>
|
|
15
|
+
export declare function standaloneBuildRoutes(registryFor: () => Pick<StandaloneRegistry, "startBuild" | "readBuild" | "readBuildLog">): Routes<OysterEnv>;
|