@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.
Files changed (43) hide show
  1. package/dist/agent-avatars.d.ts +103 -0
  2. package/dist/agent-avatars.js +66 -0
  3. package/dist/agents.d.ts +12 -2
  4. package/dist/agents.js +39 -2
  5. package/dist/apps/gate.d.ts +7 -7
  6. package/dist/apps/gate.js +6 -6
  7. package/dist/apps/installation.d.ts +35 -35
  8. package/dist/apps/installation.js +20 -19
  9. package/dist/build/candidate-cli.mjs +55 -5
  10. package/dist/build-egress.js +4 -4
  11. package/dist/cloudflare.js +4 -4
  12. package/dist/frame-binding-wire-script.js +1 -1
  13. package/dist/frame-binding-wire.d.ts +1 -1
  14. package/dist/frame-binding-wire.js +1 -1
  15. package/dist/git-journal.js +4 -4
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/installation-consent.d.ts +3 -1
  19. package/dist/installation-consent.js +2 -0
  20. package/dist/page/mount.d.ts +4 -8
  21. package/dist/page-script.js +4 -4
  22. package/dist/shell/app-directory.d.ts +37 -37
  23. package/dist/shell/app-directory.js +15 -18
  24. package/dist/shell/window-manager.d.ts +5 -1
  25. package/dist/shell/window-manager.js +21 -2
  26. package/dist/shell/workbench-directory.d.ts +9 -9
  27. package/dist/shell/workbench-host-types.d.ts +1 -1
  28. package/dist/shell/workbench-host.js +5 -0
  29. package/dist/shell/workbench-model.d.ts +3 -1
  30. package/dist/standalone/agents.d.ts +11 -4
  31. package/dist/standalone/agents.js +44 -5
  32. package/dist/standalone/app-builds-binding.js +6 -6
  33. package/dist/standalone/computer.js +3 -3
  34. package/dist/standalone/preview.js +2 -2
  35. package/dist/standalone/worker.js +2 -2
  36. package/dist/standalone/workflow-binding.d.ts +10 -10
  37. package/dist/standalone/workflow-binding.js +2 -2
  38. package/dist/standalone/workflow-host.d.ts +37 -13
  39. package/dist/standalone/workflow-host.js +36 -9
  40. package/dist/standalone/workflow-trigger-host.d.ts +24 -2
  41. package/dist/standalone/workflows.d.ts +67 -11
  42. package/dist/workers-git.js +1 -4
  43. 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 }) => ({ 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 { agentId: renamed.agentId, name: renamed.name };
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
- create(name, requestId, principal, actor) {
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, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
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 facts = run.publication === undefined
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: call.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
- return {
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: S.decodeUnknownSync(S.Json)(value),
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
+ }