@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
package/dist/workers-git.js
CHANGED
|
@@ -13,7 +13,6 @@ import { APP_INVOCATION_CEILING_MS, invocationFacts, invocationCallerContext, }
|
|
|
13
13
|
import { AppInstanceError, selectionOf } from "./index.js";
|
|
14
14
|
import { ownerOrKeyAuth } from "./auth.js";
|
|
15
15
|
import { causeClass } from "./cause-class.js";
|
|
16
|
-
import { Hono } from "hono";
|
|
17
16
|
import { HTTPException } from "hono/http-exception";
|
|
18
17
|
import * as Schema from "effect/Schema";
|
|
19
18
|
import { defineIdFamily } from "@fungi.computer/cairn";
|
|
@@ -26,7 +25,8 @@ import { agentCapabilities, agentApprovalRoutes, agentMcpServerRoutes, } from ".
|
|
|
26
25
|
import { createArtifactsRestNamespace, } from "@fungi.computer/burrow";
|
|
27
26
|
import { attemptWebhook } from "./webhook-delivery.js";
|
|
28
27
|
import { createRecipientDeliveries, } from "./event-recipients.js";
|
|
29
|
-
import { createOyster,
|
|
28
|
+
import { createOyster, } from "./host.js";
|
|
29
|
+
import { oysterPrincipalIsOwner, owner as ownerRoute, routes as routeTable, } from "./authorization.js";
|
|
30
30
|
import { OysterInstance, oysterBindingEntrypoint, oysterBindings, oysterWorkerHost, r2FileSource, r2ArtifactStore, serveOysterBootCapability, } from "./workers.js";
|
|
31
31
|
import { registryInstallations } from "./internal/registry-installations.js";
|
|
32
32
|
import { refuseOlderOysterStore } from "./older-oyster-store-refusal.js";
|
|
@@ -763,36 +763,29 @@ const subscribeSchema = Schema.Struct({
|
|
|
763
763
|
secret: Schema.optional(Schema.String.check(Schema.isMinLength(16))),
|
|
764
764
|
});
|
|
765
765
|
const keyNamePattern = /^[a-z0-9][a-z0-9-]{0,39}$/u;
|
|
766
|
-
/** `invocation.ts:486` uses `team-key:visitor` for anonymous readers; a key can never claim that name. */
|
|
767
|
-
const reservedKeyName = "visitor";
|
|
768
766
|
const keysWire = keyWire(oysterKeyVocabulary);
|
|
769
767
|
/** Owner routes for keys and webhooks; `createOyster` mounts them owner-only. */
|
|
770
768
|
export function gitAdminRoutes(env, scope) {
|
|
771
|
-
const routes =
|
|
769
|
+
const routes = routeTable();
|
|
772
770
|
const keys = () => env.KEYS.getByName(scope.durableName("keys"));
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const listed = await keys().list(owner.actor.memberId);
|
|
771
|
+
// Key and webhook custody is the owner's own, never through an App.
|
|
772
|
+
const owner = ownerRoute("direct");
|
|
773
|
+
routes.get("/api/keys", owner, async (_context, { principal }) => {
|
|
774
|
+
const listed = await keys().list(principal.actor.memberId);
|
|
778
775
|
if (listed.kind === "error")
|
|
779
776
|
return Response.json({ error: listed.error.code }, { status: 500 });
|
|
780
777
|
return Response.json({ keys: listed.value.map(keysWire.toSummary) });
|
|
781
778
|
});
|
|
782
|
-
routes.post("/api/keys", async (context) => {
|
|
779
|
+
routes.post("/api/keys", owner, async (context, { principal }) => {
|
|
783
780
|
const body = keysWire.issue.safeParse(await context.req.json());
|
|
784
781
|
if (!body.success)
|
|
785
782
|
return Response.json({ error: "Invalid key" }, { status: 400 });
|
|
786
783
|
// Named identities and rate policy belong to this host, not the wire grammar.
|
|
787
784
|
if (!keyNamePattern.test(body.data.name) ||
|
|
788
|
-
body.data.name === reservedKeyName ||
|
|
789
785
|
body.data.keyId !== undefined ||
|
|
790
786
|
body.data.rateLimit !== undefined)
|
|
791
787
|
return Response.json({ error: "Invalid key name" }, { status: 400 });
|
|
792
|
-
const
|
|
793
|
-
if (!oysterPrincipalIsOwner(owner))
|
|
794
|
-
return Response.json({ error: "Owner only" }, { status: 403 });
|
|
795
|
-
const issued = await keys().issue(owner.actor.memberId, {
|
|
788
|
+
const issued = await keys().issue(principal.actor.memberId, {
|
|
796
789
|
name: body.data.name,
|
|
797
790
|
grants: body.data.grants,
|
|
798
791
|
expiresAt: body.data.expiresAt,
|
|
@@ -804,19 +797,16 @@ export function gitAdminRoutes(env, scope) {
|
|
|
804
797
|
key: keysWire.toSummary(issued.value.record),
|
|
805
798
|
}, { status: 201 });
|
|
806
799
|
});
|
|
807
|
-
routes.post("/api/keys/revoke", async (context) => {
|
|
800
|
+
routes.post("/api/keys/revoke", owner, async (context, { principal }) => {
|
|
808
801
|
const body = keysWire.revoke.safeParse(await context.req.json());
|
|
809
802
|
if (!body.success)
|
|
810
803
|
return Response.json({ error: "Invalid request" }, { status: 400 });
|
|
811
|
-
const
|
|
812
|
-
if (!oysterPrincipalIsOwner(owner))
|
|
813
|
-
return Response.json({ error: "Owner only" }, { status: 403 });
|
|
814
|
-
const revoked = await keys().revoke(owner.actor.memberId, body.data.requestId, body.data.keyId);
|
|
804
|
+
const revoked = await keys().revoke(principal.actor.memberId, body.data.requestId, body.data.keyId);
|
|
815
805
|
if (revoked.kind === "error")
|
|
816
806
|
return Response.json({ error: revoked.error.code }, { status: revoked.error.code === "denied" ? 404 : 400 });
|
|
817
807
|
return Response.json({ key: keysWire.toSummary(revoked.value) });
|
|
818
808
|
});
|
|
819
|
-
routes.post("/api/webhooks", async (context) => {
|
|
809
|
+
routes.post("/api/webhooks", owner, async (context) => {
|
|
820
810
|
const body = Schema.decodeUnknownOption(subscribeSchema)(await context.req.json());
|
|
821
811
|
if (body._tag === "None")
|
|
822
812
|
return Response.json({ error: "Invalid subscription" }, { status: 400 });
|
|
@@ -825,9 +815,9 @@ export function gitAdminRoutes(env, scope) {
|
|
|
825
815
|
types: body.value.types ?? [],
|
|
826
816
|
}), { status: 201 });
|
|
827
817
|
});
|
|
828
|
-
routes.get("/api/webhooks", async () => Response.json(await env.WEBHOOKS.getByName(scope.durableName("webhooks")).subscriptions()));
|
|
829
|
-
routes.get("/api/webhooks/:id/deliveries", async (context) => Response.json(await env.WEBHOOKS.getByName(scope.durableName("webhooks")).deliveries(context.req.param("id"))));
|
|
830
|
-
routes.delete("/api/webhooks/:id", async (context) => (await env.WEBHOOKS.getByName(scope.durableName("webhooks")).unsubscribe(context.req.param("id")))
|
|
818
|
+
routes.get("/api/webhooks", owner, async () => Response.json(await env.WEBHOOKS.getByName(scope.durableName("webhooks")).subscriptions()));
|
|
819
|
+
routes.get("/api/webhooks/:id/deliveries", owner, async (context) => Response.json(await env.WEBHOOKS.getByName(scope.durableName("webhooks")).deliveries(context.req.param("id"))));
|
|
820
|
+
routes.delete("/api/webhooks/:id", owner, async (context) => (await env.WEBHOOKS.getByName(scope.durableName("webhooks")).unsubscribe(context.req.param("id")))
|
|
831
821
|
? new Response(null, { status: 204 })
|
|
832
822
|
: Response.json({ error: "Not found" }, { status: 404 }));
|
|
833
823
|
return routes;
|
|
@@ -889,6 +879,7 @@ export function createOysterGitWorker(root) {
|
|
|
889
879
|
hosts = new Map();
|
|
890
880
|
environments.set(env, hosts);
|
|
891
881
|
}
|
|
882
|
+
const started = Date.now();
|
|
892
883
|
let resolved;
|
|
893
884
|
try {
|
|
894
885
|
resolved = await root.admit(env, request);
|
|
@@ -907,6 +898,7 @@ export function createOysterGitWorker(root) {
|
|
|
907
898
|
const boot = await serveOysterBootCapability(request, env, scope);
|
|
908
899
|
if (boot !== null)
|
|
909
900
|
return boot;
|
|
901
|
+
const admitted = Date.now();
|
|
910
902
|
let host;
|
|
911
903
|
try {
|
|
912
904
|
host = await root.hostFor(env, scope, request);
|
|
@@ -916,6 +908,7 @@ export function createOysterGitWorker(root) {
|
|
|
916
908
|
return error.getResponse();
|
|
917
909
|
throw error;
|
|
918
910
|
}
|
|
911
|
+
const hosted = Date.now();
|
|
919
912
|
const key = `${scope.tenantId}:${scope.origin}:${host.computerId ?? ""}`;
|
|
920
913
|
let entry = hosts.get(key);
|
|
921
914
|
let cache = true;
|
|
@@ -938,7 +931,10 @@ export function createOysterGitWorker(root) {
|
|
|
938
931
|
}
|
|
939
932
|
entry.active += 1;
|
|
940
933
|
try {
|
|
941
|
-
return await entry.app.fetch(request, env, ctx)
|
|
934
|
+
return withStageTimings(await entry.app.fetch(request, env, ctx), {
|
|
935
|
+
admit: admitted - started,
|
|
936
|
+
host: hosted - admitted,
|
|
937
|
+
});
|
|
942
938
|
}
|
|
943
939
|
finally {
|
|
944
940
|
entry.active -= 1;
|
|
@@ -946,6 +942,19 @@ export function createOysterGitWorker(root) {
|
|
|
946
942
|
},
|
|
947
943
|
};
|
|
948
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* Add the root's own stages (tenant admission, host selection) to the App
|
|
947
|
+
* server's Server-Timing, so a slow request names the hop it waited on. An
|
|
948
|
+
* upgraded socket keeps its response untouched.
|
|
949
|
+
*/
|
|
950
|
+
function withStageTimings(response, stages) {
|
|
951
|
+
if (response.status === 101)
|
|
952
|
+
return response;
|
|
953
|
+
const timed = new Response(response.body, response);
|
|
954
|
+
for (const [name, duration] of Object.entries(stages))
|
|
955
|
+
timed.headers.append("Server-Timing", `${name};dur=${String(duration)}`);
|
|
956
|
+
return timed;
|
|
957
|
+
}
|
|
949
958
|
/**
|
|
950
959
|
* `host.git` for an Oyster Worker: the git door's writer projection and the
|
|
951
960
|
* admitted, installation-scoped Artifacts path with the App's push policy and
|
|
@@ -1117,8 +1126,8 @@ export async function oysterGitWorkerHost(env, options, selected, request) {
|
|
|
1117
1126
|
const base = oysterWorkerHost(env, auth, policyOptions);
|
|
1118
1127
|
const capabilities = [
|
|
1119
1128
|
...(base.capabilities ?? []),
|
|
1120
|
-
...(agents === undefined ? [] : agentCapabilities(agents
|
|
1121
|
-
...(options.capabilities?.(env, workspace
|
|
1129
|
+
...(agents === undefined ? [] : agentCapabilities(agents)),
|
|
1130
|
+
...(options.capabilities?.(env, workspace) ?? []),
|
|
1122
1131
|
];
|
|
1123
1132
|
let gitOptions = { scope };
|
|
1124
1133
|
if (options.workflowGitPass !== undefined)
|
|
@@ -1126,14 +1135,7 @@ export async function oysterGitWorkerHost(env, options, selected, request) {
|
|
|
1126
1135
|
if (options.buildGitPass !== undefined)
|
|
1127
1136
|
gitOptions = { ...gitOptions, buildGitPass: options.buildGitPass };
|
|
1128
1137
|
const git = oysterWorkerGit(env, gitOptions);
|
|
1129
|
-
const ownerRoutes =
|
|
1130
|
-
const hostOwnerRoutes = options.ownerRoutes?.(env, scope);
|
|
1131
|
-
if (hostOwnerRoutes !== undefined)
|
|
1132
|
-
ownerRoutes.route("/", hostOwnerRoutes);
|
|
1133
|
-
if (agents !== undefined) {
|
|
1134
|
-
ownerRoutes.route("/", agentApprovalRoutes(agents));
|
|
1135
|
-
ownerRoutes.route("/", agentMcpServerRoutes(agents));
|
|
1136
|
-
}
|
|
1138
|
+
const ownerRoutes = oysterGitOwnerRoutes(env, scope, options.ownerRoutes?.(env, scope), agents);
|
|
1137
1139
|
let host = {
|
|
1138
1140
|
...base,
|
|
1139
1141
|
capabilities,
|
|
@@ -1149,8 +1151,28 @@ export async function oysterGitWorkerHost(env, options, selected, request) {
|
|
|
1149
1151
|
host = { ...host, computerControl: options.computerControl(env, scope) };
|
|
1150
1152
|
if (options.homeInstallationId !== undefined)
|
|
1151
1153
|
host = { ...host, homeInstallationId: options.homeInstallationId(env) };
|
|
1154
|
+
if (options.permissionResources !== undefined)
|
|
1155
|
+
host = {
|
|
1156
|
+
...host,
|
|
1157
|
+
permissionResources: options.permissionResources(env, scope),
|
|
1158
|
+
};
|
|
1152
1159
|
return host;
|
|
1153
1160
|
}
|
|
1161
|
+
/**
|
|
1162
|
+
* A Git host's whole owner table: keys and webhooks, the root's own owner
|
|
1163
|
+
* routes, and Agent approval and MCP server custody. The host and the
|
|
1164
|
+
* authorization law both compose it here.
|
|
1165
|
+
*/
|
|
1166
|
+
export function oysterGitOwnerRoutes(env, scope, hostOwnerRoutes, agents) {
|
|
1167
|
+
const ownerRoutes = gitAdminRoutes(env, scope);
|
|
1168
|
+
if (hostOwnerRoutes !== undefined)
|
|
1169
|
+
ownerRoutes.include(hostOwnerRoutes);
|
|
1170
|
+
if (agents !== undefined) {
|
|
1171
|
+
ownerRoutes.include(agentApprovalRoutes(agents));
|
|
1172
|
+
ownerRoutes.include(agentMcpServerRoutes(agents));
|
|
1173
|
+
}
|
|
1174
|
+
return ownerRoutes;
|
|
1175
|
+
}
|
|
1154
1176
|
/**
|
|
1155
1177
|
* Authenticate against the pinned tenant. A request admitted at the Worker's
|
|
1156
1178
|
* top reuses that one admission; any other entry resolves its own.
|
package/dist/workers.d.ts
CHANGED
|
@@ -34,6 +34,11 @@ export { r2ArtifactStore, r2FileSource, ownerKeyAuth };
|
|
|
34
34
|
export type OysterBindingResult = Awaited<ReturnType<GitBinding[keyof GitBinding]>> | Schema.Json | undefined;
|
|
35
35
|
/** Verified backend custody is captured in the frame, never in a current-context field. */
|
|
36
36
|
export type OysterInvocationHost = BackgroundFrameState & Readonly<{
|
|
37
|
+
/** This frame's guest binding packets and the wall time they waited. */
|
|
38
|
+
bindingWaits: {
|
|
39
|
+
count: number;
|
|
40
|
+
ms: number;
|
|
41
|
+
};
|
|
37
42
|
artifact: Backend;
|
|
38
43
|
declaration: AppDeclaration | null;
|
|
39
44
|
/** The verified declared method; opaque backend and lifecycle frames have none. */
|
|
@@ -124,10 +129,10 @@ export declare class OysterRegistry<Env extends OysterWorkerEnv = OysterWorkerEn
|
|
|
124
129
|
readonly kind: "policy";
|
|
125
130
|
} | null;
|
|
126
131
|
readonly access: "write" | "external";
|
|
132
|
+
readonly state: "pending" | "expired" | "denied" | "applied" | "requested" | "approved" | "claimed" | "outcome_unknown";
|
|
127
133
|
readonly reason: "pending" | "expired" | "not-found" | "not-eligible" | "already-answered" | "stale-revision" | "declined" | "fenced" | "not-granted" | "read-only" | "in-flight" | "outcome-unknown" | "identity-conflict" | null;
|
|
128
134
|
readonly updatedAt: number;
|
|
129
135
|
readonly module: string;
|
|
130
|
-
readonly state: "pending" | "expired" | "denied" | "applied" | "requested" | "approved" | "claimed" | "outcome_unknown";
|
|
131
136
|
readonly recordId: string;
|
|
132
137
|
readonly authorityFence: string;
|
|
133
138
|
readonly requester: {
|
|
@@ -222,6 +227,8 @@ export declare class OysterRegistry<Env extends OysterWorkerEnv = OysterWorkerEn
|
|
|
222
227
|
get(installationId: string): Installation | null;
|
|
223
228
|
/** Durable store identity, regenerated only when the store itself is replaced. */
|
|
224
229
|
generation(): string;
|
|
230
|
+
/** Permanent audit fact for the owner Home's empty installation notice. */
|
|
231
|
+
installationResetAt(): number | null;
|
|
225
232
|
/** Native RPC returns a closed result; callers never classify a transported Error. */
|
|
226
233
|
compareAndPut(installation: Installation, expectedAuthorityVersion: number, expectedGeneration: string, tenantId?: TenantId): Promise<InstallationCommit>;
|
|
227
234
|
private writeInstallation;
|
|
@@ -387,6 +394,7 @@ export declare class OysterInstance<Env extends OysterWorkerEnv = OysterWorkerEn
|
|
|
387
394
|
/** Resolve each guest packet against its immutable frame before any host effect. */
|
|
388
395
|
bindingCall(raw: FrameBindingProps, method: string, input: readonly unknown[]): Promise<FrameBindingReply<OysterBindingResult>>;
|
|
389
396
|
private executeBindingCall;
|
|
397
|
+
private dispatchBindingCall;
|
|
390
398
|
/** Last provider-accepted ref recorded by this installation's journal. */
|
|
391
399
|
gitTip(installationId: string, repository: string, ref: string, tenantId?: TenantId): Promise<string | null>;
|
|
392
400
|
/** Join accepted fact identity and Carrier delivery in one local transaction. */
|
package/dist/workers.js
CHANGED
|
@@ -35,10 +35,12 @@ import * as Schema from "effect/Schema";
|
|
|
35
35
|
import * as Stream from "effect/Stream";
|
|
36
36
|
import { releaseSchema } from "@fungi.computer/puffball/effect";
|
|
37
37
|
import { AppInstanceError, } from "./index.js";
|
|
38
|
-
import { createCloudflareAppInstance, r2ArtifactStore, createCloudflareInvocationGate, decodeFrameBindingProps, captureFrameBindingReply, frameBindingEntrypoint, backgroundFrameMethods, } from "./cloudflare.js";
|
|
38
|
+
import { createCloudflareAppInstance, r2ArtifactStore, createCloudflareInvocationGate, edgeVerifiedObjectCache, decodeFrameBindingProps, captureFrameBindingReply, frameBindingEntrypoint, backgroundFrameMethods, } from "./cloudflare.js";
|
|
39
39
|
import { invocationFactsSchema, assertBackgroundBinding, admitBackgroundFrame, } from "./invocation.js";
|
|
40
40
|
import { deliverBackground, deliverGitPush } from "./internal/delivery.js";
|
|
41
|
-
import {
|
|
41
|
+
import { reportSlowInvocation } from "./internal/invocation-timing.js";
|
|
42
|
+
import { releaseCache } from "./internal/host-gate.js";
|
|
43
|
+
import { createOyster, } from "./host.js";
|
|
42
44
|
import { createGitPushJournal } from "./git-journal.js";
|
|
43
45
|
import { AppStorageError, } from "@fungi.computer/agar/storage";
|
|
44
46
|
import { storageFrameMethods } from "./storage-frame.js";
|
|
@@ -65,7 +67,7 @@ const approvalHomePinRow = Schema.Struct({
|
|
|
65
67
|
});
|
|
66
68
|
/** The installation registry: one SQLite-backed Durable Object per Oyster. */
|
|
67
69
|
export class OysterRegistry extends DurableObject {
|
|
68
|
-
#
|
|
70
|
+
#installationStore;
|
|
69
71
|
/** Decided once, before this version writes anything, and never revised. */
|
|
70
72
|
#installationsFromOlderOyster;
|
|
71
73
|
#approval;
|
|
@@ -148,6 +150,7 @@ export class OysterRegistry extends DurableObject {
|
|
|
148
150
|
return this.approvalHome(pin).capability(actor).setAutoApprove(input);
|
|
149
151
|
}
|
|
150
152
|
#rootData;
|
|
153
|
+
#releases = releaseCache(r2ArtifactStore(this.env.RELEASES));
|
|
151
154
|
/** Append-only App source seed receipts; a Git-capable subclass forks over these. */
|
|
152
155
|
appSourceReceipts;
|
|
153
156
|
/** Team-owned fork registrations, one App publisher record per App. */
|
|
@@ -159,7 +162,7 @@ export class OysterRegistry extends DurableObject {
|
|
|
159
162
|
this.#installationsFromOlderOyster = installationsFromOlderOyster(ctx.storage.sql);
|
|
160
163
|
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS oyster_approval_home (id INTEGER PRIMARY KEY CHECK (id = 1), tenant_id TEXT NOT NULL, origin TEXT NOT NULL) STRICT");
|
|
161
164
|
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS installations (installation_id TEXT PRIMARY KEY, record TEXT NOT NULL) STRICT");
|
|
162
|
-
this.#
|
|
165
|
+
this.#installationStore = createInstallationStoreGeneration(ctx.storage);
|
|
163
166
|
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS boot_capabilities (id TEXT PRIMARY KEY, token_digest TEXT NOT NULL, expires_at INTEGER NOT NULL, record TEXT NOT NULL) STRICT");
|
|
164
167
|
ensureAppSourceTables(ctx.storage.sql);
|
|
165
168
|
this.#installationOperations = createInstallationOperations({
|
|
@@ -196,7 +199,7 @@ export class OysterRegistry extends DurableObject {
|
|
|
196
199
|
installations: {
|
|
197
200
|
list: () => Promise.resolve(this.list()),
|
|
198
201
|
},
|
|
199
|
-
resolveRelease:
|
|
202
|
+
resolveRelease: this.#releases,
|
|
200
203
|
});
|
|
201
204
|
}
|
|
202
205
|
/** The installation owner's RPC: admit, read, load, plan or commit one
|
|
@@ -319,7 +322,7 @@ export class OysterRegistry extends DurableObject {
|
|
|
319
322
|
expected.selection.releaseId ||
|
|
320
323
|
installation.release.appId !== expected.selection.appId)))
|
|
321
324
|
throw new AppStorageError("unavailable");
|
|
322
|
-
const resolved = await
|
|
325
|
+
const resolved = await this.#releases(installation.release);
|
|
323
326
|
if (expected !== undefined &&
|
|
324
327
|
resolved.backend?.digest !== expected.artifactDigest)
|
|
325
328
|
throw new AppStorageError("unavailable");
|
|
@@ -374,7 +377,11 @@ export class OysterRegistry extends DurableObject {
|
|
|
374
377
|
}
|
|
375
378
|
/** Durable store identity, regenerated only when the store itself is replaced. */
|
|
376
379
|
generation() {
|
|
377
|
-
return this.#generation();
|
|
380
|
+
return this.#installationStore.generation();
|
|
381
|
+
}
|
|
382
|
+
/** Permanent audit fact for the owner Home's empty installation notice. */
|
|
383
|
+
installationResetAt() {
|
|
384
|
+
return this.#installationStore.resetAt();
|
|
378
385
|
}
|
|
379
386
|
/** Native RPC returns a closed result; callers never classify a transported Error. */
|
|
380
387
|
async compareAndPut(installation, expectedAuthorityVersion, expectedGeneration, tenantId = defaultTenant) {
|
|
@@ -756,13 +763,23 @@ export class OysterInstance extends DurableObject {
|
|
|
756
763
|
}
|
|
757
764
|
async executeBindingCall(raw, method, input) {
|
|
758
765
|
const props = decodeFrameBindingProps(raw);
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
766
|
+
// The packet's own frame: a closed or foreign frame id refuses.
|
|
767
|
+
const frame = this.frames.active(props.frameId);
|
|
768
|
+
if (props.instanceId !== frame.facts.instanceId)
|
|
762
769
|
throw new AppInstanceError("revoked");
|
|
770
|
+
const started = Date.now();
|
|
771
|
+
try {
|
|
772
|
+
return await this.dispatchBindingCall(frame, props, method, input);
|
|
773
|
+
}
|
|
774
|
+
finally {
|
|
775
|
+
frame.host.bindingWaits.count += 1;
|
|
776
|
+
frame.host.bindingWaits.ms += Date.now() - started;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
async dispatchBindingCall(frame, props, method, input) {
|
|
763
780
|
assertBackgroundBinding(frame, props.binding);
|
|
764
781
|
// A guest packet is its own entry: exactly one registry read re-admits it.
|
|
765
|
-
await this.assertCurrent(frame);
|
|
782
|
+
const current = await this.assertCurrent(frame);
|
|
766
783
|
const row = this.bindings[props.binding];
|
|
767
784
|
const descriptor = row?.methods[method];
|
|
768
785
|
// Operations declared by the verified Release; absent bindings grant nothing.
|
|
@@ -782,12 +799,16 @@ export class OysterInstance extends DurableObject {
|
|
|
782
799
|
throw new AppInstanceError("binding_unavailable");
|
|
783
800
|
}
|
|
784
801
|
const args = descriptor.decodeArgs(input);
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
.every((operation) => granted.includes(operation)))
|
|
802
|
+
const required = descriptor.requiredOperations(args);
|
|
803
|
+
if (!required.every((operation) => granted.includes(operation)))
|
|
788
804
|
throw new AppInstanceError("binding_not_granted");
|
|
805
|
+
if (descriptor.requestedResource !== undefined) {
|
|
806
|
+
const selected = approvedBindingGrants(current, frame.host.declaration ?? emptyAppDeclaration, descriptor.requestedResource(args)).find((grant) => grant.binding === props.binding)?.operations ?? [];
|
|
807
|
+
if (!required.every((operation) => selected.includes(operation)))
|
|
808
|
+
throw new AppInstanceError("binding_not_granted");
|
|
809
|
+
}
|
|
789
810
|
const result = await row.dispatch(this.#bindingContext, frame, props, method, args);
|
|
790
|
-
if (this.frames.active() !== frame)
|
|
811
|
+
if (this.frames.active(props.frameId) !== frame)
|
|
791
812
|
throw new AppInstanceError("revoked");
|
|
792
813
|
assertBackgroundBinding(frame, props.binding);
|
|
793
814
|
return descriptor.decodeResult(result);
|
|
@@ -846,8 +867,10 @@ export class OysterInstance extends DurableObject {
|
|
|
846
867
|
.find((currentGrant) => currentGrant.binding === grant.binding)
|
|
847
868
|
?.operations.includes(operation) !== true)))
|
|
848
869
|
throw new AppInstanceError("revoked");
|
|
870
|
+
return current;
|
|
849
871
|
}
|
|
850
872
|
async withFrame(raw, limits, work, tenantId = defaultTenant, methodName) {
|
|
873
|
+
const entered = Date.now();
|
|
851
874
|
this.requireTenant(tenantId);
|
|
852
875
|
const facts = Schema.decodeUnknownSync(invocationFactsSchema, {
|
|
853
876
|
onExcessProperty: "error",
|
|
@@ -858,6 +881,7 @@ export class OysterInstance extends DurableObject {
|
|
|
858
881
|
throw new AppInstanceError("instance_identity_mismatch");
|
|
859
882
|
const release = this.release(facts.selection.releaseId);
|
|
860
883
|
const resolved = await this.backends(release);
|
|
884
|
+
const verified = Date.now();
|
|
861
885
|
if (resolved?.digest !== facts.artifactDigest)
|
|
862
886
|
throw new AppInstanceError("invalid_invocation_facts");
|
|
863
887
|
if (facts.subject.kind === "call" &&
|
|
@@ -893,6 +917,7 @@ export class OysterInstance extends DurableObject {
|
|
|
893
917
|
};
|
|
894
918
|
}
|
|
895
919
|
let host = {
|
|
920
|
+
bindingWaits: { count: 0, ms: 0 },
|
|
896
921
|
artifact: { release, ...resolved },
|
|
897
922
|
declaration: resolved.declaration,
|
|
898
923
|
origin: "unchecked",
|
|
@@ -902,7 +927,20 @@ export class OysterInstance extends DurableObject {
|
|
|
902
927
|
// Facts carry the authority admitted at the request's top. The Instance
|
|
903
928
|
// checks them against the owner state the host applied to it, never by
|
|
904
929
|
// calling back up to the registry.
|
|
905
|
-
|
|
930
|
+
let admitted = verified;
|
|
931
|
+
try {
|
|
932
|
+
return await this.frames.run(scoped, host, (frame) => {
|
|
933
|
+
admitted = Date.now();
|
|
934
|
+
return work(this.runtimeFor(limits), frame);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
finally {
|
|
938
|
+
reportSlowInvocation(methodName ?? facts.subject.kind, host, {
|
|
939
|
+
entered,
|
|
940
|
+
verified,
|
|
941
|
+
admitted,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
906
944
|
}
|
|
907
945
|
release(releaseId) {
|
|
908
946
|
const row = this.ctx.storage.sql
|
|
@@ -1102,10 +1140,7 @@ export function oysterWorkerHost(env, auth, options = {}) {
|
|
|
1102
1140
|
},
|
|
1103
1141
|
auth,
|
|
1104
1142
|
workbench: {
|
|
1105
|
-
directory:
|
|
1106
|
-
await installations.list();
|
|
1107
|
-
return registry().directory();
|
|
1108
|
-
},
|
|
1143
|
+
directory: () => registry().directory(),
|
|
1109
1144
|
storage: (installationId, ownerId) => ({
|
|
1110
1145
|
read: (key) => registry().storageRead(installationId, ownerId, key),
|
|
1111
1146
|
write: (input) => registry().storageWrite(installationId, ownerId, input),
|
|
@@ -1115,6 +1150,10 @@ export function oysterWorkerHost(env, auth, options = {}) {
|
|
|
1115
1150
|
},
|
|
1116
1151
|
guestLimits: guestLimits(options.guestLimits),
|
|
1117
1152
|
artifacts,
|
|
1153
|
+
// Workers and workerd keep a zone cache; a runtime without one reads R2.
|
|
1154
|
+
...("caches" in globalThis && {
|
|
1155
|
+
uiCache: edgeVerifiedObjectCache(caches.default, scope.origin),
|
|
1156
|
+
}),
|
|
1118
1157
|
bootUi: oysterBootUi(env, scope),
|
|
1119
1158
|
// File-only hosts may keep workspace files beside their Release artifacts.
|
|
1120
1159
|
capabilities: [
|
package/dist/workflows.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare const agentWorkflowOperationSchema: z.ZodEnum<{
|
|
|
10
10
|
catalog: "catalog";
|
|
11
11
|
armSchedule: "armSchedule";
|
|
12
12
|
disarmSchedule: "disarmSchedule";
|
|
13
|
+
pauseSchedule: "pauseSchedule";
|
|
13
14
|
schedules: "schedules";
|
|
14
15
|
armEventTrigger: "armEventTrigger";
|
|
15
16
|
disarmEventTrigger: "disarmEventTrigger";
|
|
@@ -26,6 +27,7 @@ export declare const agentAppWorkflowRequestSchema: z.ZodObject<{
|
|
|
26
27
|
catalog: "catalog";
|
|
27
28
|
armSchedule: "armSchedule";
|
|
28
29
|
disarmSchedule: "disarmSchedule";
|
|
30
|
+
pauseSchedule: "pauseSchedule";
|
|
29
31
|
schedules: "schedules";
|
|
30
32
|
armEventTrigger: "armEventTrigger";
|
|
31
33
|
disarmEventTrigger: "disarmEventTrigger";
|
package/dist/workflows.js
CHANGED
package/docs/api/README.md
CHANGED
|
@@ -36,6 +36,13 @@
|
|
|
36
36
|
| `@fungi.computer/oyster/git` | `EventSink`, `createGitDoor`, `signStandardWebhook` | The EVENTS binding contract, the git smart-HTTP door and Standard Webhooks signing. `GitBinding` comes from `@fungi.computer/agar/git`. |
|
|
37
37
|
| `@fungi.computer/oyster/workers-git` | `oysterGitRoot`, `createOysterGitWorker`, `ownerOrKeyTenancy`, `oysterWorkerGit`, `gitAdminRoutes` | One Git root whose `hostFor` serves HTTP, MCP and Instances, or `host.git` and key/webhook routes for your own host. |
|
|
38
38
|
|
|
39
|
+
Standalone Oyster owners can `POST /agents/:agentId/archive` and
|
|
40
|
+
`POST /agents/:agentId/unarchive`. `GET /agents` lists active Agents;
|
|
41
|
+
`GET /agents?archived=true` lists only archived Agents. Archive preserves
|
|
42
|
+
existing Sessions and reads but refuses new Shiitake work with
|
|
43
|
+
`contract/archived`. It pauses Almanac and Loop schedules; restore resumes only
|
|
44
|
+
schedules paused by archive. The generated Agent API is in `docs/api/agents.md`.
|
|
45
|
+
|
|
39
46
|
The host owns admission: it decides who may apply owner state and which
|
|
40
47
|
selection a request carries. Oyster owns what happens to one Instance once the
|
|
41
48
|
host has that authority: owner-state replay and conflict rules, Release
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<!-- Generated by Botanical API docs (TypeDoc + typedoc-plugin-markdown). Do not edit. -->
|
|
2
|
+
|
|
3
|
+
[**@fungi.computer/oyster**](README.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
[@fungi.computer/oyster](modules.md) / agent-schedules
|
|
8
|
+
|
|
9
|
+
# agent-schedules
|
|
10
|
+
|
|
11
|
+
## Type Aliases
|
|
12
|
+
|
|
13
|
+
### AppAgentScheduleListInput
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
type AppAgentScheduleListInput = z.input<
|
|
17
|
+
typeof appAgentScheduleListInputSchema
|
|
18
|
+
>;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
A single bounded native read, not an all-Agent inventory grant.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### AgentScheduleTarget
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
type AgentScheduleTarget = z.output<typeof agentScheduleTargetSchema>;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Identity to admit before acquiring the corresponding Agent owner.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
### AgentSchedulePage
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
type AgentSchedulePage = typeof agentSchedulePageSchema.Type;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
One Agent's page, with no directory fan-out or availability policy.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### AgentScheduleControlReceipt
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
type AgentScheduleControlReceipt =
|
|
49
|
+
typeof agentScheduleControlReceiptSchema.Type;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Content-free acknowledgement of the native owner's completed transition.
|
|
53
|
+
|
|
54
|
+
## Variables
|
|
55
|
+
|
|
56
|
+
### appAgentScheduleListInputSchema
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const appAgentScheduleListInputSchema: ZodObject<
|
|
60
|
+
{
|
|
61
|
+
agentId: ZodString;
|
|
62
|
+
after: ZodOptional<
|
|
63
|
+
ZodObject<
|
|
64
|
+
{
|
|
65
|
+
agentId: ZodString;
|
|
66
|
+
kind: ZodUnion<readonly [ZodLiteral<"almanac">, ZodLiteral<"loop">]>;
|
|
67
|
+
after: ZodOptional<ZodString>;
|
|
68
|
+
},
|
|
69
|
+
$strict
|
|
70
|
+
>
|
|
71
|
+
>;
|
|
72
|
+
limit: ZodDefault<ZodNumber>;
|
|
73
|
+
},
|
|
74
|
+
$strict
|
|
75
|
+
>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The App selects one Agent; its host must admit that target before opening it.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
### agentScheduleBindingArgs
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const agentScheduleBindingArgs: object;
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Both hosts decode the ordinary AGENTS packet with the native owner's grammar.
|
|
89
|
+
|
|
90
|
+
#### Type Declaration
|
|
91
|
+
|
|
92
|
+
| Name | Type | Description |
|
|
93
|
+
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
94
|
+
| <a id="property-schedules"></a> `schedules` | `ZodTuple`\<\[`ZodObject`\<\{ `agentId`: `ZodString`; `after`: `ZodOptional`\<`ZodObject`\<\{ `agentId`: `ZodString`; `kind`: `ZodUnion`\<readonly \[`ZodLiteral`\<...\>, `ZodLiteral`\<...\>\]\>; `after`: `ZodOptional`\<`ZodString`\>; \}, `$strict`\>\>; `limit`: `ZodDefault`\<`ZodNumber`\>; \}, `$strict`\>\], `null`\> | One bounded content-bearing read, admitted independently of manage. |
|
|
95
|
+
| <a id="property-pauseschedule"></a> `pauseSchedule` | `ZodTuple`\<\[`ZodDiscriminatedUnion`\<\[`ZodObject`\<\{ `kind`: `ZodLiteral`\<`"almanac"`\>; `agentId`: `ZodString`; `scheduleId`: `ZodString` & `ZodType`\<`string`, `string`, `$ZodTypeInternals`\<`string`, `string`\>\>; \}, `$strict`\>, `ZodObject`\<\{ `kind`: `ZodLiteral`\<`"loop"`\>; `agentId`: `ZodString`; `sessionId`: `ZodString` & `ZodType`\<`string`, `string`, `$ZodTypeInternals`\<`string`, `string`\>\>; `loopId`: `ZodString` & `ZodType`\<`string`, `string`, `$ZodTypeInternals`\<`string`, `string`\>\>; \}, `$strict`\>\], `"kind"`\>\], `null`\> | Pause the exact listed schedule, retaining future resume and accepted work. |
|
|
96
|
+
| <a id="property-deleteschedule"></a> `deleteSchedule` | `ZodTuple`\<\[`ZodDiscriminatedUnion`\<\[`ZodObject`\<\{ `kind`: `ZodLiteral`\<`"almanac"`\>; `agentId`: `ZodString`; `scheduleId`: `ZodString` & `ZodType`\<`string`, `string`, `$ZodTypeInternals`\<`string`, `string`\>\>; \}, `$strict`\>, `ZodObject`\<\{ `kind`: `ZodLiteral`\<`"loop"`\>; `agentId`: `ZodString`; `sessionId`: `ZodString` & `ZodType`\<`string`, `string`, `$ZodTypeInternals`\<`string`, `string`\>\>; `loopId`: `ZodString` & `ZodType`\<`string`, `string`, `$ZodTypeInternals`\<`string`, `string`\>\>; \}, `$strict`\>\], `"kind"`\>\], `null`\> | Cancel future occurrences of the exact identity without erasing history. |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### agentSchedulePageSchema
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const agentSchedulePageSchema: Schema.Struct<{
|
|
104
|
+
items: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
105
|
+
agentId: Schema.String;
|
|
106
|
+
kind: Schema.Literal<"almanac">;
|
|
107
|
+
schedule: Schema.Struct<{
|
|
108
|
+
id: Schema.String;
|
|
109
|
+
sessionId: Schema.String;
|
|
110
|
+
prompt: Schema.String;
|
|
111
|
+
cron: Schema.NullOr<...>;
|
|
112
|
+
nextDueAt: Schema.Int;
|
|
113
|
+
state: Schema.Literals<...>;
|
|
114
|
+
lastCommandId: Schema.NullOr<...>;
|
|
115
|
+
lastError: Schema.NullOr<...>;
|
|
116
|
+
attempt: Schema.Int;
|
|
117
|
+
}>;
|
|
118
|
+
}>, Schema.Struct<{
|
|
119
|
+
agentId: Schema.String;
|
|
120
|
+
kind: Schema.Literal<"loop">;
|
|
121
|
+
schedule: Schema.Struct<{
|
|
122
|
+
sessionId: Schema.String;
|
|
123
|
+
loopId: Schema.String;
|
|
124
|
+
origin: Schema.Literals<...>;
|
|
125
|
+
expiresAt: Schema.NullOr<...>;
|
|
126
|
+
prompt: Schema.String;
|
|
127
|
+
mode: Schema.Literals<...>;
|
|
128
|
+
cron: Schema.NullOr<...>;
|
|
129
|
+
state: Schema.Literals<...>;
|
|
130
|
+
commandId: Schema.String;
|
|
131
|
+
reason: Schema.NullOr<...>;
|
|
132
|
+
nextDueAt: Schema.NullOr<...>;
|
|
133
|
+
lastError: Schema.NullOr<...>;
|
|
134
|
+
consecutiveFailures: Schema.Int;
|
|
135
|
+
}>;
|
|
136
|
+
}>]>>;
|
|
137
|
+
next: Schema.NullOr<Schema.Struct<{
|
|
138
|
+
agentId: Schema.String;
|
|
139
|
+
kind: Schema.Literals<readonly ["almanac", "loop"]>;
|
|
140
|
+
after: Schema.optionalKey<Schema.String>;
|
|
141
|
+
}>>;
|
|
142
|
+
}>;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
A bounded content-bearing page; owner failures reject rather than look empty.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### agentScheduleControlReceiptSchema
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const agentScheduleControlReceiptSchema: Schema.Union<
|
|
153
|
+
readonly [
|
|
154
|
+
Schema.Struct<{
|
|
155
|
+
agentId: Schema.String;
|
|
156
|
+
kind: Schema.Literal<"almanac">;
|
|
157
|
+
scheduleId: Schema.String;
|
|
158
|
+
state: Schema.Literals<
|
|
159
|
+
readonly ["active", "paused", "cancelled", "finished"]
|
|
160
|
+
>;
|
|
161
|
+
}>,
|
|
162
|
+
Schema.Struct<{
|
|
163
|
+
agentId: Schema.String;
|
|
164
|
+
kind: Schema.Literal<"loop">;
|
|
165
|
+
sessionId: Schema.String;
|
|
166
|
+
loopId: Schema.String;
|
|
167
|
+
state: Schema.Literals<
|
|
168
|
+
readonly ["active", "paused", "finished", "failed", "cancelled"]
|
|
169
|
+
>;
|
|
170
|
+
}>,
|
|
171
|
+
]
|
|
172
|
+
>;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Manage-only result: exact target and native state, never prompt or history.
|