@fungi.computer/agar 0.0.0-stage → 0.0.1

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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +766 -2
  3. package/dist/agent-message-meta.d.ts +74 -0
  4. package/dist/agent-message-meta.js +63 -0
  5. package/dist/agents.d.ts +170 -0
  6. package/dist/agents.js +218 -0
  7. package/dist/app-build-contract.d.ts +43 -0
  8. package/dist/app-build-contract.js +24 -0
  9. package/dist/apps.d.ts +66 -0
  10. package/dist/apps.js +47 -0
  11. package/dist/backend-declaration.d.ts +6 -0
  12. package/dist/backend-declaration.js +65 -0
  13. package/dist/backend-definition.d.ts +88 -0
  14. package/dist/backend-definition.js +158 -0
  15. package/dist/backend-refusal.d.ts +10 -0
  16. package/dist/backend-refusal.js +10 -0
  17. package/dist/backend.d.ts +30 -0
  18. package/dist/backend.js +28 -0
  19. package/dist/capabilities.d.ts +30 -0
  20. package/dist/capabilities.js +50 -0
  21. package/dist/channels.d.ts +142 -0
  22. package/dist/channels.js +97 -0
  23. package/dist/commands.d.ts +33 -0
  24. package/dist/commands.js +46 -0
  25. package/dist/computer-contracts.d.ts +73 -0
  26. package/dist/computer-contracts.js +57 -0
  27. package/dist/computer.d.ts +294 -0
  28. package/dist/computer.js +319 -0
  29. package/dist/connect.d.ts +11 -0
  30. package/dist/connect.js +211 -0
  31. package/dist/errors.d.ts +34 -0
  32. package/dist/errors.js +106 -0
  33. package/dist/failure.d.ts +2 -0
  34. package/dist/failure.js +9 -0
  35. package/dist/files.d.ts +25 -0
  36. package/dist/files.js +84 -0
  37. package/dist/game-servers.d.ts +530 -0
  38. package/dist/game-servers.js +244 -0
  39. package/dist/gills.d.ts +43 -0
  40. package/dist/gills.js +174 -0
  41. package/dist/git-commit.d.ts +143 -0
  42. package/dist/git-commit.js +251 -0
  43. package/dist/git-rpc.d.ts +310 -0
  44. package/dist/git-rpc.js +166 -0
  45. package/dist/git-steps.d.ts +579 -0
  46. package/dist/git-steps.js +804 -0
  47. package/dist/git-wire.d.ts +33 -0
  48. package/dist/git-wire.js +240 -0
  49. package/dist/git.d.ts +322 -0
  50. package/dist/git.js +98 -0
  51. package/dist/host.d.ts +97 -0
  52. package/dist/host.js +732 -0
  53. package/dist/inbox.d.ts +99 -0
  54. package/dist/inbox.js +184 -0
  55. package/dist/index.d.ts +31 -0
  56. package/dist/index.js +15 -0
  57. package/dist/inference.d.ts +17 -0
  58. package/dist/inference.js +115 -0
  59. package/dist/internal/app-error.d.ts +8 -0
  60. package/dist/internal/app-error.js +36 -0
  61. package/dist/internal/app-session.d.ts +8 -0
  62. package/dist/internal/app-session.js +551 -0
  63. package/dist/internal/back-inputs.d.ts +22 -0
  64. package/dist/internal/back-inputs.js +93 -0
  65. package/dist/internal/browser-bridge.d.ts +30 -0
  66. package/dist/internal/browser-bridge.js +203 -0
  67. package/dist/internal/capabilities.d.ts +13 -0
  68. package/dist/internal/capabilities.js +138 -0
  69. package/dist/internal/commands.d.ts +9 -0
  70. package/dist/internal/commands.js +257 -0
  71. package/dist/internal/disconnect-host.d.ts +7 -0
  72. package/dist/internal/disconnect-host.js +19 -0
  73. package/dist/internal/git-edit-tree.d.ts +6 -0
  74. package/dist/internal/git-edit-tree.js +46 -0
  75. package/dist/internal/memory-fs.d.ts +39 -0
  76. package/dist/internal/memory-fs.js +154 -0
  77. package/dist/internal/protocol.d.ts +116 -0
  78. package/dist/internal/protocol.js +6 -0
  79. package/dist/internal/query-feed.d.ts +23 -0
  80. package/dist/internal/query-feed.js +111 -0
  81. package/dist/internal/result.d.ts +22 -0
  82. package/dist/internal/result.js +61 -0
  83. package/dist/internal/rpc-contract.d.ts +24 -0
  84. package/dist/internal/rpc-contract.js +78 -0
  85. package/dist/internal/schemas.d.ts +307 -0
  86. package/dist/internal/schemas.js +524 -0
  87. package/dist/internal/store.d.ts +8 -0
  88. package/dist/internal/store.js +30 -0
  89. package/dist/internal/terminal-gate.d.ts +17 -0
  90. package/dist/internal/terminal-gate.js +94 -0
  91. package/dist/lifecycle.d.ts +44 -0
  92. package/dist/lifecycle.js +1 -0
  93. package/dist/marketplace.d.ts +159 -0
  94. package/dist/marketplace.js +102 -0
  95. package/dist/media-json.d.ts +181 -0
  96. package/dist/media-json.js +204 -0
  97. package/dist/media-library.d.ts +11 -0
  98. package/dist/media-library.js +15 -0
  99. package/dist/media-request-id.d.ts +7 -0
  100. package/dist/media-request-id.js +16 -0
  101. package/dist/media.d.ts +354 -0
  102. package/dist/media.js +654 -0
  103. package/dist/method-refusal.d.ts +64 -0
  104. package/dist/method-refusal.js +48 -0
  105. package/dist/pending.d.ts +10 -0
  106. package/dist/pending.js +10 -0
  107. package/dist/query.d.ts +76 -0
  108. package/dist/query.js +195 -0
  109. package/dist/react-mount.d.ts +23 -0
  110. package/dist/react-mount.js +108 -0
  111. package/dist/react.d.ts +41 -0
  112. package/dist/react.js +111 -0
  113. package/dist/read-copy.d.ts +103 -0
  114. package/dist/read-copy.js +195 -0
  115. package/dist/roles/files.d.ts +295 -0
  116. package/dist/roles/files.js +184 -0
  117. package/dist/root.d.ts +21 -0
  118. package/dist/root.js +1 -0
  119. package/dist/rpc.d.ts +1 -0
  120. package/dist/rpc.js +1 -0
  121. package/dist/status.d.ts +17 -0
  122. package/dist/status.js +50 -0
  123. package/dist/storage-wire.d.ts +167 -0
  124. package/dist/storage-wire.js +115 -0
  125. package/dist/storage.d.ts +72 -0
  126. package/dist/storage.js +143 -0
  127. package/dist/store.d.ts +11 -0
  128. package/dist/store.js +1 -0
  129. package/dist/test-kit/git.d.ts +31 -0
  130. package/dist/test-kit/git.js +174 -0
  131. package/dist/ui.d.ts +169 -0
  132. package/dist/ui.js +10 -0
  133. package/dist/window.d.ts +41 -0
  134. package/dist/window.js +15 -0
  135. package/dist/world.d.ts +16 -0
  136. package/dist/world.js +17 -0
  137. package/docs/api/README.md +28 -0
  138. package/docs/api/app-build-contract.md +113 -0
  139. package/docs/api/backend-declaration.md +62 -0
  140. package/docs/api/backend-definition.md +215 -0
  141. package/docs/api/backend.md +269 -0
  142. package/docs/api/errors.md +169 -0
  143. package/docs/api/files.md +91 -0
  144. package/docs/api/gills.md +164 -0
  145. package/docs/api/git-commit.md +279 -0
  146. package/docs/api/git-rpc.md +243 -0
  147. package/docs/api/git-steps.md +1010 -0
  148. package/docs/api/git-wire.md +197 -0
  149. package/docs/api/git.md +579 -0
  150. package/docs/api/index.md +2880 -0
  151. package/docs/api/media-json.md +507 -0
  152. package/docs/api/media-request-id.md +32 -0
  153. package/docs/api/modules.md +32 -0
  154. package/docs/api/query.md +253 -0
  155. package/docs/api/react.md +216 -0
  156. package/docs/api/read-copy.md +204 -0
  157. package/docs/api/rpc.md +128 -0
  158. package/docs/api/storage.md +620 -0
  159. package/package.json +197 -3
