@fungi.computer/agar 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 (52) hide show
  1. package/dist/agent-message-meta.d.ts +15 -15
  2. package/dist/agent-message-meta.js +5 -5
  3. package/dist/agents.d.ts +86 -13
  4. package/dist/agents.js +48 -25
  5. package/dist/apps.js +4 -4
  6. package/dist/backend-definition.d.ts +41 -4
  7. package/dist/backend-definition.js +70 -10
  8. package/dist/backend.js +4 -4
  9. package/dist/channels.js +4 -4
  10. package/dist/computer-contracts.d.ts +15 -15
  11. package/dist/computer-contracts.js +14 -14
  12. package/dist/computer.js +16 -16
  13. package/dist/game-servers.js +2 -2
  14. package/dist/git-rpc.d.ts +23 -23
  15. package/dist/git-rpc.js +14 -16
  16. package/dist/git.d.ts +9 -9
  17. package/dist/git.js +8 -8
  18. package/dist/host.d.ts +3 -1
  19. package/dist/host.js +5 -1
  20. package/dist/index.d.ts +5 -4
  21. package/dist/index.js +3 -2
  22. package/dist/inference.js +2 -1
  23. package/dist/internal/app-session.js +37 -1
  24. package/dist/internal/protocol.d.ts +4 -4
  25. package/dist/internal/schemas.d.ts +33 -7
  26. package/dist/internal/schemas.js +107 -16
  27. package/dist/marketplace.js +7 -5
  28. package/dist/media-request-id.d.ts +1 -1
  29. package/dist/media-request-id.js +2 -2
  30. package/dist/media.js +2 -2
  31. package/dist/method-presentation.d.ts +7 -0
  32. package/dist/method-presentation.js +14 -0
  33. package/dist/method-refusal.d.ts +5 -5
  34. package/dist/method-refusal.js +4 -4
  35. package/dist/react.d.ts +14 -2
  36. package/dist/react.js +40 -4
  37. package/dist/read-copy.d.ts +1 -1
  38. package/dist/read-copy.js +3 -6
  39. package/dist/storage.js +1 -1
  40. package/dist/test-kit/git.js +19 -2
  41. package/dist/window.d.ts +64 -0
  42. package/dist/window.js +31 -0
  43. package/docs/api/backend-definition.md +83 -9
  44. package/docs/api/backend.md +44 -38
  45. package/docs/api/git-rpc.md +35 -35
  46. package/docs/api/git.md +66 -80
  47. package/docs/api/index.md +775 -128
  48. package/docs/api/media-request-id.md +1 -1
  49. package/docs/api/method-presentation.md +21 -0
  50. package/docs/api/modules.md +1 -0
  51. package/docs/api/react.md +10 -4
  52. package/package.json +10 -5
@@ -1,9 +1,9 @@
1
1
  import * as z from "zod";
2
2
  /** Who a Session line came from, as the admitting host authenticated them. */
3
- export declare const FungiAuthorSchema: z.ZodObject<{
3
+ export declare const FungiAuthorSchema: z.ZodLazy<z.ZodObject<{
4
4
  id: z.ZodString;
5
5
  name: z.ZodString;
6
- }, z.core.$strict>;
6
+ }, z.core.$strict>>;
7
7
  /** Who a Session line came from, as the admitting host authenticated them. */
8
8
  export type FungiAuthor = z.infer<typeof FungiAuthorSchema>;
9
9
  /**
@@ -11,10 +11,10 @@ export type FungiAuthor = z.infer<typeof FungiAuthorSchema>;
11
11
  * mail it sent. `commandId` is the replying Session's accepted command, the
12
12
  * same id as the sender's `agents.send` receipt.
13
13
  */
