@fungi.computer/oyster 0.6.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 +132 -78
- 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 +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 +59 -21
- 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/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 +153 -272
- 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 +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 +157 -20
- package/dist/standalone/agents.js +234 -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 +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/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 +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/auth.md +0 -55
- package/docs/api/authorization.md +1300 -0
- package/docs/api/build.md +40 -37
- 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 +124 -235
- package/package.json +39 -20
- 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
|
@@ -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 {
|
|
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
|
|
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:
|
|
25
|
-
detach: (principal:
|
|
26
|
-
read: (principal:
|
|
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):
|
|
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 {
|
|
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 =
|
|
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;
|