package/dist/apps.js ADDED
@@ -0,0 +1,47 @@
1
+ import * as z from "zod";
2
+ import { parseJsonValue } from "@fungi.computer/whistle/wire";
3
+ import { defineCapability } from "./capabilities.js";
4
+ import { rpcHandleSchema, rpcVoid, rpcMethod, rpcDataSchema, } from "./internal/rpc-contract.js";
5
+ import * as Schema from "effect/Schema";
6
+ /** A recipient App decides how to present this request; opening never sends it. */
7
+ export const appIntentSchema = Schema.Union([
8
+ Schema.Struct({
9
+ kind: Schema.Literal("compose"),
10
+ text: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(2_000))),
11
+ }),
12
+ Schema.Struct({
13
+ kind: Schema.Literal("navigate"),
14
+ params: Schema.Array(Schema.Struct({
15
+ key: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(64))),
16
+ value: Schema.String.pipe(Schema.check(Schema.isMaxLength(1_024))),
17
+ })).pipe(Schema.check(Schema.isMaxLength(16))),
18
+ }),
19
+ ]);
20
+ export const appOpenInputSchema = Schema.Union([
21
+ Schema.Struct({
22
+ role: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(80))),
23
+ intent: appIntentSchema,
24
+ }),
25
+ Schema.Struct({
26
+ appId: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(128))),
27
+ intent: appIntentSchema,
28
+ }),
29
+ ], { mode: "oneOf" });
30
+ /** The App the Team chose for a role, as the host's role routing resolves it; a null name is an unnamed App. */
31
+ export const appRoleChoiceSchema = z
32
+ .strictObject({ displayName: z.string().min(1).max(80).nullable() })
33
+ .nullable();
34
+ /**
35
+ * Open the Team's chosen App for a role with a bounded presentation intent.
36
+ * `role` answers which App that open would reach (null when none fills it),
37
+ * so a caller can name or hide its entry without hard-coding another App.
38
+ */
39
+ export const appsOpen = defineCapability("apps.open", 1, rpcHandleSchema({
40
+ open: rpcVoid,
41
+ role: rpcMethod(rpcDataSchema(appRoleChoiceSchema)),
42
+ }));
43
+ export const openApp = (directory, input) => directory.require(appsOpen).open(input);
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({
46
+ call: rpcMethod(rpcDataSchema(z.unknown().transform((value) => parseJsonValue(value)))),
47
+ }));
@@ -0,0 +1,6 @@
1
+ import type { AppApi, AppDeclaration } from "@fungi.computer/puffball";
2
+ import type { AppBackendDefinition } from "./backend-definition.js";
3
+ /** Build-only API projection from the schemas used by runtime calls. */
4
+ export declare function buildAppApi<Context>(backend: Pick<AppBackendDefinition<Context>, "methods" | "events" | "toolFamily">): AppApi;
5
+ /** Strict build-only declaration generation; Workers never import this module. */
6
+ export declare function buildAppDeclaration<Context>(backend: Pick<AppBackendDefinition<Context>, "methods" | "events" | "gills" | "sync" | "toolFamily">, declaration: unknown): AppDeclaration;
@@ -0,0 +1,65 @@
1
+ import * as z from "zod";
2
+ import * as Schema from "effect/Schema";
3
+ import { appApiSchema, appDeclarationSchema, } from "@fungi.computer/puffball/app";
4
+ /** Build-only API projection from the schemas used by runtime calls. */
5
+ export function buildAppApi(backend) {
6
+ let api = {
7
+ methods: Object.fromEntries(Object.entries(backend.methods).map(([name, method]) => [
8
+ name,
9
+ {
10
+ ...method.policy,
11
+ input: z.toJSONSchema(method.input, { io: "input" }),
12
+ output: z.toJSONSchema(method.output, { io: "output" }),
13
+ },
14
+ ])),
15
+ };
16
+ if (backend.events !== undefined)
17
+ api = {
18
+ ...api,
19
+ events: Object.fromEntries(Object.entries(backend.events).map(([name, event]) => [
20
+ name,
21
+ {
22
+ description: event.description,
23
+ payload: z.toJSONSchema(event.payload, { io: "output" }),
24
+ },
25
+ ])),
26
+ };
27
+ if (backend.toolFamily !== undefined)
28
+ api = { ...api, toolFamily: backend.toolFamily };
29
+ return Schema.decodeUnknownSync(appApiSchema)(api);
30
+ }
31
+ /** Strict build-only declaration generation; Workers never import this module. */
32
+ export function buildAppDeclaration(backend, declaration) {
33
+ const metadata = Schema.decodeUnknownSync(appDeclarationSchema)(declaration);
34
+ if (metadata.gills !== undefined)
35
+ throw new Error("App Gills must be generated from the backend, not a second catalog");
36
+ if (metadata.backend?.sync !== undefined)
37
+ throw new Error("App sync continuation must be generated from the backend");
38
+ const methods = new Map(Object.entries(backend.methods));
39
+ for (const view of backend.gills ?? []) {
40
+ const method = methods.get(view.declaration.source);
41
+ if (method?.policy.effect !== "read")
42
+ throw new Error(`App Gill source must be a declared read method: ${view.declaration.source}`);
43
+ for (const param of view.declaration.params ?? []) {
44
+ if (param.options !== undefined &&
45
+ methods.get(param.options)?.policy.effect !== "read")
46
+ throw new Error(`Gill options source must be a declared read method: ${param.options}`);
47
+ }
48
+ }
49
+ let generated = { ...metadata, api: buildAppApi(backend) };
50
+ if (backend.sync !== undefined)
51
+ generated = {
52
+ ...generated,
53
+ backend: {
54
+ ...metadata.backend,
55
+ bindings: metadata.backend?.bindings ?? [],
56
+ sync: backend.sync,
57
+ },
58
+ };
59
+ if (backend.gills !== undefined)
60
+ generated = {
61
+ ...generated,
62
+ gills: backend.gills.map((view) => view.declaration),
63
+ };
64
+ return Schema.decodeUnknownSync(appDeclarationSchema)(generated);
65
+ }
@@ -0,0 +1,88 @@
1
+ import * as z from "zod";
2
+ import type { GillCall, GillReply } from "@fungi.computer/gills";
3
+ import type { AppBackendGill } from "./gills.js";
4
+ export type { AppBackendGill } from "./gills.js";
5
+ import { type AppMethodCallContext } from "@fungi.computer/puffball/app-method-runtime";
6
+ import type { AppApi, AppCapabilityId } from "@fungi.computer/puffball";
7
+ import type { WhistleJsonValue } from "@fungi.computer/whistle";
8
+ import { type Pending } from "./pending.js";
9
+ export { pending, pendingSchema, type Pending } from "./pending.js";
10
+ /** EVENTS.emit acknowledges delivery with native void, not a JSON value. */
11
+ export declare const appEventEmitResultSchema: z.ZodUndefined;
12
+ /** App-originated Session send refusal; retry advice is a duration, not a timestamp. */
13
+ export declare const appSendBudgetRefusalSchema: z.ZodObject<{
14
+ error: z.ZodLiteral<"send_budget_exceeded">;
15
+ retryAfterMs: z.ZodNumber;
16
+ }, z.core.$strict>;
17
+ /** A declined Session send, with how long to wait before retrying. */
18
+ export type AppSendBudgetRefusal = z.output<typeof appSendBudgetRefusalSchema>;
19
+ /** A method's schemas own both its TypeScript contract and JSON boundary. */
20
+ export type MethodPolicy = Readonly<{
21
+ description: string;
22
+ effect: "read" | "write" | "external";
23
+ requires: readonly AppCapabilityId[];
24
+ /** Omit this method from Workflow and tool discovery; direct calls keep their validators. */
25
+ hidden?: true;
26
+ /** Request anonymous read admission; hosts still require explicit installer approval. */
27
+ anonymous?: true;
28
+ /** Allow the shared Pending result while this method is still working. */
29
+ pending?: true;
30
+ }>;
31
+ /** Prepared method export consumed by the backend and its build. */
32
+ export type AppBackendMethod<Context = void> = Readonly<{
33
+ policy: MethodPolicy;
34
+ input: z.ZodType;
35
+ output: z.ZodType;
36
+ /** Parse the raw input, run the handler, and return its JSON result. */
37
+ call: (input: WhistleJsonValue, context: Context, caller: AppMethodCallContext) => Promise<WhistleJsonValue>;
38
+ }>;
39
+ /** 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 & {
41
+ input: I;
42
+ output: O;
43
+ } & ({
44
+ pending: true;
45
+ handler: (input: z.output<I>, context: Context, caller: AppMethodCallContext) => z.input<O> | Pending | Promise<z.input<O> | Pending>;
46
+ } | {
47
+ pending?: undefined;
48
+ handler: (input: z.output<I>, context: Context, caller: AppMethodCallContext) => z.input<O> | Promise<z.input<O>>;
49
+ })): AppBackendMethod<Context>;
50
+ /** Typed backend export shared by a build and its runtime method admission. */
51
+ export type AppBackendClass = (abstract new (...args: never[]) => object) & {
52
+ prototype: object;
53
+ };
54
+ /** A backend's full generated API: its declared methods, the facet
55
+ * installer, and runtime dispatch by method name. */
56
+ export type AppBackendDefinition<Context = void> = Readonly<{
57
+ methods: Readonly<Record<string, AppBackendMethod<Context>>>;
58
+ /** Declared ordinary write method re-admitted for member background work. */
59
+ sync?: Readonly<{
60
+ method: string;
61
+ }>;
62
+ /** Generated, digest-covered views of declared read methods. */
63
+ gills?: readonly AppBackendGill[];
64
+ /** Run a declared projection through its source method. */
65
+ gill: (input: GillCall, context: Context, caller: AppMethodCallContext) => Promise<GillReply>;
66
+ /** Signed discovery metadata; listing never grants authority to execute. */
67
+ toolFamily?: AppApi["toolFamily"];
68
+ /** Event schemas are projected into the Release for host-side emit validation. */
69
+ events?: Readonly<Record<string, Readonly<{
70
+ description: string;
71
+ payload: z.ZodType;
72
+ }>>>;
73
+ /** Install declared methods on the facet, retaining its exact constructor
74
+ * type. Each installed method answers with an {@link AppMethodReply}. */
75
+ implement: <C extends AppBackendClass>(implementation: C) => C;
76
+ /** Look up a declared method by name and run its `call`, rejecting any
77
+ * name that was not declared. A handler's {@link AppMethodRefusal} is
78
+ * thrown to the caller unchanged. */
79
+ call: (method: string, input: WhistleJsonValue, context: Context, caller: AppMethodCallContext) => Promise<WhistleJsonValue>;
80
+ }>;
81
+ /** Generate the API from the method exports; callers cannot supply a second catalog. */
82
+ export declare function defineAppBackend<Context = void>(options: Readonly<{
83
+ methods: Readonly<Record<string, AppBackendMethod<Context>>>;
84
+ events?: AppBackendDefinition<Context>["events"];
85
+ gills?: readonly AppBackendGill[];
86
+ sync?: AppBackendDefinition<Context>["sync"];
87
+ toolFamily?: AppBackendDefinition<Context>["toolFamily"];
88
+ }>): AppBackendDefinition<Context>;
@@ -0,0 +1,158 @@
1
+ import * as z from "zod";
2
+ import { backgroundSyncAllowed, parseAppMethodCallContext, isAppApiMethodName, appApiReservedMethodNames, } from "@fungi.computer/puffball/app-method-runtime";
3
+ import { pendingSchema } from "./pending.js";
4
+ import { AppMethodRefusal, appMethodRefusedReplySchema, } from "./method-refusal.js";
5
+ export { pending, pendingSchema } from "./pending.js";
6
+ /** EVENTS.emit acknowledges delivery with native void, not a JSON value. */
7
+ export const appEventEmitResultSchema = z.undefined();
8
+ /** App-originated Session send refusal; retry advice is a duration, not a timestamp. */
9
+ export const appSendBudgetRefusalSchema = z.strictObject({
10
+ error: z.literal("send_budget_exceeded"),
11
+ retryAfterMs: z.number().int().min(1).max(60_000),
12
+ });
13
+ /** Define a typed method once; neither discovery nor builds execute its handler. */
14
+ export function defineAppMethod(method) {
15
+ const output = method.pending === true
16
+ ? z.union([method.output, pendingSchema])
17
+ : method.output;
18
+ let policy = {
19
+ description: method.description,
20
+ effect: method.effect,
21
+ requires: Object.freeze([...method.requires]),
22
+ };
23
+ if (method.pending === true)
24
+ policy = { ...policy, pending: true };
25
+ if (method.hidden === true)
26
+ policy = { ...policy, hidden: true };
27
+ if (method.anonymous === true)
28
+ policy = { ...policy, anonymous: true };
29
+ return Object.freeze({
30
+ policy: Object.freeze(policy),
31
+ input: method.input,
32
+ 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
+ },
39
+ });
40
+ }
41
+ /** The generated method RPC answers with a typed reply: a handler's
42
+ * {@link AppMethodRefusal} crosses as a refusal, never as an execution failure. */
43
+ async function methodReply(run) {
44
+ try {
45
+ return { kind: "ok", value: await run() };
46
+ }
47
+ catch (error) {
48
+ if (!(error instanceof AppMethodRefusal))
49
+ throw error;
50
+ return appMethodRefusedReplySchema.parse({
51
+ kind: "refused",
52
+ code: error.code,
53
+ message: error.message,
54
+ });
55
+ }
56
+ }
57
+ /** Generate the API from the method exports; callers cannot supply a second catalog. */
58
+ export function defineAppBackend(options) {
59
+ const methods = new Map(Object.entries(options.methods));
60
+ for (const name of methods.keys())
61
+ if (!isAppApiMethodName(name))
62
+ throw new Error(`Invalid App backend method name: ${name}`);
63
+ if (!backgroundSyncAllowed({
64
+ methods: Object.fromEntries([...methods].map(([name, method]) => [name, method.policy])),
65
+ }, options.sync))
66
+ throw new Error("Background continuation must be a declared write covering all write requirements");
67
+ const call = async (name, input, context, caller) => {
68
+ const method = methods.get(name);
69
+ if (!method)
70
+ throw new Error(`Undeclared App backend method: ${name}`);
71
+ return method.call(input, context, caller);
72
+ };
73
+ const gills = new Map();
74
+ for (const view of options.gills ?? []) {
75
+ if (gills.has(view.declaration.id))
76
+ throw new Error(`Duplicate App Gill: ${view.declaration.id}`);
77
+ gills.set(view.declaration.id, view);
78
+ }
79
+ const gill = async (raw, context, caller) => {
80
+ const first = options.gills?.[0];
81
+ if (first === undefined)
82
+ throw new Error("Undeclared App Gill");
83
+ const selection = first.parseCall(raw);
84
+ const view = gills.get(selection.id);
85
+ if (!view)
86
+ throw new Error(`Undeclared App Gill: ${selection.id}`);
87
+ const method = methods.get(view.declaration.source);
88
+ if (method?.policy.effect !== "read")
89
+ throw new Error(`App Gill source must be a declared read method: ${view.declaration.source}`);
90
+ return view.call(selection, async (name, input, kind) => {
91
+ const source = methods.get(name);
92
+ if (source?.policy.effect !== "read")
93
+ throw new Error(kind === "options"
94
+ ? "Gill options source must be a declared read method"
95
+ : `App Gill source must be a declared read method: ${name}`);
96
+ return source.call(input, context, caller);
97
+ });
98
+ };
99
+ let definition = {
100
+ methods: Object.freeze({ ...options.methods }),
101
+ implement: (implementation) => {
102
+ const members = new Map();
103
+ let prototype = implementation.prototype;
104
+ while (prototype !== null && prototype !== Object.prototype) {
105
+ for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(prototype))) {
106
+ if (!members.has(name))
107
+ members.set(name, descriptor);
108
+ }
109
+ prototype = Reflect.getPrototypeOf(prototype);
110
+ }
111
+ const allowed = new Set([
112
+ ...appApiReservedMethodNames.intrinsic,
113
+ ...appApiReservedMethodNames.lifecycle,
114
+ ]);
115
+ for (const [name, descriptor] of members) {
116
+ if (descriptor.get !== undefined || descriptor.set !== undefined)
117
+ throw new Error(`App backend accessor unavailable: ${name}`);
118
+ if (methods.has(name))
119
+ throw new Error(`App backend method must be generated: ${name}`);
120
+ if (allowed.has(name) &&
121
+ (name === "constructor" ||
122
+ z.function().safeParse(descriptor.value).success))
123
+ continue;
124
+ throw new Error(`Undeclared App backend method: ${name}`);
125
+ }
126
+ for (const name of methods.keys())
127
+ Object.defineProperty(implementation.prototype, name, {
128
+ value: function (input, caller) {
129
+ return methodReply(() => definition.call(name, input, this, caller));
130
+ },
131
+ });
132
+ if (gills.size > 0)
133
+ Object.defineProperty(implementation.prototype, "gill", {
134
+ value: function (input, caller) {
135
+ return definition.gill(input, this, caller);
136
+ },
137
+ });
138
+ return implementation;
139
+ },
140
+ call,
141
+ gill,
142
+ };
143
+ if (options.sync !== undefined)
144
+ definition = { ...definition, sync: Object.freeze({ ...options.sync }) };
145
+ if (options.gills !== undefined)
146
+ definition = { ...definition, gills: Object.freeze([...options.gills]) };
147
+ if (options.events !== undefined)
148
+ definition = {
149
+ ...definition,
150
+ events: Object.freeze({ ...options.events }),
151
+ };
152
+ if (options.toolFamily !== undefined)
153
+ definition = {
154
+ ...definition,
155
+ toolFamily: Object.freeze({ ...options.toolFamily }),
156
+ };
157
+ return Object.freeze(definition);
158
+ }
@@ -0,0 +1,10 @@
1
+ import * as z from "zod";
2
+ /** Closed host refusal vocabulary. Private backend messages never reach callers. */
3
+ export declare const appBackendRefusalSchema: z.ZodObject<{
4
+ error: z.ZodLiteral<"App execution unavailable">;
5
+ reason: z.ZodEnum<{
6
+ exec_isolation_required: "exec_isolation_required";
7
+ execution_not_admitted: "execution_not_admitted";
8
+ artifact_unavailable: "artifact_unavailable";
9
+ }>;
10
+ }, z.core.$strict>;
@@ -0,0 +1,10 @@
1
+ import * as z from "zod";
2
+ /** Closed host refusal vocabulary. Private backend messages never reach callers. */
3
+ export const appBackendRefusalSchema = z.strictObject({
4
+ error: z.literal("App execution unavailable"),
5
+ reason: z.enum([
6
+ "exec_isolation_required",
7
+ "execution_not_admitted",
8
+ "artifact_unavailable",
9
+ ]),
10
+ });
@@ -0,0 +1,30 @@
1
+ import type { CapabilityContract } from "./capabilities.js";
2
+ import type { WhistleJsonValue } from "@fungi.computer/whistle";
3
+ /** Invoke this App's admitted backend. The App defines its JSON request/result. */
4
+ export declare const appBackend: CapabilityContract<{
5
+ /** Send one JSON-encoded request to this App's admitted backend and
6
+ * resolve with its decoded JSON result; the App's own backend defines
7
+ * what `input` must contain. */
8
+ request(input: string): Promise<WhistleJsonValue>;
9
+ }>;
10
+ /**
11
+ * Call one of this App's declared, typed methods through its host. Unlike
12
+ * `appBackend`, the host knows which method is called, so it can apply the
13
+ * method's declared effect (for example, allow only `read` methods to a
14
+ * visitor of a public App).
15
+ */
16
+ export declare const appMethods: CapabilityContract<{
17
+ /** Call the named method with JSON-encoded `input`; resolves with its JSON result. */
18
+ call(method: string, input: string): Promise<WhistleJsonValue>;
19
+ }>;
20
+ /** The admitting host overwrites this header from its authenticated principal.
21
+ * App request bodies and incoming browser headers never supply this identity. */
22
+ export declare const appBackendActorHeader = "X-Agar-Actor-Id";
23
+ /** The admitting host overwrites this header with the Computer it has
24
+ * verified the caller is admitted to use for this invocation, or omits it
25
+ * when no Computer is bound to the call. App request bodies and incoming
26
+ * browser headers never supply this identity: a backend that needs a
27
+ * Computer scope must read it from here, never from its own request body. */
28
+ export declare const appBackendComputerHeader = "X-Agar-Computer-Id";
29
+ export { appBackendRefusalSchema } from "./backend-refusal.js";
30
+ export { AppMethodRefusal, appMethodRefusalCodes, appMethodRefusalStatus, appMethodRefusedReplySchema, appMethodReplySchema, maxAppMethodRefusalMessage, type AppMethodRefusalCode, type AppMethodRefusedReply, type AppMethodReply, } from "./method-refusal.js";
@@ -0,0 +1,28 @@
1
+ import * as z from "zod";
2
+ import { defineCapability } from "./capabilities.js";
3
+ import { parseJsonValue } from "@fungi.computer/whistle/wire";
4
+ import { rpcDataSchema, rpcHandleSchema, rpcMethod, } from "./internal/rpc-contract.js";
5
+ /** Invoke this App's admitted backend. The App defines its JSON request/result. */
6
+ export const appBackend = defineCapability("app.backend", 1, rpcHandleSchema({
7
+ request: rpcMethod(rpcDataSchema(z.unknown().transform((value) => parseJsonValue(value)))),
8
+ }));
9
+ /**
10
+ * Call one of this App's declared, typed methods through its host. Unlike
11
+ * `appBackend`, the host knows which method is called, so it can apply the
12
+ * method's declared effect (for example, allow only `read` methods to a
13
+ * visitor of a public App).
14
+ */
15
+ export const appMethods = defineCapability("app.methods", 1, rpcHandleSchema({
16
+ call: rpcMethod(rpcDataSchema(z.unknown().transform((value) => parseJsonValue(value)))),
17
+ }));
18
+ /** The admitting host overwrites this header from its authenticated principal.
19
+ * App request bodies and incoming browser headers never supply this identity. */
20
+ export const appBackendActorHeader = "X-Agar-Actor-Id";
21
+ /** The admitting host overwrites this header with the Computer it has
22
+ * verified the caller is admitted to use for this invocation, or omits it
23
+ * when no Computer is bound to the call. App request bodies and incoming
24
+ * browser headers never supply this identity: a backend that needs a
25
+ * Computer scope must read it from here, never from its own request body. */
26
+ export const appBackendComputerHeader = "X-Agar-Computer-Id";
27
+ export { appBackendRefusalSchema } from "./backend-refusal.js";
28
+ export { AppMethodRefusal, appMethodRefusalCodes, appMethodRefusalStatus, appMethodRefusedReplySchema, appMethodReplySchema, maxAppMethodRefusalMessage, } from "./method-refusal.js";
@@ -0,0 +1,30 @@
1
+ import * as z from "zod";
2
+ import type { Disposer } from "./store.js";
3
+ /** A capability owner's frozen, typed descriptor. Descriptor identity is not authority. */
4
+ export type CapabilityContract<T> = {
5
+ readonly id: string;
6
+ readonly version: number;
7
+ readonly schema: z.ZodType<T>;
8
+ };
9
+ /** Define a capability with the owner's decoder for its live RPC interface. */
10
+ export declare function defineCapability<T>(id: string, version: number, schema: z.ZodType<T>): CapabilityContract<T>;
11
+ /** The App's read-only view of its currently admitted capabilities. */
12
+ export type CapabilityDirectory = {
13
+ /** Changes after a successful grant replacement or disconnection. */
14
+ readonly version: number;
15
+ /** Read the current directory after a change. Does not emit on subscribe. */
16
+ subscribe(listener: () => void): Disposer;
17
+ /** Whether an implementation for this contract is currently admitted. */
18
+ has(contract: CapabilityContract<unknown>): boolean;
19
+ /** The admitted implementation for this contract, or throw a
20
+ * `CAPABILITY_DENIED` AppError when none is granted. */
21
+ require<T>(contract: CapabilityContract<T>): T;
22
+ };
23
+ /** Local capabilities admitted through their owner's typed contract. */
24
+ export type CapabilityRegistry = CapabilityDirectory & {
25
+ /** Admit an implementation for this contract, replacing any previous one
26
+ * and bumping `version` so subscribers can react. */
27
+ provide: <T>(contract: CapabilityContract<T>, implementation: NoInfer<T>) => void;
28
+ };
29
+ /** Register typed local implementations without reopening their erased types. */
30
+ export declare function createCapabilityRegistry(): CapabilityRegistry;
@@ -0,0 +1,50 @@
1
+ import * as Context from "effect/Context";
2
+ import * as Option from "effect/Option";
3
+ import * as z from "zod";
4
+ import { AppError } from "./errors.js";
5
+ import { createReadonlyStore } from "./internal/store.js";
6
+ class DefinedCapability {
7
+ id;
8
+ version;
9
+ #schema;
10
+ constructor(id, version, schema) {
11
+ this.id = id;
12
+ this.version = version;
13
+ this.#schema = schema;
14
+ Object.freeze(this);
15
+ }
16
+ // Only id/version travel with a wire grant; the parser stays with its owner.
17
+ get schema() {
18
+ return this.#schema;
19
+ }
20
+ }
21
+ /** Define a capability with the owner's decoder for its live RPC interface. */
22
+ export function defineCapability(id, version, schema) {
23
+ return new DefinedCapability(id, version, schema);
24
+ }
25
+ /** Register typed local implementations without reopening their erased types. */
26
+ export function createCapabilityRegistry() {
27
+ let services = Context.empty();
28
+ const version = createReadonlyStore(0);
29
+ const key = (contract) => Context.Service(`${contract.id}@${String(contract.version)}`);
30
+ return {
31
+ get version() {
32
+ return version.get();
33
+ },
34
+ subscribe: (listener) => version.subscribe(listener),
35
+ provide: (contract, implementation) => {
36
+ services = Context.add(services, key(contract), implementation);
37
+ version.update(version.get() + 1);
38
+ },
39
+ has: (contract) => Option.isSome(Context.getOption(services, key(contract))),
40
+ require(contract) {
41
+ const implementation = Context.getOption(services, key(contract));
42
+ if (Option.isSome(implementation))
43
+ return implementation.value;
44
+ throw new AppError("The capability is not granted", {
45
+ code: "CAPABILITY_DENIED",
46
+ retryable: false,
47
+ });
48
+ },
49
+ };
50
+ }