14
- export declare const FungiDelegationReplySchema: z.ZodObject<{
15
- agentId: z.ZodString;
16
- sessionId: z.ZodString;
17
- commandId: z.ZodString;
14
+ export declare const FungiDelegationReplySchema: z.ZodLazy<z.ZodObject<{
15
+ agentId: z.ZodLazy<z.ZodString>;
16
+ sessionId: z.ZodLazy<z.ZodString>;
17
+ commandId: z.ZodLazy<z.ZodString>;
18
18
  outcome: z.ZodEnum<{
19
19
  completed: "completed";
20
20
  failed: "failed";
@@ -23,7 +23,7 @@ export declare const FungiDelegationReplySchema: z.ZodObject<{
23
23
  outcome_unknown: "outcome_unknown";
24
24
  }>;
25
25
  diagnostic: z.ZodOptional<z.ZodString>;
26
- }, z.core.$strict>;
26
+ }, z.core.$strict>>;
27
27
  /** A delegated Agent's reply as the admitting host authenticated it. */
28
28
  export type FungiDelegationReply = z.infer<typeof FungiDelegationReplySchema>;
29
29
  /**
@@ -31,17 +31,17 @@ export type FungiDelegationReply = z.infer<typeof FungiDelegationReplySchema>;
31
31
  * Shiitake stores it opaquely; the host writes it and Apps read it.
32
32
  */
33
33
  export declare const FungiMessageMetaSchema: z.ZodObject<{
34
- author: z.ZodOptional<z.ZodObject<{
34
+ author: z.ZodOptional<z.ZodLazy<z.ZodObject<{
35
35
  id: z.ZodString;
36
36
  name: z.ZodString;
37
- }, z.core.$strict>>;
37
+ }, z.core.$strict>>>;
38
38
  delegation: z.ZodOptional<z.ZodObject<{
39
- ancestors: z.ZodArray<z.ZodString>;
39
+ ancestors: z.ZodArray<z.ZodLazy<z.ZodString>>;
40
40
  maxDepth: z.ZodNumber;
41
- reply: z.ZodOptional<z.ZodObject<{
42
- agentId: z.ZodString;
43
- sessionId: z.ZodString;
44
- commandId: z.ZodString;
41
+ reply: z.ZodOptional<z.ZodLazy<z.ZodObject<{
42
+ agentId: z.ZodLazy<z.ZodString>;
43
+ sessionId: z.ZodLazy<z.ZodString>;
44
+ commandId: z.ZodLazy<z.ZodString>;
45
45
  outcome: z.ZodEnum<{
46
46
  completed: "completed";
47
47
  failed: "failed";
@@ -50,7 +50,7 @@ export declare const FungiMessageMetaSchema: z.ZodObject<{
50
50
  outcome_unknown: "outcome_unknown";
51
51
  }>;
52
52
  diagnostic: z.ZodOptional<z.ZodString>;
53
- }, z.core.$strict>>;
53
+ }, z.core.$strict>>>;
54
54
  }, z.core.$strict>>;
55
55
  }, z.core.$strict>;
56
56
  /** The metadata the host attaches to every prompt and operator line it admits. */
@@ -1,16 +1,16 @@
1
1
  import * as z from "zod";
2
2
  /** Who a Session line came from, as the admitting host authenticated them. */
3
- export const FungiAuthorSchema = z.strictObject({
3
+ export const FungiAuthorSchema = z.lazy(() => z.strictObject({
4
4
  id: z.string().min(1).max(256),
5
5
  name: z.string().min(1).max(128),
6
- });
7
- const identifier = z.string().min(1).max(256);
6
+ }));
7
+ const identifier = z.lazy(() => z.string().min(1).max(256));
8
8
  /**
9
9
  * A delegated Agent's reply as the admitting host authenticated it against the
10
10
  * mail it sent. `commandId` is the replying Session's accepted command, the
11
11
  * same id as the sender's `agents.send` receipt.
12
12
  */
13
- export const FungiDelegationReplySchema = z.strictObject({
13
+ export const FungiDelegationReplySchema = z.lazy(() => z.strictObject({
14
14
  agentId: identifier,
15
15
  sessionId: identifier,
16
16
  commandId: identifier,
@@ -24,7 +24,7 @@ export const FungiDelegationReplySchema = z.strictObject({
24
24
  ]),
25
25
  /** Bounded, single-line terminal diagnostic for work that did not complete. */
26
26
  diagnostic: z.string().min(1).max(200).optional(),
27
- });
27
+ }));
28
28
  /**
29
29
  * The metadata the host attaches to every prompt and operator line it admits.
30
30
  * Shiitake stores it opaquely; the host writes it and Apps read it.
package/dist/agents.d.ts CHANGED
@@ -4,13 +4,44 @@ import * as z from "zod";
4
4
  import * as Schema from "effect/Schema";
5
5
  import type { CapabilityContract, CapabilityDirectory } from "./capabilities.js";
6
6
  /**
7
- * Validates the wire shape of an Agent record: its id, display name,
8
- * and which session operations (read, send, manage) this App may
9
- * perform on it.
7
+ * An Agent's avatar: Sprite's Avatar v1 envelope. Apps check its shape; the
8
+ * host owns the character-catalog refinement when it stores one.
9
+ */
10
+ export declare const appAgentAvatarSchema: Schema.declare<{
11
+ v: 1;
12
+ character: "mushroom" | "cat" | "burger";
13
+ appearance: Record<string, string | number | boolean>;
14
+ expression: string;
15
+ renderStyle: "plain" | "pencil" | "manga" | "riso" | "halftone" | "blueprint" | "crt" | "watercolor";
16
+ }, {
17
+ v: 1;
18
+ character: "mushroom" | "cat" | "burger";
19
+ appearance: Record<string, string | number | boolean>;
20
+ expression: string;
21
+ renderStyle: "plain" | "pencil" | "manga" | "riso" | "halftone" | "blueprint" | "crt" | "watercolor";
22
+ }>;
23
+ /**
24
+ * Validates the wire shape of an Agent record: its id, display name, avatar,
25
+ * and which session operations (read, send, manage) this App may perform on
26
+ * it. Every Agent has an avatar: the one its owner chose, else Sprite's
27
+ * `seededAvatar(agentId)`. Conversations carry none of their own.
10
28
  */
11
29
  export declare const appAgentSchema: Schema.Struct<{
12
30
  readonly agentId: Schema.String;
13
31
  readonly name: Schema.String;
32
+ readonly avatar: Schema.declare<{
33
+ v: 1;
34
+ character: "mushroom" | "cat" | "burger";
35
+ appearance: Record<string, string | number | boolean>;
36
+ expression: string;
37
+ renderStyle: "plain" | "pencil" | "manga" | "riso" | "halftone" | "blueprint" | "crt" | "watercolor";
38
+ }, {
39
+ v: 1;
40
+ character: "mushroom" | "cat" | "burger";
41
+ appearance: Record<string, string | number | boolean>;
42
+ expression: string;
43
+ renderStyle: "plain" | "pencil" | "manga" | "riso" | "halftone" | "blueprint" | "crt" | "watercolor";
44
+ }>;
14
45
  readonly sessionOperations: Schema.$Array<Schema.Literals<readonly ["agent.read", "agent.send", "agent.manage"]>>;
15
46
  }>;
16
47
  /**
@@ -19,24 +50,38 @@ export declare const appAgentSchema: Schema.Struct<{
19
50
  */
20
51
  export type AppAgent = Schema.Schema.Type<typeof appAgentSchema>;
21
52
  /** The admitted AGENTS.list projection, containing only ids and display names. */
22
- export declare const appAgentListResultSchema: z.ZodObject<{
53
+ export declare const appAgentListResultSchema: z.ZodLazy<z.ZodObject<{
23
54
  agents: z.ZodArray<z.ZodObject<{
24
55
  agentId: z.ZodString;
25
56
  name: z.ZodString;
26
57
  }, z.core.$strict>>;
27
- }, z.core.$strict>;
58
+ }, z.core.$strict>>;
28
59
  /** Computer provisioning is a Team creation choice, separate from Home policy. */
29
60
  export declare const appAgentComputerSchema: Schema.Literals<readonly ["new", "none"]>;
30
- /** Validates Agent creation input: a name, a caller-chosen request id and an
31
- * optional Computer provisioning choice. */
61
+ /** Validates Agent creation input: a name, a caller-chosen request id, an
62
+ * optional Computer provisioning choice and an optional chosen avatar. */
32
63
  export declare const appAgentCreateInputSchema: Schema.Struct<{
33
64
  readonly name: Schema.String;
34
65
  readonly requestId: Schema.String;
35
66
  readonly computer: Schema.optionalKey<Schema.Literals<readonly ["new", "none"]>>;
67
+ readonly avatar: Schema.optionalKey<Schema.declare<{
68
+ v: 1;
69
+ character: "mushroom" | "cat" | "burger";
70
+ appearance: Record<string, string | number | boolean>;
71
+ expression: string;
72
+ renderStyle: "plain" | "pencil" | "manga" | "riso" | "halftone" | "blueprint" | "crt" | "watercolor";
73
+ }, {
74
+ v: 1;
75
+ character: "mushroom" | "cat" | "burger";
76
+ appearance: Record<string, string | number | boolean>;
77
+ expression: string;
78
+ renderStyle: "plain" | "pencil" | "manga" | "riso" | "halftone" | "blueprint" | "crt" | "watercolor";
79
+ }>>;
36
80
  }>;
37
81
  /**
38
82
  * Input for creating a new Agent: its name, a caller-chosen request id
39
- * for idempotency, and whether to provision a new Computer for it.
83
+ * for idempotency, whether to provision a new Computer for it, and the
84
+ * avatar it wears (omitted, it wears its seeded avatar).
40
85
  */
41
86
  export type AppAgentCreateInput = Readonly<Schema.Schema.Type<typeof appAgentCreateInputSchema>>;
42
87
  /** Live object-capability shape received from Cap'n Web. */
@@ -93,26 +138,54 @@ export type AppAgentRenameHandle = {
93
138
  export declare const agentRename: CapabilityContract<AppAgentRenameHandle>;
94
139
  /** Rename an Agent through the host-granted owner-only capability. */
95
140
  export declare function renameAppAgent(capabilities: CapabilityDirectory, input: AppAgentRenameInput): Promise<AppAgent>;
141
+ /** Validates avatar input: the target Agent and the avatar it now wears. */
142
+ export declare const appAgentAvatarInputSchema: Schema.Struct<{
143
+ readonly agentId: Schema.String;
144
+ readonly avatar: Schema.declare<{
145
+ v: 1;
146
+ character: "mushroom" | "cat" | "burger";
147
+ appearance: Record<string, string | number | boolean>;
148
+ expression: string;
149
+ renderStyle: "plain" | "pencil" | "manga" | "riso" | "halftone" | "blueprint" | "crt" | "watercolor";
150
+ }, {
151
+ v: 1;
152
+ character: "mushroom" | "cat" | "burger";
153
+ appearance: Record<string, string | number | boolean>;
154
+ expression: string;
155
+ renderStyle: "plain" | "pencil" | "manga" | "riso" | "halftone" | "blueprint" | "crt" | "watercolor";
156
+ }>;
157
+ }>;
158
+ /** Input for changing an existing Agent's avatar. */
159
+ export type AppAgentAvatarInput = Readonly<Schema.Schema.Type<typeof appAgentAvatarInputSchema>>;
160
+ /** Capability handle for changing an Agent's avatar. */
161
+ export type AppAgentAvatarHandle = {
162
+ /** Set an existing Agent's avatar and return its updated record. */
163
+ setAvatar(input: AppAgentAvatarInput): Promise<AppAgent>;
164
+ };
165
+ /** Owner-only capability to change an existing Agent's avatar; not every host grants it. */
166
+ export declare const agentAvatar: CapabilityContract<AppAgentAvatarHandle>;
167
+ /** Change an Agent's avatar through the host-granted owner-only capability. */
168
+ export declare function setAppAgentAvatar(capabilities: CapabilityDirectory, input: AppAgentAvatarInput): Promise<AppAgent>;
96
169
  /** One Agent's App-tool policy, as the owner-only policy capability reports it. */
97
- export declare const appAgentToolPolicyStatusSchema: z.ZodUnion<readonly [z.ZodObject<{
170
+ export declare const appAgentToolPolicyStatusSchema: z.ZodLazy<z.ZodUnion<readonly [z.ZodObject<{
98
171
  status: z.ZodLiteral<"pending">;
99
172
  revision: z.ZodString;
100
173
  }, z.core.$strip>, z.ZodObject<{
101
174
  status: z.ZodLiteral<"approved">;
102
175
  revision: z.ZodString;
103
- }, z.core.$strip>]>;
176
+ }, z.core.$strip>]>>;
104
177
  /** Whether an Agent's App tools are still pending the owner's approval. */
105
178
  export type AppAgentToolPolicyStatus = z.infer<typeof appAgentToolPolicyStatusSchema>;
106
179
  /** The outcome of an owner's direct App-tool approval. */
107
- export declare const appAgentToolPolicyResultSchema: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodObject<{
180
+ export declare const appAgentToolPolicyResultSchema: z.ZodLazy<z.ZodUnion<readonly [z.ZodLazy<z.ZodUnion<readonly [z.ZodObject<{
108
181
  status: z.ZodLiteral<"pending">;
109
182
  revision: z.ZodString;
110
183
  }, z.core.$strip>, z.ZodObject<{
111
184
  status: z.ZodLiteral<"approved">;
112
185
  revision: z.ZodString;
113
- }, z.core.$strip>]>, z.ZodObject<{
186
+ }, z.core.$strip>]>>, z.ZodObject<{
114
187
  error: z.ZodString;
115
- }, z.core.$strip>]>;
188
+ }, z.core.$strip>]>>;
116
189
  /** The outcome of an owner's direct App-tool approval. */
117
190
  export type AppAgentToolPolicyResult = z.infer<typeof appAgentToolPolicyResultSchema>;
118
191
  /** Capability handle for reading and approving an Agent's App-tool policy. */
package/dist/agents.js CHANGED
@@ -3,31 +3,40 @@ import * as z from "zod";
3
3
  import { defineCapability } from "./capabilities.js";
4
4
  import { rpcDataSchema, rpcHandleSchema, rpcMethod, rpcFetchHandleSchema, } from "./internal/rpc-contract.js";
5
5
  import * as Schema from "effect/Schema";
6
+ import { avatarEnvelopeSchema, } from "@fungi.computer/sprite/avatar";
6
7
  import { AppError } from "./errors.js";
7
8
  import { reportCatchFailure } from "./failure.js";
8
9
  const boundedIdentity = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(256)));
