@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.
Files changed (185) hide show
  1. package/README.md +155 -63
  2. package/dist/agent-archive-projection.d.ts +8 -0
  3. package/dist/agent-archive-projection.js +49 -0
  4. package/dist/agent-schedules.d.ts +50 -0
  5. package/dist/agent-schedules.js +17 -0
  6. package/dist/agents.d.ts +35 -14
  7. package/dist/agents.js +97 -144
  8. package/dist/app-builds.d.ts +7 -2
  9. package/dist/app-builds.js +19 -2
  10. package/dist/approval-capability.d.ts +1 -1
  11. package/dist/approval-capability.js +11 -16
  12. package/dist/approval-home.d.ts +2 -1
  13. package/dist/approval-home.js +5 -3
  14. package/dist/apps/catalog.js +1 -1
  15. package/dist/apps/gate.d.ts +4 -7
  16. package/dist/apps/gate.js +14 -29
  17. package/dist/apps/index.d.ts +1 -1
  18. package/dist/apps/installation.d.ts +4 -42
  19. package/dist/apps/installation.js +7 -75
  20. package/dist/apps/operations.js +0 -10
  21. package/dist/apps/permissions.d.ts +18 -8
  22. package/dist/apps/permissions.js +7 -1
  23. package/dist/auth.js +9 -1
  24. package/dist/authorization.d.ts +307 -0
  25. package/dist/authorization.js +542 -0
  26. package/dist/boot-host-script.js +1 -1
  27. package/dist/build/build-jobs.d.ts +2 -2
  28. package/dist/build/candidate-builder.d.ts +2 -0
  29. package/dist/build/candidate-builder.js +5 -1
  30. package/dist/build/candidate-cli.js +3 -0
  31. package/dist/build/candidate-cli.mjs +67 -22
  32. package/dist/build/job-log.d.ts +23 -0
  33. package/dist/build/job-log.js +88 -0
  34. package/dist/build/local-build.d.ts +2 -0
  35. package/dist/build/local-build.js +11 -9
  36. package/dist/channels/binding.d.ts +8 -1
  37. package/dist/channels/binding.js +12 -0
  38. package/dist/channels/conversations.d.ts +16 -5
  39. package/dist/channels/visitor-channel.d.ts +53 -0
  40. package/dist/channels/visitor-channel.js +124 -0
  41. package/dist/channels/visitor-edge.d.ts +115 -0
  42. package/dist/channels/visitor-edge.js +582 -0
  43. package/dist/channels/visitor-request.d.ts +7 -0
  44. package/dist/channels/visitor-request.js +17 -0
  45. package/dist/channels/visitor-stream-slots.d.ts +2 -0
  46. package/dist/channels/visitor-stream-slots.js +33 -0
  47. package/dist/channels/visitors.d.ts +47 -0
  48. package/dist/channels/visitors.js +181 -0
  49. package/dist/cloudflare.d.ts +11 -2
  50. package/dist/cloudflare.js +27 -1
  51. package/dist/{page/computer-target.d.ts → computer-target.d.ts} +2 -0
  52. package/dist/{page/computer-target.js → computer-target.js} +3 -1
  53. package/dist/files.js +9 -8
  54. package/dist/git-admission.d.ts +3 -3
  55. package/dist/git-admission.js +4 -6
  56. package/dist/git-door.d.ts +8 -7
  57. package/dist/git-door.js +18 -17
  58. package/dist/git.d.ts +1 -1
  59. package/dist/host.d.ts +38 -42
  60. package/dist/host.js +182 -239
  61. package/dist/installation-execution.js +10 -5
  62. package/dist/installation-operations.d.ts +4 -180
  63. package/dist/installation-operations.js +1 -5
  64. package/dist/installation-store-generation.d.ts +4 -1
  65. package/dist/installation-store-generation.js +59 -6
  66. package/dist/internal/delivery.js +0 -1
  67. package/dist/internal/host-gate.d.ts +9 -6
  68. package/dist/internal/host-gate.js +19 -25
  69. package/dist/internal/invocation-timing.d.ts +16 -0
  70. package/dist/internal/invocation-timing.js +23 -0
  71. package/dist/internal/registry-installations.js +1 -0
  72. package/dist/invocation.d.ts +19 -11
  73. package/dist/invocation.js +171 -96
  74. package/dist/keys.d.ts +2 -19
  75. package/dist/keys.js +1 -37
  76. package/dist/mcp.d.ts +15 -27
  77. package/dist/mcp.js +105 -155
  78. package/dist/page/agent-browser.d.ts +15 -0
  79. package/dist/page/agent-browser.js +162 -0
  80. package/dist/page/app-permissions-port.d.ts +3 -0
  81. package/dist/page/app-permissions-port.js +43 -0
  82. package/dist/page/app-permissions.d.ts +11 -0
  83. package/dist/page/app-permissions.js +296 -0
  84. package/dist/page/capabilities.js +1 -1
  85. package/dist/page/child-selection.d.ts +27 -0
  86. package/dist/page/child-selection.js +38 -0
  87. package/dist/page/isolated-page-theme.d.ts +2 -0
  88. package/dist/page/isolated-page-theme.js +11 -0
  89. package/dist/page/mount.d.ts +2 -13
  90. package/dist/page/mount.js +8 -94
  91. package/dist/page-script.d.ts +0 -3
  92. package/dist/page-script.js +3 -4
  93. package/dist/release.d.ts +13 -0
  94. package/dist/release.js +10 -0
  95. package/dist/request-origin.d.ts +4 -0
  96. package/dist/request-origin.js +10 -0
  97. package/dist/standalone/agent-archive-owner.d.ts +42 -0
  98. package/dist/standalone/agent-archive-owner.js +116 -0
  99. package/dist/standalone/agent-archive-schedules.d.ts +6 -0
  100. package/dist/standalone/agent-archive-schedules.js +76 -0
  101. package/dist/standalone/agent-archive.d.ts +31 -0
  102. package/dist/standalone/agent-archive.js +67 -0
  103. package/dist/standalone/agent-computers.d.ts +7 -6
  104. package/dist/standalone/agent-computers.js +4 -7
  105. package/dist/standalone/agent-mail.d.ts +2 -2
  106. package/dist/standalone/agents.d.ts +157 -20
  107. package/dist/standalone/agents.js +242 -38
  108. package/dist/standalone/app-builds-binding.js +1 -1
  109. package/dist/standalone/app-set.js +2 -54
  110. package/dist/standalone/app-source.d.ts +3 -4
  111. package/dist/standalone/app-source.js +12 -9
  112. package/dist/standalone/builds.d.ts +2 -14
  113. package/dist/standalone/builds.js +15 -13
  114. package/dist/standalone/computer-fleet.d.ts +4 -4
  115. package/dist/standalone/computer-fleet.js +20 -43
  116. package/dist/standalone/computer.d.ts +4 -3
  117. package/dist/standalone/computer.js +52 -53
  118. package/dist/standalone/dashboard-approval.d.ts +10 -0
  119. package/dist/standalone/dashboard-approval.js +66 -0
  120. package/dist/standalone/machine-link.d.ts +4 -221
  121. package/dist/standalone/machine-link.js +21 -15
  122. package/dist/standalone/media.d.ts +1 -0
  123. package/dist/standalone/media.js +20 -14
  124. package/dist/standalone/owner-routes.d.ts +16 -0
  125. package/dist/standalone/owner-routes.js +21 -0
  126. package/dist/standalone/permission-resources.d.ts +5 -0
  127. package/dist/standalone/permission-resources.js +31 -0
  128. package/dist/standalone/registry.d.ts +7 -0
  129. package/dist/standalone/registry.js +3 -0
  130. package/dist/standalone/tenancy.js +3 -1
  131. package/dist/standalone/worker-routes.d.ts +19 -0
  132. package/dist/standalone/worker-routes.js +9 -0
  133. package/dist/standalone/worker.d.ts +1 -1
  134. package/dist/standalone/worker.js +27 -35
  135. package/dist/standalone/workflow-binding.d.ts +8 -3
  136. package/dist/standalone/workflow-binding.js +14 -5
  137. package/dist/standalone/workflow-git-capture.js +2 -1
  138. package/dist/standalone/workflow-host.d.ts +8 -103
  139. package/dist/standalone/workflow-host.js +1 -1
  140. package/dist/standalone/workflow-node-capture.js +2 -2
  141. package/dist/standalone/workflow-root-authority.d.ts +1 -1
  142. package/dist/standalone/workflow-root-authority.js +1 -1
  143. package/dist/standalone/workflow-schedules.d.ts +14 -5
  144. package/dist/standalone/workflow-schedules.js +11 -5
  145. package/dist/standalone/workflow-webhooks.d.ts +4 -18
  146. package/dist/standalone/workflow-webhooks.js +5 -5
  147. package/dist/standalone/workflows.d.ts +36 -28
  148. package/dist/standalone/workflows.js +6 -3
  149. package/dist/storage-frame.d.ts +6 -6
  150. package/dist/testing/capabilities.d.ts +7 -6
  151. package/dist/testing/capabilities.js +3 -6
  152. package/dist/testing/installation.d.ts +0 -1
  153. package/dist/testing/installation.js +2 -5
  154. package/dist/turnstile.d.ts +27 -0
  155. package/dist/turnstile.js +51 -0
  156. package/dist/workers-git.d.ts +12 -4
  157. package/dist/workers-git.js +58 -36
  158. package/dist/workers.d.ts +9 -1
  159. package/dist/workers.js +59 -20
  160. package/dist/workflows.d.ts +2 -0
  161. package/dist/workflows.js +1 -0
  162. package/docs/api/README.md +7 -0
  163. package/docs/api/agent-schedules.md +175 -0
  164. package/docs/api/agents.md +34 -13
  165. package/docs/api/app-storage.md +1 -1
  166. package/docs/api/apps.md +323 -225
  167. package/docs/api/authorization.md +1300 -0
  168. package/docs/api/build-contract.md +2 -2
  169. package/docs/api/build.md +40 -37
  170. package/docs/api/cloudflare.md +27 -2
  171. package/docs/api/git.md +20 -11
  172. package/docs/api/host.md +80 -199
  173. package/docs/api/index.md +1 -1
  174. package/docs/api/invocation.md +15 -11
  175. package/docs/api/keys.md +1 -98
  176. package/docs/api/mcp.md +43 -40
  177. package/docs/api/modules.md +2 -0
  178. package/docs/api/page/mount.md +16 -19
  179. package/docs/api/workers-git.md +46 -12
  180. package/docs/api/workers.md +125 -235
  181. package/docs/site/agent-quickstart.mdx +6 -2
  182. package/examples/standalone/wrangler.celld.jsonc +66 -0
  183. package/package.json +64 -21
  184. package/dist/standalone/channel-session-door.d.ts +0 -4
  185. package/dist/standalone/channel-session-door.js +0 -42
