@fungi.computer/oyster 0.1.0 → 0.2.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/dist/agent-avatars.d.ts +103 -0
- package/dist/agent-avatars.js +66 -0
- package/dist/agents.d.ts +12 -2
- package/dist/agents.js +39 -2
- package/dist/apps/gate.d.ts +7 -7
- package/dist/apps/gate.js +6 -6
- package/dist/apps/installation.d.ts +35 -35
- package/dist/apps/installation.js +20 -19
- package/dist/build/candidate-cli.mjs +55 -5
- package/dist/build-egress.js +4 -4
- package/dist/cloudflare.js +4 -4
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.d.ts +1 -1
- package/dist/frame-binding-wire.js +1 -1
- package/dist/git-journal.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/installation-consent.d.ts +3 -1
- package/dist/installation-consent.js +2 -0
- package/dist/page/mount.d.ts +4 -8
- package/dist/page-script.js +4 -4
- package/dist/shell/app-directory.d.ts +37 -37
- package/dist/shell/app-directory.js +15 -18
- package/dist/shell/window-manager.d.ts +5 -1
- package/dist/shell/window-manager.js +21 -2
- package/dist/shell/workbench-directory.d.ts +9 -9
- package/dist/shell/workbench-host-types.d.ts +1 -1
- package/dist/shell/workbench-host.js +5 -0
- package/dist/shell/workbench-model.d.ts +3 -1
- package/dist/standalone/agents.d.ts +11 -4
- package/dist/standalone/agents.js +44 -5
- package/dist/standalone/app-builds-binding.js +6 -6
- package/dist/standalone/computer.js +3 -3
- package/dist/standalone/preview.js +2 -2
- package/dist/standalone/worker.js +2 -2
- package/dist/standalone/workflow-binding.d.ts +10 -10
- package/dist/standalone/workflow-binding.js +2 -2
- package/dist/standalone/workflow-host.d.ts +37 -13
- package/dist/standalone/workflow-host.js +36 -9
- package/dist/standalone/workflow-trigger-host.d.ts +24 -2
- package/dist/standalone/workflows.d.ts +67 -11
- package/dist/workers-git.js +1 -4
- package/package.json +19 -17
|
@@ -19,6 +19,7 @@ import * as Schema from "effect/Schema";
|
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
import {} from "../cloudflare.js";
|
|
21
21
|
import { AppInstanceError } from "../index.js";
|
|
22
|
+
import { createAgentAvatarStore, } from "../agent-avatars.js";
|
|
22
23
|
import { defineIdFamily } from "@fungi.computer/cairn";
|
|
23
24
|
import { agentIds, createAgentDirectory, createFleetSessions, DirectoryError, } from "@fungi.computer/shiitake/fleet";
|
|
24
25
|
import { appSurfaceCache, oysterAppMethods, } from "../mcp.js";
|
|
@@ -356,6 +357,7 @@ const counterRow = Schema.Struct({ value: Schema.Number });
|
|
|
356
357
|
/** Names this Oyster's agents and remembers which Sessions the member opened. */
|
|
357
358
|
export class AgentDirectory extends DurableObject {
|
|
358
359
|
directory;
|
|
360
|
+
avatars;
|
|
359
361
|
composedTenancy;
|
|
360
362
|
grant() {
|
|
361
363
|
return {
|
|
@@ -368,6 +370,7 @@ export class AgentDirectory extends DurableObject {
|
|
|
368
370
|
constructor(ctx, env) {
|
|
369
371
|
super(ctx, env);
|
|
370
372
|
this.directory = createAgentDirectory(ctx.storage.sql);
|
|
373
|
+
this.avatars = createAgentAvatarStore(ctx.storage.sql);
|
|
371
374
|
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS visits (agent_id TEXT NOT NULL, session_id TEXT NOT NULL, visited_at INTEGER NOT NULL, PRIMARY KEY (agent_id, session_id)) STRICT");
|
|
372
375
|
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS counters (name TEXT PRIMARY KEY, value INTEGER NOT NULL) STRICT");
|
|
373
376
|
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS app_tool_policies (agent_id TEXT PRIMARY KEY, policy TEXT NOT NULL) STRICT");
|
|
@@ -376,7 +379,11 @@ export class AgentDirectory extends DurableObject {
|
|
|
376
379
|
return this.directory
|
|
377
380
|
.list(this.grant())
|
|
378
381
|
.toReversed()
|
|
379
|
-
.map(({ agentId, name }) => ({
|
|
382
|
+
.map(({ agentId, name }) => ({
|
|
383
|
+
agentId,
|
|
384
|
+
name,
|
|
385
|
+
avatar: this.avatars.read(agentId),
|
|
386
|
+
}));
|
|
380
387
|
}
|
|
381
388
|
has(agentId) {
|
|
382
389
|
const id = agentIds.agent.parse(agentId);
|
|
@@ -399,7 +406,11 @@ export class AgentDirectory extends DurableObject {
|
|
|
399
406
|
name,
|
|
400
407
|
});
|
|
401
408
|
this.bump();
|
|
402
|
-
return {
|
|
409
|
+
return {
|
|
410
|
+
agentId: renamed.agentId,
|
|
411
|
+
name: renamed.name,
|
|
412
|
+
avatar: this.avatars.read(renamed.agentId),
|
|
413
|
+
};
|
|
403
414
|
}
|
|
404
415
|
catch (cause) {
|
|
405
416
|
if (cause instanceof DirectoryError && cause.code === "not_found")
|
|
@@ -410,7 +421,27 @@ export class AgentDirectory extends DurableObject {
|
|
|
410
421
|
}
|
|
411
422
|
});
|
|
412
423
|
}
|
|
413
|
-
|
|
424
|
+
/** Owner-only avatar change; the Agent wears it in every App and list. */
|
|
425
|
+
setAvatar(agentId, avatar, principal) {
|
|
426
|
+
if (!oysterPrincipalIsOwner(principal))
|
|
427
|
+
return { error: "not_granted" };
|
|
428
|
+
const id = agentIds.agent.parse(agentId);
|
|
429
|
+
if (!id.ok)
|
|
430
|
+
return { error: "not_found" };
|
|
431
|
+
return this.ctx.storage.transactionSync(() => {
|
|
432
|
+
const agent = this.directory.read(this.grant(), id.value);
|
|
433
|
+
if (agent === null)
|
|
434
|
+
return { error: "not_found" };
|
|
435
|
+
this.avatars.write(agent.agentId, avatar);
|
|
436
|
+
this.bump();
|
|
437
|
+
return {
|
|
438
|
+
agentId: agent.agentId,
|
|
439
|
+
name: agent.name,
|
|
440
|
+
avatar: this.avatars.read(agent.agentId),
|
|
441
|
+
};
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
create(name, requestId, principal, actor, avatar) {
|
|
414
445
|
if (!oysterPrincipalIsOwner(principal))
|
|
415
446
|
throw new Error("Only the authenticated owner creates Agents");
|
|
416
447
|
return this.ctx.storage.transactionSync(() => {
|
|
@@ -419,6 +450,8 @@ export class AgentDirectory extends DurableObject {
|
|
|
419
450
|
provenance: { actor: oysterPrincipalId(principal) },
|
|
420
451
|
}, { name, requestId, configuration: "none" });
|
|
421
452
|
if (fact !== null) {
|
|
453
|
+
if (avatar !== undefined)
|
|
454
|
+
this.avatars.write(agent.agentId, avatar);
|
|
422
455
|
this.bump();
|
|
423
456
|
this.writePolicy(agent.agentId, {
|
|
424
457
|
status: "pending",
|
|
@@ -431,6 +464,7 @@ export class AgentDirectory extends DurableObject {
|
|
|
431
464
|
return {
|
|
432
465
|
agentId: agent.agentId,
|
|
433
466
|
name: agent.name,
|
|
467
|
+
avatar: this.avatars.read(agent.agentId),
|
|
434
468
|
policyRevision: stored === null
|
|
435
469
|
? null
|
|
436
470
|
: stored.status === "pending"
|
|
@@ -610,12 +644,13 @@ export function standaloneAgents(env, scope) {
|
|
|
610
644
|
const toAgent = (entry) => ({
|
|
611
645
|
agentId: entry.agentId,
|
|
612
646
|
name: entry.name,
|
|
647
|
+
avatar: entry.avatar,
|
|
613
648
|
sessionOperations: everyOperation,
|
|
614
649
|
});
|
|
615
650
|
return {
|
|
616
651
|
list: async () => (await directory().list()).map(toAgent),
|
|
617
|
-
create: async (name, requestId, principal, actor) => {
|
|
618
|
-
const entry = await directory().create(name, requestId, principal, actor);
|
|
652
|
+
create: async (name, requestId, principal, actor, avatar) => {
|
|
653
|
+
const entry = await directory().create(name, requestId, principal, actor, avatar);
|
|
619
654
|
await env.AGENTS.getByName(entry.agentId).usePolicyDirectory({
|
|
620
655
|
directoryName,
|
|
621
656
|
tenantId: scope?.tenantId ?? defaultTenant,
|
|
@@ -632,6 +667,10 @@ export function standaloneAgents(env, scope) {
|
|
|
632
667
|
const result = await directory().rename(agentId, name, principal);
|
|
633
668
|
return "error" in result ? result : toAgent(result);
|
|
634
669
|
},
|
|
670
|
+
setAvatar: async (agentId, avatar, principal) => {
|
|
671
|
+
const result = await directory().setAvatar(agentId, avatar, principal);
|
|
672
|
+
return "error" in result ? result : toAgent(result);
|
|
673
|
+
},
|
|
635
674
|
policyStatus: async (agentId) => directory().policyStatus(agentId),
|
|
636
675
|
appToolPolicy: (actor) => directory().appToolPolicy(actor, scope?.tenantId ?? defaultTenant),
|
|
637
676
|
policyRevision: (agentId) => directory().policyRevision(agentId),
|
|
@@ -12,17 +12,17 @@ import { installationIdSchema } from "../apps/index.js";
|
|
|
12
12
|
import { appIdSchema, releaseSchema } from "@fungi.computer/puffball/effect";
|
|
13
13
|
/** The calling tenant's registry owns its App builds. */
|
|
14
14
|
const buildRegistry = (env, scope) => env.REGISTRY.getByName(scope.durableName("registry"));
|
|
15
|
-
const buildInput = z.strictObject({
|
|
15
|
+
const buildInput = z.lazy(() => z.strictObject({
|
|
16
16
|
appId: z.string().min(1).max(256),
|
|
17
17
|
sourceRevision: z.string().min(1).max(256),
|
|
18
|
-
});
|
|
19
|
-
const buildStatusInput = z.strictObject({
|
|
18
|
+
}));
|
|
19
|
+
const buildStatusInput = z.lazy(() => z.strictObject({
|
|
20
20
|
jobId: z.string().min(1).max(256),
|
|
21
|
-
});
|
|
22
|
-
const selectInput = z.strictObject({
|
|
21
|
+
}));
|
|
22
|
+
const selectInput = z.lazy(() => z.strictObject({
|
|
23
23
|
installationId: z.string().min(1).max(256),
|
|
24
24
|
release: z.unknown(),
|
|
25
|
-
});
|
|
25
|
+
}));
|
|
26
26
|
/** The Instance gate and native guest entrypoint share these argument codecs. */
|
|
27
27
|
export const appsFrameMethods = {
|
|
28
28
|
APPS: {
|
|
@@ -18,7 +18,7 @@ import { computerSlots } from "../computer.js";
|
|
|
18
18
|
import { oysterPrincipalId, oysterPrincipalIsOwner, } from "../host.js";
|
|
19
19
|
import { keyAllowsComputerUse } from "../keys.js";
|
|
20
20
|
import { oysterAppBuildTimeoutMs } from "../app-builds.js";
|
|
21
|
-
const missingFile = z.object({ code: z.literal("ENOENT") });
|
|
21
|
+
const missingFile = z.lazy(() => z.object({ code: z.literal("ENOENT") }));
|
|
22
22
|
const workspaceId = computerGrantSchema.shape.computerId;
|
|
23
23
|
const command = computerExecutionRequestSchema({ maximumTimeMs: 45_000 }).omit({
|
|
24
24
|
requestId: true,
|
|
@@ -39,10 +39,10 @@ const processStart = processStartInputSchema.omit({
|
|
|
39
39
|
const processStartHttpBody = processStartInputSchema.omit({
|
|
40
40
|
requestScope: true,
|
|
41
41
|
});
|
|
42
|
-
const processStartReply = z.union([
|
|
42
|
+
const processStartReply = z.lazy(() => z.union([
|
|
43
43
|
computerProcessSchema,
|
|
44
44
|
z.strictObject({ error: z.literal("conflict") }),
|
|
45
|
-
]);
|
|
45
|
+
]));
|
|
46
46
|
const bindingWrite = writeInputSchema.extend({
|
|
47
47
|
expectedContent: replaceInputSchema.shape.expectedContent.optional(),
|
|
48
48
|
});
|
|
@@ -10,7 +10,7 @@ export const previewSelectorSchema = Schema.Struct({
|
|
|
10
10
|
selection: selectionSchema,
|
|
11
11
|
artifactDigest: sha256DigestSchema,
|
|
12
12
|
});
|
|
13
|
-
const originSchema = z
|
|
13
|
+
const originSchema = z.lazy(() => z
|
|
14
14
|
.url()
|
|
15
15
|
.transform((input) => new URL(input))
|
|
16
16
|
.refine((url) => url.protocol === "https:" &&
|
|
@@ -18,7 +18,7 @@ const originSchema = z
|
|
|
18
18
|
!url.password &&
|
|
19
19
|
url.pathname === "/" &&
|
|
20
20
|
!url.search &&
|
|
21
|
-
!url.hash);
|
|
21
|
+
!url.hash));
|
|
22
22
|
export function previewOrigins(env) {
|
|
23
23
|
if (env.OYSTER_PREVIEW_ORIGIN === undefined)
|
|
24
24
|
return null;
|
|
@@ -38,13 +38,13 @@ export { OysterKeys } from "../workers-git.js";
|
|
|
38
38
|
export { StandaloneEventDelivery as OysterWebhooks } from "./instance.js";
|
|
39
39
|
export { AgentDirectory } from "./agents.js";
|
|
40
40
|
/** After Oyster's bearer prefix: `<library>.<pass>`, as the library minted it. */
|
|
41
|
-
const workflowGitPassToken = z
|
|
41
|
+
const workflowGitPassToken = z.lazy(() => z
|
|
42
42
|
.string()
|
|
43
43
|
.regex(/^([a-zA-Z0-9_-]{1,256})\.([0-9a-f]{64})$/u)
|
|
44
44
|
.transform((token) => {
|
|
45
45
|
const [libraryId = "", passId = ""] = token.split(".");
|
|
46
46
|
return [libraryId, passId];
|
|
47
|
-
});
|
|
47
|
+
}));
|
|
48
48
|
/** The one composition root: every door reaches a tenant's host through `hostFor`. */
|
|
49
49
|
const oysterRoot = oysterGitRoot({
|
|
50
50
|
tenancy: (env) => ({
|
|
@@ -30,9 +30,9 @@ export declare const triggerArmTargetSchema: z.ZodObject<{
|
|
|
30
30
|
}, string, z.core.$ZodTypeInternals<string & {
|
|
31
31
|
readonly appReleaseKind: "Sha256Digest";
|
|
32
32
|
}, string>>;
|
|
33
|
-
bindings: z.ZodRecord<z.ZodString
|
|
33
|
+
bindings: z.ZodRecord<z.ZodLazy<z.ZodString>, z.ZodUnion<readonly [z.ZodString, z.ZodLazy<z.ZodObject<{
|
|
34
34
|
kind: z.ZodLiteral<"team-source">;
|
|
35
|
-
}, z.core.$strict
|
|
35
|
+
}, z.core.$strict>>]>>;
|
|
36
36
|
workflowId: z.ZodString;
|
|
37
37
|
delivery: z.ZodOptional<z.ZodCustom<{
|
|
38
38
|
readonly agentId: string;
|
|
@@ -69,12 +69,12 @@ export declare const workflowFrameMethods: {
|
|
|
69
69
|
decodeArgs: ((input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly [{
|
|
70
70
|
readonly installationId?: string | undefined;
|
|
71
71
|
}]) | ((input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly [{
|
|
72
|
-
readonly bindings: Record<string, string | {
|
|
73
|
-
kind: "team-source";
|
|
74
|
-
}>;
|
|
75
72
|
readonly input: {
|
|
76
73
|
readonly [x: string]: S.Json;
|
|
77
74
|
};
|
|
75
|
+
readonly bindings: Record<string, string | {
|
|
76
|
+
kind: "team-source";
|
|
77
|
+
}>;
|
|
78
78
|
readonly requestId: string;
|
|
79
79
|
readonly definition: unknown;
|
|
80
80
|
readonly delivery?: {
|
|
@@ -83,12 +83,12 @@ export declare const workflowFrameMethods: {
|
|
|
83
83
|
} | undefined;
|
|
84
84
|
readonly published?: undefined;
|
|
85
85
|
} | {
|
|
86
|
-
readonly bindings: Record<string, string | {
|
|
87
|
-
kind: "team-source";
|
|
88
|
-
}>;
|
|
89
86
|
readonly input: {
|
|
90
87
|
readonly [x: string]: S.Json;
|
|
91
88
|
};
|
|
89
|
+
readonly bindings: Record<string, string | {
|
|
90
|
+
kind: "team-source";
|
|
91
|
+
}>;
|
|
92
92
|
readonly requestId: string;
|
|
93
93
|
readonly published: {
|
|
94
94
|
workflowId: string;
|
|
@@ -169,7 +169,7 @@ export declare function workflowEventRecipients(installations: Readonly<{
|
|
|
169
169
|
*/
|
|
170
170
|
export declare function deliverWorkflowEvent(env: WorkflowEventEnv, libraryId: string, event: HostRecipientEvent): Promise<void>;
|
|
171
171
|
/** What a Workflow node frame attests when it asks for its Git step's pass. */
|
|
172
|
-
export declare const workflowGitPassRequestSchema: z.ZodObject<{
|
|
172
|
+
export declare const workflowGitPassRequestSchema: z.ZodLazy<z.ZodObject<{
|
|
173
173
|
installationId: z.ZodString;
|
|
174
174
|
idempotencyKey: z.ZodString;
|
|
175
175
|
workflow: import("zod/mini").ZodMiniObject<{
|
|
@@ -189,7 +189,7 @@ export declare const workflowGitPassRequestSchema: z.ZodObject<{
|
|
|
189
189
|
}, z.core.$strip>;
|
|
190
190
|
memberId: z.ZodString;
|
|
191
191
|
computerId: z.ZodString;
|
|
192
|
-
}, z.core.$strict
|
|
192
|
+
}, z.core.$strict>>;
|
|
193
193
|
export type WorkflowGitPassRequest = z.output<typeof workflowGitPassRequestSchema>;
|
|
194
194
|
/** The library run owners a node frame's pass request may reach. */
|
|
195
195
|
export type WorkflowGitPassEnv = Readonly<{
|
|
@@ -99,13 +99,13 @@ export async function deliverWorkflowEvent(env, libraryId, event) {
|
|
|
99
99
|
await env.WORKFLOW_RUNS.getByName(libraryId).deliverEvent(fact);
|
|
100
100
|
}
|
|
101
101
|
/** What a Workflow node frame attests when it asks for its Git step's pass. */
|
|
102
|
-
export const workflowGitPassRequestSchema = z.strictObject({
|
|
102
|
+
export const workflowGitPassRequestSchema = z.lazy(() => z.strictObject({
|
|
103
103
|
installationId: z.string().min(1).max(256),
|
|
104
104
|
idempotencyKey: z.string().min(1).max(128),
|
|
105
105
|
workflow: workflowAttestationSchema,
|
|
106
106
|
memberId: z.string().min(1).max(256),
|
|
107
107
|
computerId: z.string().min(1).max(256),
|
|
108
|
-
});
|
|
108
|
+
}));
|
|
109
109
|
/**
|
|
110
110
|
* COMPUTER.workflowGitPass from a published Workflow node frame. Only the
|
|
111
111
|
* frame's host-attested Workflow, call key, member and pinned Computer cross;
|
|
@@ -53,6 +53,26 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
53
53
|
readonly iteration: string;
|
|
54
54
|
readonly instanceId: string;
|
|
55
55
|
readonly eventType: string;
|
|
56
|
+
readonly position
|
|
57
|
+
/**
|
|
58
|
+
* The key's own catalog view: an installation it cannot reach at all drops
|
|
59
|
+
* out, and a read-only installation keeps only its read methods. A node
|
|
60
|
+
* that reaches past the key then fails to compile with the engine's own
|
|
61
|
+
* "Installation is not available to you" or unknown-method issue -- never a
|
|
62
|
+
* live grant computed here.
|
|
63
|
+
*/
|
|
64
|
+
? /**
|
|
65
|
+
* The key's own catalog view: an installation it cannot reach at all drops
|
|
66
|
+
* out, and a read-only installation keeps only its read methods. A node
|
|
67
|
+
* that reaches past the key then fails to compile with the engine's own
|
|
68
|
+
* "Installation is not available to you" or unknown-method issue -- never a
|
|
69
|
+
* live grant computed here.
|
|
70
|
+
*/: readonly {
|
|
71
|
+
readonly node: string;
|
|
72
|
+
readonly loop: "repeat" | "map";
|
|
73
|
+
readonly index: number;
|
|
74
|
+
readonly count: number;
|
|
75
|
+
}[];
|
|
56
76
|
readonly choices?: readonly string[];
|
|
57
77
|
};
|
|
58
78
|
status: "closed" | "pending" | "answered" | "delivered" | "timed_out";
|
|
@@ -93,7 +113,7 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
93
113
|
visibility: "private" | "public";
|
|
94
114
|
declaredOperationsDigest: import("@fungi.computer/puffball").Sha256Digest;
|
|
95
115
|
approvedGrants: readonly Readonly<{
|
|
96
|
-
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
116
|
+
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
97
117
|
capabilityApproved: true;
|
|
98
118
|
operations: readonly string[];
|
|
99
119
|
resources: "tenant-scope" | readonly Readonly<{
|
|
@@ -227,7 +247,7 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
227
247
|
visibility: "private" | "public";
|
|
228
248
|
declaredOperationsDigest: import("@fungi.computer/puffball").Sha256Digest;
|
|
229
249
|
approvedGrants: readonly Readonly<{
|
|
230
|
-
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
250
|
+
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
231
251
|
capabilityApproved: true;
|
|
232
252
|
operations: readonly string[];
|
|
233
253
|
resources: "tenant-scope" | readonly Readonly<{
|
|
@@ -311,7 +331,7 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
311
331
|
visibility: "private" | "public";
|
|
312
332
|
declaredOperationsDigest: import("@fungi.computer/puffball").Sha256Digest;
|
|
313
333
|
approvedGrants: readonly Readonly<{
|
|
314
|
-
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
334
|
+
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
315
335
|
capabilityApproved: true;
|
|
316
336
|
operations: readonly string[];
|
|
317
337
|
resources: "tenant-scope" | readonly Readonly<{
|
|
@@ -428,7 +448,7 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
428
448
|
visibility: "private" | "public";
|
|
429
449
|
declaredOperationsDigest: import("@fungi.computer/puffball").Sha256Digest;
|
|
430
450
|
approvedGrants: readonly Readonly<{
|
|
431
|
-
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
451
|
+
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
432
452
|
capabilityApproved: true;
|
|
433
453
|
operations: readonly string[];
|
|
434
454
|
resources: "tenant-scope" | readonly Readonly<{
|
|
@@ -526,7 +546,7 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
526
546
|
visibility: "private" | "public";
|
|
527
547
|
declaredOperationsDigest: import("@fungi.computer/puffball").Sha256Digest;
|
|
528
548
|
approvedGrants: readonly Readonly<{
|
|
529
|
-
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
549
|
+
capability: "agent.list" | "agent.manage" | "agent.rename" | "agent.avatar" | "agent.toolPolicy" | "agent.read" | "agent.send" | "computer.list" | "computer.readFile" | "computer.writeFile" | "computer.execute" | "llm.generation" | "media.generation" | "games:servers" | "git.repository" | "apps.open" | "apps.call" | "app.storage" | "app.events.emit" | "team.workbenchHost" | "marketplace.browse" | "marketplace.purchase" | "workflow.run" | "workflow.publish" | "workflow.arm" | "world" | "channels.read" | "channels.personal.manage";
|
|
530
550
|
capabilityApproved: true;
|
|
531
551
|
operations: readonly string[];
|
|
532
552
|
resources: "tenant-scope" | readonly Readonly<{
|
|
@@ -684,9 +704,11 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
684
704
|
readonly port: string;
|
|
685
705
|
}[];
|
|
686
706
|
readonly timeout: number;
|
|
707
|
+
readonly detail?: string | undefined;
|
|
687
708
|
readonly choices?: readonly {
|
|
688
|
-
readonly id: string;
|
|
689
709
|
readonly label: string;
|
|
710
|
+
readonly id: string;
|
|
711
|
+
readonly description?: string | undefined;
|
|
690
712
|
}[] | undefined;
|
|
691
713
|
} | {
|
|
692
714
|
readonly id: string;
|
|
@@ -779,9 +801,11 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
779
801
|
readonly port: string;
|
|
780
802
|
}[];
|
|
781
803
|
readonly timeout: number;
|
|
804
|
+
readonly detail?: string | undefined;
|
|
782
805
|
readonly choices?: readonly {
|
|
783
|
-
readonly id: string;
|
|
784
806
|
readonly label: string;
|
|
807
|
+
readonly id: string;
|
|
808
|
+
readonly description?: string | undefined;
|
|
785
809
|
}[] | undefined;
|
|
786
810
|
} | {
|
|
787
811
|
readonly id: string;
|
|
@@ -835,10 +859,10 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
835
859
|
readonly triggers?: readonly ({
|
|
836
860
|
readonly id: string;
|
|
837
861
|
readonly on: "webhook";
|
|
838
|
-
readonly when?: import("@fungi.computer/braid").Predicate | undefined;
|
|
839
862
|
readonly input?: {
|
|
840
863
|
readonly [x: string]: import("@fungi.computer/braid").Mapping;
|
|
841
864
|
} | undefined;
|
|
865
|
+
readonly when?: import("@fungi.computer/braid").Predicate | undefined;
|
|
842
866
|
readonly title?: string | undefined;
|
|
843
867
|
} | {
|
|
844
868
|
readonly id: string;
|
|
@@ -857,11 +881,11 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
857
881
|
readonly on: "event";
|
|
858
882
|
readonly app: string;
|
|
859
883
|
readonly eventDigest: string;
|
|
860
|
-
readonly repository?: import("@fungi.computer/braid").Mapping | undefined;
|
|
861
|
-
readonly when?: import("@fungi.computer/braid").Predicate | undefined;
|
|
862
884
|
readonly input?: {
|
|
863
885
|
readonly [x: string]: import("@fungi.computer/braid").Mapping;
|
|
864
886
|
} | undefined;
|
|
887
|
+
readonly when?: import("@fungi.computer/braid").Predicate | undefined;
|
|
888
|
+
readonly repository?: import("@fungi.computer/braid").Mapping | undefined;
|
|
865
889
|
readonly title?: string | undefined;
|
|
866
890
|
})[] | undefined;
|
|
867
891
|
};
|
|
@@ -869,13 +893,13 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
869
893
|
readonly [x: string]: {
|
|
870
894
|
readonly methods: {
|
|
871
895
|
readonly [x: string]: {
|
|
872
|
-
readonly digest: string;
|
|
873
896
|
readonly output: {
|
|
874
897
|
readonly [x: string]: S.Json;
|
|
875
898
|
};
|
|
876
899
|
readonly input: {
|
|
877
900
|
readonly [x: string]: S.Json;
|
|
878
901
|
};
|
|
902
|
+
readonly digest: string;
|
|
879
903
|
readonly approval: "none" | "queue" | "simulate";
|
|
880
904
|
readonly description: string;
|
|
881
905
|
readonly effect: "read" | "write" | "external";
|
|
@@ -904,14 +928,14 @@ export declare function createStandaloneWorkflowHost(owner: WatchdogSqliteOwner,
|
|
|
904
928
|
};
|
|
905
929
|
readonly estimatedSteps: number;
|
|
906
930
|
readonly requiredBindings: readonly {
|
|
907
|
-
readonly method: string;
|
|
908
|
-
readonly installationId: string;
|
|
909
931
|
readonly output: {
|
|
910
932
|
readonly [x: string]: S.Json;
|
|
911
933
|
};
|
|
912
934
|
readonly input: {
|
|
913
935
|
readonly [x: string]: S.Json;
|
|
914
936
|
};
|
|
937
|
+
readonly method: string;
|
|
938
|
+
readonly installationId: string;
|
|
915
939
|
readonly approval: "none" | "queue" | "simulate";
|
|
916
940
|
readonly description: string;
|
|
917
941
|
readonly effect: "read" | "write" | "external";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as S from "effect/Schema";
|
|
2
2
|
import { admitRunInput, accepts, catalogEvents, catalogMethods, catalogSchema, contentDigest, canonicalJson, parseProjection, nodeEventSchema, } from "@fungi.computer/braid";
|
|
3
3
|
import { deriveDeclaredOperations, approvedBindingGrants, grantsFor, } from "../apps/index.js";
|
|
4
|
-
import { pendingSchema } from "@fungi.computer/agar/backend-definition";
|
|
4
|
+
import { pendingSchema, presentMethodName, } from "@fungi.computer/agar/backend-definition";
|
|
5
|
+
import { resultRowSchemaV1 } from "@fungi.computer/gills";
|
|
5
6
|
import { verifyRelease, } from "../host.js";
|
|
6
7
|
import { APP_INVOCATION_CEILING_MS, invocationAuthorityOf, invocationFacts, invocationFactsSchema, } from "../invocation.js";
|
|
7
8
|
import { AppInstanceError } from "../index.js";
|
|
@@ -653,11 +654,8 @@ export function createStandaloneWorkflowHost(owner, host, options) {
|
|
|
653
654
|
};
|
|
654
655
|
const deadlineAt = Date.now() + APP_INVOCATION_CEILING_MS;
|
|
655
656
|
const deadline = { deadlineAt, grantExpiresAt: deadlineAt };
|
|
656
|
-
const
|
|
657
|
-
? invocationFacts.fromCaller(authority, {
|
|
658
|
-
...savedCaller,
|
|
659
|
-
idempotencyKey: call.idempotencyKey,
|
|
660
|
-
}, deadline)
|
|
657
|
+
const factsFor = (idempotencyKey) => run.publication === undefined
|
|
658
|
+
? invocationFacts.fromCaller(authority, { ...savedCaller, idempotencyKey }, deadline)
|
|
661
659
|
: invocationFacts.fromWorkflowNode(authority, {
|
|
662
660
|
...savedCaller,
|
|
663
661
|
workflow: {
|
|
@@ -666,8 +664,9 @@ export function createStandaloneWorkflowHost(owner, host, options) {
|
|
|
666
664
|
nodeId: resolved.nodeId,
|
|
667
665
|
armedBy: savedCaller.actor,
|
|
668
666
|
},
|
|
669
|
-
idempotencyKey
|
|
667
|
+
idempotencyKey,
|
|
670
668
|
}, deadline);
|
|
669
|
+
const facts = factsFor(call.idempotencyKey);
|
|
671
670
|
if (resolved.gitAccess !== undefined)
|
|
672
671
|
recordGitNode(call.idempotencyKey, {
|
|
673
672
|
teamId: run.teamId,
|
|
@@ -741,11 +740,39 @@ export function createStandaloneWorkflowHost(owner, host, options) {
|
|
|
741
740
|
return pending.data;
|
|
742
741
|
if (!accepts(parseProjection(pinned.output), value))
|
|
743
742
|
return { kind: "refused", code: "invalid_output" };
|
|
744
|
-
|
|
743
|
+
const output = S.decodeUnknownSync(S.Json)(value);
|
|
744
|
+
const row = verified.api.methods[resolved.method]?.present === undefined
|
|
745
|
+
? undefined
|
|
746
|
+
: await presentedRow(resolved.method, host.instances
|
|
747
|
+
.get(authority.instanceId)
|
|
748
|
+
.call(factsFor(`${call.idempotencyKey}:present`), verified.api, presentMethodName, { method: resolved.method, input: resolved.input, output }));
|
|
749
|
+
const complete = {
|
|
745
750
|
kind: "complete",
|
|
746
|
-
output
|
|
751
|
+
output,
|
|
747
752
|
costMilliunits: 0,
|
|
748
753
|
};
|
|
754
|
+
return row === undefined ? complete : { ...complete, row };
|
|
749
755
|
},
|
|
750
756
|
};
|
|
751
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* A failed or malformed projection costs the step only its row: the step stays
|
|
760
|
+
* complete, Trellis reads it as "<App> ran <method>" with the raw view, and
|
|
761
|
+
* the operator log records why.
|
|
762
|
+
*/
|
|
763
|
+
async function presentedRow(method, reply) {
|
|
764
|
+
try {
|
|
765
|
+
const result = await reply;
|
|
766
|
+
if (!result.ok || result.value.kind !== "ok") {
|
|
767
|
+
console.error("Workflow step presentation was refused", method);
|
|
768
|
+
return undefined;
|
|
769
|
+
}
|
|
770
|
+
return S.decodeUnknownSync(resultRowSchemaV1, {
|
|
771
|
+
onExcessProperty: "error",
|
|
772
|
+
})(result.value.value);
|
|
773
|
+
}
|
|
774
|
+
catch (cause) {
|
|
775
|
+
console.error("Workflow step presentation failed", method, cause);
|
|
776
|
+
return undefined;
|
|
777
|
+
}
|
|
778
|
+
}
|