9
10
  /**
10
- * Validates the wire shape of an Agent record: its id, display name,
11
- * and which session operations (read, send, manage) this App may
12
- * perform on it.
11
+ * An Agent's avatar: Sprite's Avatar v1 envelope. Apps check its shape; the
12
+ * host owns the character-catalog refinement when it stores one.
13
+ */
14
+ export const appAgentAvatarSchema = Schema.declare((value) => avatarEnvelopeSchema.safeParse(value).success, { expected: "an Avatar v1 descriptor" });
15
+ /**
16
+ * Validates the wire shape of an Agent record: its id, display name, avatar,
17
+ * and which session operations (read, send, manage) this App may perform on
18
+ * it. Every Agent has an avatar: the one its owner chose, else Sprite's
19
+ * `seededAvatar(agentId)`. Conversations carry none of their own.
13
20
  */
14
21
  export const appAgentSchema = Schema.Struct({
15
22
  agentId: boundedIdentity,
16
23
  name: boundedIdentity,
24
+ avatar: appAgentAvatarSchema,
17
25
  sessionOperations: Schema.Array(Schema.Literals(["agent.read", "agent.send", "agent.manage"])),
18
26
  });
19
27
  /** The admitted AGENTS.list projection, containing only ids and display names. */
20
- export const appAgentListResultSchema = z.strictObject({
28
+ export const appAgentListResultSchema = z.lazy(() => z.strictObject({
21
29
  agents: z.array(z.strictObject({ agentId: z.string(), name: z.string() })),
22
- });
30
+ }));
23
31
  /** Computer provisioning is a Team creation choice, separate from Home policy. */