package/dist/release.d.ts CHANGED
@@ -60,6 +60,19 @@ export type VerifiedRelease = Readonly<{
60
60
  export declare function sha256(bytes: Uint8Array): Promise<string>;
61
61
  /** Read one artifact and refuse it unless its bytes match `digest`. */
62
62
  export declare function readVerified(store: ArtifactStore, artifactRef: ArtifactRef, digest: Sha256Digest): Promise<Uint8Array>;
63
+ /**
64
+ * Copies of verified artifacts near the reader, keyed by content digest. An
65
+ * entry holds only bytes that already matched its digest, so a hit is served
66
+ * without another store read or hash.
67
+ */
68
+ export type VerifiedObjectCache = Readonly<{
69
+ /** The cached bytes for `digest`, or null on a miss. */
70
+ match: (digest: Sha256Digest) => Promise<ReadableStream<Uint8Array> | null>;
71
+ /** Keep verified bytes; an unstored write only costs the next reader a store read. */
72
+ put: (digest: Sha256Digest, bytes: Uint8Array) => Promise<"stored" | "unstored">;
73
+ }>;
74
+ /** {@link readVerified} through an optional digest-keyed cache it fills on a miss. */
75
+ export declare function readCachedVerified(store: ArtifactStore, cache: VerifiedObjectCache | undefined, artifactRef: ArtifactRef, digest: Sha256Digest): Promise<ReadableStream<Uint8Array> | Uint8Array<ArrayBuffer>>;
63
76
  /** Decode an untrusted Release record, as an install request carries it. */
64
77
  export declare function parseRelease(value: unknown): Release;
65
78
  /**
package/dist/release.js CHANGED
@@ -36,6 +36,16 @@ export async function readVerified(store, artifactRef, digest) {
36
36
  throw new ReleaseError("digest_mismatch", `Digest mismatch ${artifactRef}`);
37
37
  return bytes;
38
38
  }
39
+ /** {@link readVerified} through an optional digest-keyed cache it fills on a miss. */
40
+ export async function readCachedVerified(store, cache, artifactRef, digest) {
41
+ const hit = await cache?.match(digest);
42
+ if (hit !== undefined && hit !== null)
43
+ return hit;
44
+ const bytes = await readVerified(store, artifactRef, digest);
45
+ await cache?.put(digest, bytes);
46
+ // A response body owns a plain ArrayBuffer, whatever view the store returned.
47
+ return new Uint8Array(bytes);
48
+ }
39
49
  const decodeText = (bytes) => new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(bytes);
40
50
  /** Decode an untrusted Release record, as an install request carries it. */
41
51
  export function parseRelease(value) {
@@ -0,0 +1,4 @@
1
+ /** Require the request's own Origin and refuse cross-site provenance. */
2
+ export declare function sameOriginRequest(request: Request): boolean;
3
+ /** Channel reads allow absent Origin but never conflicting or cross-site provenance. */
4
+ export declare function sameOriginReadRequest(request: Request): boolean;
@@ -0,0 +1,10 @@
1
+ /** Require the request's own Origin and refuse cross-site provenance. */
2
+ export function sameOriginRequest(request) {
3
+ return request.headers.has("origin") && sameOriginReadRequest(request);
4
+ }
5
+ /** Channel reads allow absent Origin but never conflicting or cross-site provenance. */
6
+ export function sameOriginReadRequest(request) {
7
+ const origin = request.headers.get("origin");
8
+ return (request.headers.get("sec-fetch-site") !== "cross-site" &&
9
+ (origin === null || origin === new URL(request.url).origin));
10
+ }
@@ -0,0 +1,42 @@
1
+ import type { CloudflareShiitake } from "@fungi.computer/shiitake/cloudflare";
2
+ import { type ArchiveRefusalReason } from "@fungi.computer/shiitake/agent-control";
3
+ import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
4
+ export { agentArchiveProjection } from "../agent-archive-projection.js";
5
+ export type AgentArchiveTransitionResult = Readonly<{
6
+ archived: boolean;
7
+ revision: number;
8
+ }> | Readonly<{
9
+ error: ArchiveRefusalReason;
10
+ message?: string;
11
+ archived?: boolean;
12
+ revision?: number;
13
+ }>;
14
+ /** The one owner-local archive transition, admission fence and repair cursor.
15
+ * The host lends only its existing transport and revisioned directory projection.
16
+ */
17
+ export declare function agentArchiveOwner(deps: Readonly<{
18
+ storage: Pick<WatchdogSqliteOwner, "sql" | "transactionSync">;
19
+ agentId: () => string | undefined;
20
+ agent: () => Promise<CloudflareShiitake>;
21
+ /** Enable reconciliation where the host mounts Almanac and Loop with command grants. */
22
+ scheduledExtensions?: boolean;
23
+ refreshOutstanding: (agentId: string) => Promise<void>;
24
+ record: (agentId: string, archived: boolean, revision: number) => Promise<boolean>;
25
+ wake: () => Promise<void>;
26
+ waitUntil: (task: Promise<unknown>) => void;
27
+ }>): {
28
+ read: () => {
29
+ archived: boolean;
30
+ revision: number;
31
+ pending: boolean;
32
+ };
33
+ assertOpen(): void;
34
+ work: {
35
+ pending: () => {
36
+ due: boolean;
37
+ nextWakeAt: number | null;
38
+ };
39
+ run: () => Promise<void>;
40
+ };
41
+ setArchived: (archived: boolean) => Promise<AgentArchiveTransitionResult>;
42
+ };
@@ -0,0 +1,116 @@
1
+ import { ShiitakeError } from "@fungi.computer/shiitake";
2
+ import { AgentArchiveRefused, } from "@fungi.computer/shiitake/agent-control";
3
+ import { agentArchive } from "./agent-archive.js";
4
+ import { reconcileArchiveSchedules } from "./agent-archive-schedules.js";
5
+ export { agentArchiveProjection } from "../agent-archive-projection.js";
6
+ /** The one owner-local archive transition, admission fence and repair cursor.
7
+ * The host lends only its existing transport and revisioned directory projection.
8
+ */
9
+ export function agentArchiveOwner(deps) {
10
+ const archive = agentArchive(deps.storage.sql);
11
+ let retryAfter = 0;
12
+ let tail = Promise.resolve();
13
+ const serialize = (action) => {
14
+ const run = tail.then(action);
15
+ tail = run.then(() => undefined, () => undefined);
16
+ return run;
17
+ };
18
+ const repairOnce = async () => {
19
+ const state = archive.read();
20
+ if (!state.pending)
21
+ return;
22
+ try {
23
+ const agentId = deps.agentId();
24
+ if (agentId === undefined)
25
+ throw new Error("Agent archive scope is unavailable");
26
+ if (deps.scheduledExtensions)
27
+ await reconcileArchiveSchedules(await deps.agent(), archive, state.archived);
28
+ if (!(await deps.record(agentId, state.archived, state.revision)))
29
+ throw new Error("Agent archive directory is unavailable");
30
+ deps.storage.transactionSync(() => {
31
+ archive.ack(state.revision);
32
+ });
33
+ retryAfter = 0;
34
+ }
35
+ catch (cause) {
36
+ retryAfter = Date.now() + 30_000;
37
+ throw cause;
38
+ }
39
+ };
40
+ return {
41
+ read: archive.read,
42
+ assertOpen() {
43
+ if (archive.read().archived)
44
+ throw new ShiitakeError("contract", "archived", "Agent is archived");
45
+ },
46
+ work: {
47
+ pending: () => {
48
+ const state = archive.read();
49
+ return {
50
+ due: state.pending && Date.now() >= retryAfter,
51
+ nextWakeAt: state.pending ? Math.max(Date.now(), retryAfter) : null,
52
+ };
53
+ },
54
+ run: () => serialize(repairOnce),
55
+ },
56
+ setArchived: (archived) => serialize(async () => {
57
+ const agent = await deps.agent();
58
+ const agentId = deps.agentId();
59
+ if (agentId === undefined)
60
+ return { error: "unreadable" };
61
+ if (archived) {
62
+ try {
63
+ await deps.refreshOutstanding(agentId);
64
+ }
65
+ catch (cause) {
66
+ console.error("agent archive settlement refresh failed", {
67
+ kind: cause instanceof Error ? cause.name : "Unknown",
68
+ });
69
+ return { error: "unreadable" };
70
+ }
71
+ }
72
+ let changed;
73
+ try {
74
+ changed = deps.storage.transactionSync(() => {
75
+ const before = archive.read();
76
+ if (archived && !before.archived) {
77
+ const busy = agent.busySync();
78
+ if (busy !== undefined)
79
+ throw new AgentArchiveRefused(busy.kind === "session" ? "running" : "pending", busy.kind === "session"
80
+ ? `Command ${busy.commandId} is busy in Session ${busy.sessionId}`
81
+ : `Delegated command ${busy.commandId} is unsettled for Agent ${busy.agentId}`);
82
+ }
83
+ return archive.transition(archived);
84
+ });
85
+ }
86
+ catch (error) {
87
+ if (error instanceof AgentArchiveRefused)
88
+ return { error: error.reason, message: error.message };
89
+ console.error("agent archive busy read failed", {
90
+ kind: error instanceof Error ? error.name : "Unknown",
91
+ });
92
+ return { error: "unreadable" };
93
+ }
94
+ if (changed.pending) {
95
+ try {
96
+ await repairOnce();
97
+ }
98
+ catch (error) {
99
+ deps.waitUntil(deps.wake().catch((cause) => {
100
+ console.error("agent archive repair wake failed", cause);
101
+ }));
102
+ console.error("agent archive projection pending", {
103
+ kind: error instanceof Error ? error.name : "Unknown",
104
+ });
105
+ return {
106
+ error: "unreadable",
107
+ message: "Archive state changed; directory or schedule repair is pending",
108
+ archived: changed.archived,
109
+ revision: changed.revision,
110
+ };
111
+ }
112
+ }
113
+ return { archived: changed.archived, revision: changed.revision };
114
+ }),
115
+ };
116
+ }
@@ -0,0 +1,6 @@
1
+ import type { ShiitakeAgent } from "@fungi.computer/shiitake/client";
2
+ import type { agentArchive } from "./agent-archive.js";
3
+ /** Reconcile extension-owned timers via their ordinary commands, not private tables.
4
+ * Durable pause markers are written before the commands, so restart can retry.
5
+ */
6
+ export declare function reconcileArchiveSchedules(owner: ShiitakeAgent, archive: ReturnType<typeof agentArchive>, archived: boolean): Promise<void>;
@@ -0,0 +1,76 @@
1
+ import { almanacContract, Schedule, } from "@fungi.computer/shiitake-extensions/almanac";
2
+ import { loopContract, LoopProjection, } from "@fungi.computer/shiitake-extensions/loop";
3
+ import * as Schema from "effect/Schema";
4
+ const schedules = Schema.Array(Schedule);
5
+ const schedule = Schema.NullOr(Schedule);
6
+ const loop = Schema.NullOr(LoopProjection);
7
+ /** Reconcile extension-owned timers via their ordinary commands, not private tables.
8
+ * Durable pause markers are written before the commands, so restart can retry.
9
+ */
10
+ export async function reconcileArchiveSchedules(owner, archive, archived) {
11
+ if (archived) {
12
+ let after;
13
+ do {
14
+ const request = after === undefined ? { limit: 64 } : { after, limit: 64 };
15
+ const listed = Schema.decodeUnknownSync(schedules)(await owner.extensions.call(almanacContract.id, "list", request));
16
+ for (const item of listed) {
17
+ if (item.state !== "active")
18
+ continue;
19
+ archive.suspend("almanac", item.id);
20
+ await owner.extensions.call(almanacContract.id, "pause", {
21
+ scheduleId: item.id,
22
+ });
23
+ }
24
+ after = listed.length === 64 ? listed.at(-1)?.id : undefined;
25
+ } while (after !== undefined);
26
+ let next;
27
+ do {
28
+ const page = await owner.listSessions(next);
29
+ for (const row of page.sessions) {
30
+ const slot = (await owner.session(row.sessionId).read({ limit: 1 }))
31
+ .extensions[loopContract.id];
32
+ if (slot?.status !== "running")
33
+ continue;
34
+ const current = Schema.decodeUnknownSync(loop)(slot.value);
35
+ if (current?.loop.state !== "active")
36
+ continue;
37
+ archive.suspend("loop", row.sessionId);
38
+ await owner
39
+ .session(row.sessionId)
40
+ .extension(loopContract.id)
41
+ .call("pause", {});
42
+ }
43
+ next = page.next;
44
+ } while (next !== undefined);
45
+ return;
46
+ }
47
+ for (;;) {
48
+ const batch = archive.suspended();
49
+ if (batch.length === 0)
50
+ break;
51
+ for (const item of batch) {
52
+ if (item.kind === "almanac") {
53
+ const current = Schema.decodeUnknownSync(schedule)(await owner.extensions.call(almanacContract.id, "read", {
54
+ scheduleId: item.id,
55
+ }));
56
+ if (current?.state === "paused")
57
+ await owner.extensions.call(almanacContract.id, "resume", {
58
+ scheduleId: item.id,
59
+ });
60
+ }
61
+ else {
62
+ const slot = (await owner.session(item.id).read({ limit: 1 }))
63
+ .extensions[loopContract.id];
64
+ if (slot?.status === "running") {
65
+ const current = Schema.decodeUnknownSync(loop)(slot.value);
66
+ if (current?.loop.state === "paused")
67
+ await owner
68
+ .session(item.id)
69
+ .extension(loopContract.id)
70
+ .call("resume", {});
71
+ }
72
+ }
73
+ archive.resumed(item.kind, item.id);
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
3
+ declare const suspendedRow: z.ZodObject<{
4
+ kind: z.ZodEnum<{
5
+ almanac: "almanac";
6
+ loop: "loop";
7
+ }>;
8
+ id: z.ZodString;
9
+ }, z.core.$strict>;
10
+ /** The Agent DO's one archive authority and durable repair cursor.
11
+ * Call transitions inside the same SQLite transaction as Shiitake.busySync.
12
+ */
13
+ export declare function agentArchive(sql: WatchdogSqliteOwner["sql"]): {
14
+ read: () => {
15
+ archived: boolean;
16
+ revision: number;
17
+ pending: boolean;
18
+ };
19
+ /** Idempotent intent; a failed directory or schedule write retains pending. */
20
+ transition(archived: boolean): {
21
+ archived: boolean;
22
+ revision: number;
23
+ pending: boolean;
24
+ };
25
+ ack(revision: number): void;
26
+ /** Only schedules this archive paused are eligible for auto-resume. */
27
+ suspend(kind: "almanac" | "loop", id: string): void;
28
+ suspended(): readonly z.infer<typeof suspendedRow>[];
29
+ resumed(kind: "almanac" | "loop", id: string): void;
30
+ };
31
+ export {};
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+ const stateRow = z.strictObject({
3
+ archived: z.union([z.literal(0), z.literal(1)]),
4
+ revision: z.number().int().nonnegative(),
5
+ pending: z.union([z.literal(0), z.literal(1)]),
6
+ });
7
+ const suspendedRow = z.strictObject({
8
+ kind: z.enum(["almanac", "loop"]),
9
+ id: z.string(),
10
+ });
11
+ /** The Agent DO's one archive authority and durable repair cursor.
12
+ * Call transitions inside the same SQLite transaction as Shiitake.busySync.
13
+ */
14
+ export function agentArchive(sql) {
15
+ sql.exec(`CREATE TABLE IF NOT EXISTS oyster_agent_archive (
16
+ singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
17
+ archived INTEGER NOT NULL CHECK(archived IN (0, 1)),
18
+ revision INTEGER NOT NULL,
19
+ pending INTEGER NOT NULL CHECK(pending IN (0, 1))
20
+ ) STRICT`);
21
+ sql.exec("INSERT OR IGNORE INTO oyster_agent_archive(singleton,archived,revision,pending) VALUES(1,0,0,0)");
22
+ sql.exec(`CREATE TABLE IF NOT EXISTS oyster_agent_archive_suspended (
23
+ kind TEXT NOT NULL CHECK(kind IN ('almanac','loop')),
24
+ id TEXT NOT NULL,
25
+ PRIMARY KEY(kind,id)
26
+ ) STRICT`);
27
+ const read = () => {
28
+ const raw = sql
29
+ .exec("SELECT archived,revision,pending FROM oyster_agent_archive WHERE singleton=1")
30
+ .toArray()[0];
31
+ if (raw === undefined)
32
+ throw new Error("Agent archive authority is missing");
33
+ const row = stateRow.parse(raw);
34
+ return {
35
+ archived: row.archived === 1,
36
+ revision: row.revision,
37
+ pending: row.pending === 1,
38
+ };
39
+ };
40
+ return {
41
+ read,
42
+ /** Idempotent intent; a failed directory or schedule write retains pending. */
43
+ transition(archived) {
44
+ const before = read();
45
+ if (before.archived === archived)
46
+ return before;
47
+ sql.exec("UPDATE oyster_agent_archive SET archived=?, revision=revision+1, pending=1 WHERE singleton=1", archived ? 1 : 0);
48
+ return read();
49
+ },
50
+ ack(revision) {
51
+ sql.exec("UPDATE oyster_agent_archive SET pending=0 WHERE singleton=1 AND revision=?", revision);
52
+ },
53
+ /** Only schedules this archive paused are eligible for auto-resume. */
54
+ suspend(kind, id) {
55
+ sql.exec("INSERT OR IGNORE INTO oyster_agent_archive_suspended(kind,id) VALUES(?,?)", kind, id);
56
+ },
57
+ suspended() {
58
+ return sql
59
+ .exec("SELECT kind,id FROM oyster_agent_archive_suspended ORDER BY kind,id LIMIT 64")
60
+ .toArray()
61
+ .map((raw) => suspendedRow.parse(raw));
62
+ },
63
+ resumed(kind, id) {
64
+ sql.exec("DELETE FROM oyster_agent_archive_suspended WHERE kind=? AND id=?", kind, id);
65
+ },
66
+ };
67
+ }
@@ -1,8 +1,9 @@
1
- import { Hono } from "hono";
1
+ import type { TenantPrincipal } from "../tenant.js";
2
2
  import { type ComputerGrant } from "@fungi.computer/computer/fleet";
3
3
  import { type AgentComputerProjection, type ComputerAttachmentInput } from "../agent-computers.js";
4
4
  import type { AgentComputerConnection, AgentComputerPorts } from "../agent-computer-source.js";
5
- import { type OysterEnv, type OysterPrincipal } from "../host.js";
5
+ import { type OysterEnv } from "../host.js";
6
+ import { type Routes } from "../authorization.js";
6
7
  import type { StandaloneAgentComputerRpc } from "./computer.js";
7
8
  /** The directory supplies live authority; this adapter never invents a grant. */
8
9
  export type StandaloneAgentComputerAccess = Readonly<{
@@ -21,9 +22,9 @@ export type StandaloneAgentComputerReply = Readonly<{
21
22
  }>;
22
23
  /** Principal and persisted attachment transitions belong to the directory owner. */
23
24
  export type StandaloneAgentComputerControl = Readonly<{
24
- attach: (principal: OysterPrincipal, command: ComputerAttachmentInput) => Promise<StandaloneAgentComputerReply>;
25
- detach: (principal: OysterPrincipal, command: ComputerAttachmentInput) => Promise<StandaloneAgentComputerReply>;
26
- read: (principal: OysterPrincipal, agentId: string) => Promise<StandaloneAgentComputerReply>;
25
+ attach: (principal: TenantPrincipal, command: ComputerAttachmentInput) => Promise<StandaloneAgentComputerReply>;
26
+ detach: (principal: TenantPrincipal, command: ComputerAttachmentInput) => Promise<StandaloneAgentComputerReply>;
27
+ read: (principal: TenantPrincipal, agentId: string) => Promise<StandaloneAgentComputerReply>;
27
28
  }>;
28
29
  /** Owner HTTP transport over the retained attachment command grammar. */
29
- export declare function standaloneAgentComputerRoutes(control: StandaloneAgentComputerControl): Hono<OysterEnv>;
30
+ export declare function standaloneAgentComputerRoutes(control: StandaloneAgentComputerControl): Routes<OysterEnv>;
@@ -1,9 +1,9 @@
1
- import { Hono } from "hono";
2
1
  import { createComputerFilesPort } from "@fungi.computer/computer";
3
2
  import { checkComputerGrant, FleetGrantRefused, } from "@fungi.computer/computer/fleet";
4
3
  import { computerCapabilities, computerValueSchema, } from "@fungi.computer/computer-contract";
5
4
  import { attachComputerInputSchema, detachComputerInputSchema, readAgentComputersInputSchema, } from "../agent-computers.js";
6
- import { oysterPrincipalIsOwner, } from "../host.js";
5
+ import {} from "../host.js";
6
+ import { owner, routes as routeTable } from "../authorization.js";
7
7
  /** Native operations use the captured Computer, never the mutable workspace selection. */
8
8
  export function standaloneAgentComputerPorts(access, connection) {
9
9
  const { computerId } = connection.attachment;
@@ -55,12 +55,9 @@ export function standaloneAgentComputerPorts(access, connection) {
55
55
  }
56
56
  /** Owner HTTP transport over the retained attachment command grammar. */
57
57
  export function standaloneAgentComputerRoutes(control) {
58
- const routes = new Hono();
59
- routes.post("/api/agents/:agentId/computers/:operation", async (context) => {
58
+ const routes = routeTable();
59
+ routes.post("/api/agents/:agentId/computers/:operation", owner("direct-or-app"), async (context, { principal }) => {
60
60
  context.header("Cache-Control", "no-store");
61
- const principal = context.get("principal");
62
- if (!oysterPrincipalIsOwner(principal))
63
- return context.json({ error: "not_granted" }, 403);
64
61
  const operation = context.req.param("operation");
65
62
  if (!["attach", "detach", "read"].includes(operation))
66
63
  return context.json({ error: "not_found" }, 404);
@@ -21,11 +21,11 @@ export declare function agentMail(options: Omit<ConstructorParameters<typeof Age
21
21
  tickConcurrent: () => Promise<WatchdogTickResult>;
22
22
  }>) => {
23
23
  execute: (job: {
24
+ readonly lane: string;
25
+ readonly state: "running";
24
26
  readonly queue: string;
25
27
  readonly priority: number;
26
28
  readonly payload: unknown;
27
- readonly state: "running";
28
- readonly lane: string;
29
29
  readonly jobId: string;
30
30
  readonly recovery?: {
31
31
  readonly maxRecoveries: number;