24
32
  export const appAgentComputerSchema = Schema.Literals(["new", "none"]);
25
- /** Validates Agent creation input: a name, a caller-chosen request id and an
26
- * optional Computer provisioning choice. */
33
+ /** Validates Agent creation input: a name, a caller-chosen request id, an
34
+ * optional Computer provisioning choice and an optional chosen avatar. */
27
35
  export const appAgentCreateInputSchema = Schema.Struct({
28
36
  name: boundedIdentity,
29
37
  requestId: boundedIdentity,
30
38
  computer: Schema.optionalKey(appAgentComputerSchema),
39
+ avatar: Schema.optionalKey(appAgentAvatarSchema),
31
40
  });
32
41
  const appAgentDirectorySchema = Schema.Array(appAgentSchema);
33
42
  const decodeAgents = Schema.decodeUnknownSync(appAgentDirectorySchema, {
@@ -36,64 +45,78 @@ const decodeAgents = Schema.decodeUnknownSync(appAgentDirectorySchema, {
36
45
  const decodeAgentId = Schema.decodeUnknownSync(boundedIdentity);
37
46
  const decodeResponse = Schema.decodeUnknownSync(Schema.instanceOf(Response));
38
47
  /** Capability to list the Agents visible to this App in read-only form. */
39
- export const agentList = defineCapability("agent.list", 1, rpcHandleSchema({
48
+ export const agentList = defineCapability("agent.list", 1, z.lazy(() => rpcHandleSchema({
40
49
  list: rpcMethod(rpcDataSchema(z.unknown().transform((value) => decodeAgents(value)))),
41
- }));
50
+ })));
42
51
  /** Capability to create new Agents and open transports to them. */
43
- export const agentManage = defineCapability("agent.manage", 1, rpcHandleSchema({
52
+ export const agentManage = defineCapability("agent.manage", 1, z.lazy(() => rpcHandleSchema({
44
53
  open: rpcMethod(rpcFetchHandleSchema),
45
54
  create: rpcMethod(rpcDataSchema(z
46
55
  .unknown()
47
56
  .transform((value) => Schema.decodeUnknownSync(appAgentSchema)(value)))),
48
- }));
57
+ })));
49
58
  /** Validates Agent rename input: the target Agent and its new display name. */
50
59
  export const appAgentRenameInputSchema = Schema.Struct({
51
60
  agentId: boundedIdentity,
52
61
  name: boundedIdentity,
53
62
  });
54
63
  /** Owner-only capability to rename an existing Agent; not every host grants it. */
55
- export const agentRename = defineCapability("agent.rename", 1, rpcHandleSchema({
64
+ export const agentRename = defineCapability("agent.rename", 1, z.lazy(() => rpcHandleSchema({
56
65
  rename: rpcMethod(rpcDataSchema(z
57
66
  .unknown()
58
67
  .transform((value) => Schema.decodeUnknownSync(appAgentSchema)(value)))),
59
- }));
68
+ })));
60
69
  /** Rename an Agent through the host-granted owner-only capability. */
61
70
  export async function renameAppAgent(capabilities, input) {
62
71
  const request = Schema.decodeUnknownSync(appAgentRenameInputSchema)(input);
63
72
  const agent = await capabilities.require(agentRename).rename(request);
64
73
  return Schema.decodeUnknownSync(appAgentSchema)(agent);
65
74
  }
75
+ /** Validates avatar input: the target Agent and the avatar it now wears. */
76
+ export const appAgentAvatarInputSchema = Schema.Struct({
77
+ agentId: boundedIdentity,
78
+ avatar: appAgentAvatarSchema,
79
+ });
80
+ /** Owner-only capability to change an existing Agent's avatar; not every host grants it. */
81
+ export const agentAvatar = defineCapability("agent.avatar", 1, z.lazy(() => rpcHandleSchema({
82
+ setAvatar: rpcMethod(rpcDataSchema(z
83
+ .unknown()
84
+ .transform((value) => Schema.decodeUnknownSync(appAgentSchema)(value)))),
85
+ })));
86
+ /** Change an Agent's avatar through the host-granted owner-only capability. */
87
+ export async function setAppAgentAvatar(capabilities, input) {
88
+ const request = Schema.decodeUnknownSync(appAgentAvatarInputSchema)(input);
89
+ const agent = await capabilities.require(agentAvatar).setAvatar(request);
90
+ return Schema.decodeUnknownSync(appAgentSchema)(agent);
91
+ }
66
92
  /** One Agent's App-tool policy, as the owner-only policy capability reports it. */
67
- export const appAgentToolPolicyStatusSchema = z.union([
93
+ export const appAgentToolPolicyStatusSchema = z.lazy(() => z.union([
68
94
  z.object({ status: z.literal("pending"), revision: z.string() }),
69
95
  z.object({ status: z.literal("approved"), revision: z.string() }),
70
- ]);
96
+ ]));
71
97
  /** The outcome of an owner's direct App-tool approval. */
72
- export const appAgentToolPolicyResultSchema = z.union([
73
- appAgentToolPolicyStatusSchema,
74
- z.object({ error: z.string() }),
75
- ]);
98
+ export const appAgentToolPolicyResultSchema = z.lazy(() => z.union([appAgentToolPolicyStatusSchema, z.object({ error: z.string() })]));
76
99
  /** Owner-only capability to read and approve an Agent's App-tool policy. */
77
- export const agentToolPolicy = defineCapability("agent.toolPolicy", 1, rpcHandleSchema({
100
+ export const agentToolPolicy = defineCapability("agent.toolPolicy", 1, z.lazy(() => rpcHandleSchema({
78
101
  status: rpcMethod(rpcDataSchema(z.union([appAgentToolPolicyStatusSchema, z.null()]))),
79
102
  approve: rpcMethod(rpcDataSchema(appAgentToolPolicyResultSchema)),
80
- }));
103
+ })));
81
104
  /**
82
105
  * Capability for read-only Agent sessions: open a transport to an
83
106
  * existing Agent, or read the member's inbox feed.
84
107
  */
85
- export const agentSessionRead = defineCapability("agent.read", 1, rpcHandleSchema({
108
+ export const agentSessionRead = defineCapability("agent.read", 1, z.lazy(() => rpcHandleSchema({
86
109
  open: rpcMethod(rpcFetchHandleSchema),
87
110
  openInbox: rpcMethod(rpcFetchHandleSchema),
88
- }));
111
+ })));
89
112
  /**
90
113
  * Capability for read-write Agent sessions: open a transport to an
91
114
  * existing Agent and send to it, including the member's inbox feed.
92
115
  */
93
- export const agentSessionWrite = defineCapability("agent.send", 1, rpcHandleSchema({
116
+ export const agentSessionWrite = defineCapability("agent.send", 1, z.lazy(() => rpcHandleSchema({
94
117
  open: rpcMethod(rpcFetchHandleSchema),
95
118
  openInbox: rpcMethod(rpcFetchHandleSchema),
96
- }));
119
+ })));
97
120
  /** List the Agents in the host-granted Team or local-project scope. */
98
121
  export async function listAppAgents(capabilities) {
99
122
  return decodeAgents(await capabilities.require(agentList).list());
package/dist/apps.js CHANGED
@@ -36,12 +36,12 @@ export const appRoleChoiceSchema = z
36
36
  * `role` answers which App that open would reach (null when none fills it),
37
37
  * so a caller can name or hide its entry without hard-coding another App.
38
38
  */
39
- export const appsOpen = defineCapability("apps.open", 1, rpcHandleSchema({
39
+ export const appsOpen = defineCapability("apps.open", 1, z.lazy(() => rpcHandleSchema({
40
40
  open: rpcVoid,
41
41
  role: rpcMethod(rpcDataSchema(appRoleChoiceSchema)),
42
- }));
42
+ })));
43
43
  export const openApp = (directory, input) => directory.require(appsOpen).open(input);
44
44
  /** Call a declared method of another App as the signed-in member. Caller grants never travel. */
45
- export const appsCall = defineCapability("apps.call", 1, rpcHandleSchema({
45
+ export const appsCall = defineCapability("apps.call", 1, z.lazy(() => rpcHandleSchema({
46
46
  call: rpcMethod(rpcDataSchema(z.unknown().transform((value) => parseJsonValue(value)))),
47
- }));
47
+ })));
@@ -1,5 +1,5 @@
1
1
  import * as z from "zod";
2
- import type { GillCall, GillReply } from "@fungi.computer/gills";
2
+ import type { GillCall, GillReply, ResultRow } from "@fungi.computer/gills";
3
3
  import type { AppBackendGill } from "./gills.js";
4
4
  export type { AppBackendGill } from "./gills.js";
5
5
  import { type AppMethodCallContext } from "@fungi.computer/puffball/app-method-runtime";
@@ -10,10 +10,10 @@ export { pending, pendingSchema, type Pending } from "./pending.js";
10
10
  /** EVENTS.emit acknowledges delivery with native void, not a JSON value. */
11
11
  export declare const appEventEmitResultSchema: z.ZodUndefined;
12
12
  /** App-originated Session send refusal; retry advice is a duration, not a timestamp. */
13
- export declare const appSendBudgetRefusalSchema: z.ZodObject<{
13
+ export declare const appSendBudgetRefusalSchema: z.ZodLazy<z.ZodObject<{
14
14
  error: z.ZodLiteral<"send_budget_exceeded">;
15
15
  retryAfterMs: z.ZodNumber;
16
- }, z.core.$strict>;
16
+ }, z.core.$strict>>;
17
17
  /** A declined Session send, with how long to wait before retrying. */
18
18
  export type AppSendBudgetRefusal = z.output<typeof appSendBudgetRefusalSchema>;
19
19
  /** A method's schemas own both its TypeScript contract and JSON boundary. */
@@ -27,19 +27,56 @@ export type MethodPolicy = Readonly<{
27
27
  anonymous?: true;
28
28
  /** Allow the shared Pending result while this method is still working. */
29
29
  pending?: true;
30
+ /** Past tense for timelines ("made", "wrote"); the row itself comes from {@link MethodPresentation}. */
31
+ present?: Readonly<{
32
+ verb: string;
33
+ }>;
34
+ }>;
35
+ /** Decodes and bounds result rows; see `resultRows` in `@fungi.computer/agar/method-presentation`. */
36
+ export type ResultRowCodec = Readonly<{
37
+ /** Check one projected row against the shared row's bounds; throws when it breaks them. */
38
+ decode: (row: ResultRow) => ResultRow;
39
+ /** The row's JSON boundary, as the generated `present` method's output. */
40
+ schema: z.ZodType;
41
+ }>;
42
+ /**
43
+ * How a finished call reads to a person: a pure projection run in the App
44
+ * backend. `rows` is `resultRows` from
45
+ * `@fungi.computer/agar/method-presentation`; only a backend that presents
46
+ * imports it, so one without presentations carries no row decoder.
47
+ */
48
+ export type MethodPresentation<I, O> = Readonly<{
49
+ /** Past tense for timelines: "made", "wrote", "pushed". */
50
+ verb: string;
51
+ /** Pure: one finished call to the row a person reads. */
52
+ row: (call: Readonly<{
53
+ input: I;
54
+ output: O;
55
+ }>) => ResultRow;
56
+ /** The shared row codec, `resultRows`. */
57
+ rows: ResultRowCodec;
30
58
  }>;
59
+ /** Name of the generated, hidden read method that projects a finished call into its row. */
60
+ export declare const presentMethodName = "present";
31
61
  /** Prepared method export consumed by the backend and its build. */
32
62
  export type AppBackendMethod<Context = void> = Readonly<{
33
63
  policy: MethodPolicy;
34
64
  input: z.ZodType;
35
65
  output: z.ZodType;
66
+ /** Parse a recorded call against this method's schemas and project its row. */
67
+ present?: (input: WhistleJsonValue, output: WhistleJsonValue) => ResultRow;
68
+ /** The row's JSON boundary, present exactly when `present` is. */
69
+ presentRow?: z.ZodType;
36
70
  /** Parse the raw input, run the handler, and return its JSON result. */
37
71
  call: (input: WhistleJsonValue, context: Context, caller: AppMethodCallContext) => Promise<WhistleJsonValue>;
38
72
  }>;
73
+ /** The prepared methods of one backend, keyed by their public names. */
74
+ export type AppBackendMethods<Context = void> = Readonly<Record<string, AppBackendMethod<Context>>>;
39
75
  /** Define a typed method once; neither discovery nor builds execute its handler. */
40
- export declare function defineAppMethod<I extends z.ZodType, O extends z.ZodType, Context = void>(method: MethodPolicy & {
76
+ export declare function defineAppMethod<I extends z.ZodType, O extends z.ZodType, Context = void>(method: Omit<MethodPolicy, "present"> & {
41
77
  input: I;
42
78
  output: O;
79
+ present?: MethodPresentation<z.output<I>, z.output<O>>;
43
80
  } & ({
44
81
  pending: true;
45
82
  handler: (input: z.output<I>, context: Context, caller: AppMethodCallContext) => z.input<O> | Pending | Promise<z.input<O> | Pending>;
@@ -6,10 +6,12 @@ export { pending, pendingSchema } from "./pending.js";
6
6
  /** EVENTS.emit acknowledges delivery with native void, not a JSON value. */
7
7
  export const appEventEmitResultSchema = z.undefined();
8
8
  /** App-originated Session send refusal; retry advice is a duration, not a timestamp. */
9
- export const appSendBudgetRefusalSchema = z.strictObject({
9
+ export const appSendBudgetRefusalSchema = z.lazy(() => z.strictObject({
10
10
  error: z.literal("send_budget_exceeded"),
11
11
  retryAfterMs: z.number().int().min(1).max(60_000),
12
- });
12
+ }));
13
+ /** Name of the generated, hidden read method that projects a finished call into its row. */
14
+ export const presentMethodName = "present";
13
15
  /** Define a typed method once; neither discovery nor builds execute its handler. */
14
16
  export function defineAppMethod(method) {
15
17
  const output = method.pending === true
@@ -26,16 +28,31 @@ export function defineAppMethod(method) {
26
28
  policy = { ...policy, hidden: true };
27
29
  if (method.anonymous === true)
28
30
  policy = { ...policy, anonymous: true };
31
+ const call = async (rawInput, context, rawCaller) => {
32
+ const caller = parseAppMethodCallContext(rawCaller);
33
+ const parsed = method.input.parse(rawInput);
34
+ const result = await method.handler(parsed, context, caller);
35
+ return z.json().parse(output.parse(result));
36
+ };
37
+ const presentation = method.present;
38
+ if (presentation === undefined)
39
+ return Object.freeze({
40
+ policy: Object.freeze(policy),
41
+ input: method.input,
42
+ output,
43
+ call,
44
+ });
45
+ policy = { ...policy, present: Object.freeze({ verb: presentation.verb }) };
29
46
  return Object.freeze({
30
47
  policy: Object.freeze(policy),
31
48
  input: method.input,
32
49
  output,
33
- call: async (rawInput, context, rawCaller) => {
34
- const caller = parseAppMethodCallContext(rawCaller);
35
- const parsed = method.input.parse(rawInput);
36
- const result = await method.handler(parsed, context, caller);
37
- return z.json().parse(output.parse(result));
38
- },
50
+ present: (rawInput, rawOutput) => presentation.rows.decode(presentation.row({
51
+ input: method.input.parse(rawInput),
52
+ output: method.output.parse(rawOutput),
53
+ })),
54
+ presentRow: presentation.rows.schema,
55
+ call,
39
56
  });
40
57
  }
41
58
  /** The generated method RPC answers with a typed reply: a handler's
@@ -54,9 +71,52 @@ async function methodReply(run) {
54
71
  });
55
72
  }
56
73
  }
74
+ const presentInput = z.strictObject({
75
+ method: z.string().min(1).max(80),
76
+ input: z.json(),
77
+ output: z.json(),
78
+ });
79
+ const generatedPresentMethods = new WeakSet();
80
+ /**
81
+ * Methods that declare a presentation share one generated, hidden read method
82
+ * that projects a recorded call into its row, so hosts reach it through the
83
+ * ordinary method admission instead of a second invocation path.
84
+ */
85
+ function withPresentation(all) {
86
+ // A backend rebuilt from another definition's methods regenerates its own.
87
+ const methods = Object.fromEntries(Object.entries(all).filter(([name, method]) => name !== presentMethodName || !generatedPresentMethods.has(method)));
88
+ const presenting = new Map(Object.entries(methods).filter(([, method]) => method.present !== undefined));
89
+ const presentRow = [...presenting.values()][0]?.presentRow;
90
+ if (presentRow === undefined)
91
+ return methods;
92
+ if (Object.hasOwn(methods, presentMethodName))
93
+ throw new Error(`App backend method is reserved: ${presentMethodName}`);
94
+ const present = Object.freeze({
95
+ policy: Object.freeze({
96
+ description: "Project one finished call of a presenting method into its result row.",
97
+ effect: "read",
98
+ requires: Object.freeze([]),
99
+ hidden: true,
100
+ }),
101
+ input: presentInput,
102
+ output: presentRow,
103
+ call: (rawInput) => Promise.resolve().then(() => {
104
+ const request = presentInput.parse(rawInput);
105
+ const row = presenting
106
+ .get(request.method)
107
+ ?.present?.(request.input, request.output);
108
+ if (row === undefined)
109
+ throw new Error(`Undeclared App presentation: ${request.method}`);
110
+ return z.json().parse(presentRow.parse(row));
111
+ }),
112
+ });
113
+ generatedPresentMethods.add(present);
114
+ return { ...methods, [presentMethodName]: present };
115
+ }
57
116
  /** Generate the API from the method exports; callers cannot supply a second catalog. */
58
117
  export function defineAppBackend(options) {
59
- const methods = new Map(Object.entries(options.methods));
118
+ const declared = withPresentation(options.methods);
119
+ const methods = new Map(Object.entries(declared));
60
120
  for (const name of methods.keys())
61
121
  if (!isAppApiMethodName(name))
62
122
  throw new Error(`Invalid App backend method name: ${name}`);
@@ -97,7 +157,7 @@ export function defineAppBackend(options) {
97
157
  });
98
158
  };
99
159
  let definition = {
100
- methods: Object.freeze({ ...options.methods }),
160
+ methods: Object.freeze({ ...declared }),
101
161
  implement: (implementation) => {
102
162
  const members = new Map();
103
163
  let prototype = implementation.prototype;
package/dist/backend.js CHANGED
@@ -3,18 +3,18 @@ import { defineCapability } from "./capabilities.js";
3
3
  import { parseJsonValue } from "@fungi.computer/whistle/wire";
4
4
  import { rpcDataSchema, rpcHandleSchema, rpcMethod, } from "./internal/rpc-contract.js";
5
5
  /** Invoke this App's admitted backend. The App defines its JSON request/result. */
6
- export const appBackend = defineCapability("app.backend", 1, rpcHandleSchema({
6
+ export const appBackend = defineCapability("app.backend", 1, z.lazy(() => rpcHandleSchema({
7
7
  request: rpcMethod(rpcDataSchema(z.unknown().transform((value) => parseJsonValue(value)))),
8
- }));
8
+ })));
9
9
  /**
10
10
  * Call one of this App's declared, typed methods through its host. Unlike
11
11
  * `appBackend`, the host knows which method is called, so it can apply the
12
12
  * method's declared effect (for example, allow only `read` methods to a
13
13
  * visitor of a public App).
14
14
  */
15
- export const appMethods = defineCapability("app.methods", 1, rpcHandleSchema({
15
+ export const appMethods = defineCapability("app.methods", 1, z.lazy(() => rpcHandleSchema({
16
16
  call: rpcMethod(rpcDataSchema(z.unknown().transform((value) => parseJsonValue(value)))),
17
- }));
17
+ })));
18
18
  /** The admitting host overwrites this header from its authenticated principal.
19
19
  * App request bodies and incoming browser headers never supply this identity. */
20
20
  export const appBackendActorHeader = "X-Agar-Actor-Id";
package/dist/channels.js CHANGED
@@ -84,14 +84,14 @@ export const appWebsiteReplyResponseSchema = z.strictObject({
84
84
  status: z.literal("sent"),
85
85
  });
86
86
  /** Reading website conversations is never an automatic default-App grant. */
87
- export const channelsRead = defineCapability("channels.read", 1, rpcHandleSchema({
87
+ export const channelsRead = defineCapability("channels.read", 1, z.lazy(() => rpcHandleSchema({
88
88
  list: rpcMethod(rpcDataSchema(appWebsitePageSchema)),
89
89
  conversations: rpcMethod(rpcDataSchema(appWebsiteConversationPageSchema)),
90
- }));
90
+ })));
91
91
  /** Member-only takeover; separate consent from read-only channel access. */
92
- export const channelsPersonalManage = defineCapability("channels.personal.manage", 1, rpcHandleSchema({
92
+ export const channelsPersonalManage = defineCapability("channels.personal.manage", 1, z.lazy(() => rpcHandleSchema({
93
93
  hold: rpcMethod(rpcDataSchema(appWebsiteConversationSchema)),
94
94
  release: rpcMethod(rpcDataSchema(appWebsiteConversationSchema)),
95
95
  dismiss: rpcMethod(rpcDataSchema(appWebsiteConversationSchema)),
96
96
  reply: rpcMethod(rpcDataSchema(appWebsiteReplyResponseSchema)),
97
- }));
97
+ })));