@fungi.computer/agar 0.1.0 → 0.3.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 (73) hide show
  1. package/README.md +34 -18
  2. package/dist/agent-message-meta.d.ts +21 -16
  3. package/dist/agent-message-meta.js +17 -6
  4. package/dist/agents.d.ts +86 -13
  5. package/dist/agents.js +48 -25
  6. package/dist/approvals.d.ts +5 -0
  7. package/dist/approvals.js +36 -0
  8. package/dist/apps.js +4 -4
  9. package/dist/backend-definition.d.ts +41 -4
  10. package/dist/backend-definition.js +70 -10
  11. package/dist/backend-refusal.d.ts +1 -0
  12. package/dist/backend-refusal.js +1 -0
  13. package/dist/backend.js +4 -4
  14. package/dist/channels.js +4 -4
  15. package/dist/computer-contracts.d.ts +11 -28
  16. package/dist/computer-contracts.js +10 -29
  17. package/dist/computer.js +16 -16
  18. package/dist/connect.d.ts +3 -3
  19. package/dist/connect.js +7 -17
  20. package/dist/errors.d.ts +39 -1
  21. package/dist/errors.js +62 -5
  22. package/dist/game-servers.js +2 -2
  23. package/dist/git-rpc.d.ts +23 -23
  24. package/dist/git-rpc.js +14 -16
  25. package/dist/git.d.ts +9 -9
  26. package/dist/git.js +8 -8
  27. package/dist/host.d.ts +3 -1
  28. package/dist/host.js +5 -1
  29. package/dist/index.d.ts +8 -4
  30. package/dist/index.js +5 -2
  31. package/dist/inference.js +2 -1
  32. package/dist/internal/app-session.js +37 -1
  33. package/dist/internal/media-diagnostic.d.ts +11 -0
  34. package/dist/internal/media-diagnostic.js +21 -0
  35. package/dist/internal/protocol.d.ts +4 -4
  36. package/dist/internal/schemas.d.ts +40 -7
  37. package/dist/internal/schemas.js +122 -35
  38. package/dist/marketplace.js +7 -5
  39. package/dist/media-json.d.ts +14 -0
  40. package/dist/media-json.js +1 -0
  41. package/dist/media-request-id.d.ts +1 -1
  42. package/dist/media-request-id.js +2 -2
  43. package/dist/media.d.ts +11 -0
  44. package/dist/media.js +8 -2
  45. package/dist/method-presentation.d.ts +7 -0
  46. package/dist/method-presentation.js +14 -0
  47. package/dist/method-refusal.d.ts +5 -5
  48. package/dist/method-refusal.js +4 -4
  49. package/dist/query.d.ts +2 -2
  50. package/dist/react-mount.d.ts +7 -1
  51. package/dist/react-mount.js +28 -8
  52. package/dist/react.d.ts +14 -2
  53. package/dist/react.js +40 -4
  54. package/dist/read-copy.d.ts +1 -1
  55. package/dist/read-copy.js +3 -6
  56. package/dist/storage.js +1 -1
  57. package/dist/test-kit/git.js +19 -2
  58. package/dist/window.d.ts +68 -0
  59. package/dist/window.js +31 -0
  60. package/docs/api/README.md +15 -15
  61. package/docs/api/backend-definition.md +83 -9
  62. package/docs/api/backend.md +45 -38
  63. package/docs/api/errors.md +104 -0
  64. package/docs/api/git-rpc.md +35 -35
  65. package/docs/api/git.md +66 -80
  66. package/docs/api/index.md +851 -143
  67. package/docs/api/media-json.md +32 -59
  68. package/docs/api/media-request-id.md +1 -1
  69. package/docs/api/method-presentation.md +21 -0
  70. package/docs/api/modules.md +1 -0
  71. package/docs/api/query.md +1 -1
  72. package/docs/api/react.md +23 -10
  73. package/package.json +16 -6
@@ -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;
@@ -3,6 +3,7 @@ import * as z from "zod";
3
3
  export declare const appBackendRefusalSchema: z.ZodObject<{
4
4
  error: z.ZodLiteral<"App execution unavailable">;
5
5
  reason: z.ZodEnum<{
6
+ disabled: "disabled";
6
7
  exec_isolation_required: "exec_isolation_required";
7
8
  execution_not_admitted: "execution_not_admitted";
8
9
  artifact_unavailable: "artifact_unavailable";
@@ -6,5 +6,6 @@ export const appBackendRefusalSchema = z.strictObject({
6
6
  "exec_isolation_required",
7
7
  "execution_not_admitted",
8
8
  "artifact_unavailable",
9
+ "disabled",
9
10
  ]),
10
11
  });
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
+ })));
@@ -1,13 +1,4 @@
1
1
  import * as z from "zod";
2
- /** Public metadata for one host-admitted Workflow Git pass; parsing never grants authority. */
3
- export declare const workflowGitPassResultSchema: z.ZodObject<{
4
- passId: z.ZodString;
5
- remote: z.ZodURL;
6
- expiresAt: z.ZodNumber;
7
- bearer: z.ZodExactOptional<z.ZodString>;
8
- }, z.core.$strict>;
9
- /** The complete remote stays opaque; the host owns admission, renewal and revocation. */
10
- export type WorkflowGitPassResult = z.output<typeof workflowGitPassResultSchema>;
11
2
  /** Existing attachment budget operation kinds shared by Computer and Media. */
12
3
  export declare const connectorOperationBudgetKindSchema: z.ZodEnum<{
13
4
  write: "write";
@@ -15,7 +6,7 @@ export declare const connectorOperationBudgetKindSchema: z.ZodEnum<{
15
6
  exec: "exec";
16
7
  }>;
17
8
  /** Existing owner budget refusal; it remains a domain result, not a wire envelope. */
18
- export declare const connectorOperationBudgetRefusalSchema: z.ZodObject<{
9
+ export declare const connectorOperationBudgetRefusalSchema: z.ZodLazy<z.ZodObject<{
19
10
  error: z.ZodLiteral<"attachment_budget_exceeded">;
20
11
  kind: z.ZodEnum<{
21
12
  write: "write";
@@ -23,16 +14,16 @@ export declare const connectorOperationBudgetRefusalSchema: z.ZodObject<{
23
14
  exec: "exec";
24
15
  }>;
25
16
  retryAfterMs: z.ZodNumber;
26
- }, z.core.$strict>;
17
+ }, z.core.$strict>>;
27
18
  /** Computer admission refusals returned before native dispatch. */
28
- export declare const appComputerAdmissionRefusalSchema: z.ZodUnion<readonly [z.ZodObject<{
19
+ export declare const appComputerAdmissionRefusalSchema: z.ZodLazy<z.ZodUnion<readonly [z.ZodObject<{
29
20
  error: z.ZodEnum<{
30
21
  unavailable: "unavailable";
31
22
  revoked: "revoked";
32
23
  not_granted: "not_granted";
33
24
  grantor_lost_authority: "grantor_lost_authority";
34
25
  }>;
35
- }, z.core.$strict>, z.ZodObject<{
26
+ }, z.core.$strict>, z.ZodLazy<z.ZodObject<{
36
27
  error: z.ZodLiteral<"attachment_budget_exceeded">;
37
28
  kind: z.ZodEnum<{
38
29
  write: "write";
@@ -40,34 +31,26 @@ export declare const appComputerAdmissionRefusalSchema: z.ZodUnion<readonly [z.Z
40
31
  exec: "exec";
41
32
  }>;
42
33
  retryAfterMs: z.ZodNumber;
43
- }, z.core.$strict>]>;
34
+ }, z.core.$strict>>]>>;
44
35
  /** Existing Computer broker argument refusals. */
45
- export declare const appComputerInputRefusalSchema: z.ZodObject<{
36
+ export declare const appComputerInputRefusalSchema: z.ZodLazy<z.ZodObject<{
46
37
  error: z.ZodEnum<{
47
38
  invalid: "invalid";
48
39
  too_large: "too_large";
49
40
  invalid_path: "invalid_path";
50
41
  }>;
51
- }, z.core.$strict>;
42
+ }, z.core.$strict>>;
52
43
  /** Existing Computer dispatch status/decoding refusals. */
53
- export declare const appComputerDispatchRefusalSchema: z.ZodObject<{
44
+ export declare const appComputerDispatchRefusalSchema: z.ZodLazy<z.ZodObject<{
54
45
  error: z.ZodEnum<{
55
46
  unavailable: "unavailable";
56
47
  conflict: "conflict";
57
48
  not_granted: "not_granted";
58
49
  too_large: "too_large";
59
50
  }>;
60
- }, z.core.$strict>;
51
+ }, z.core.$strict>>;
61
52
  /** An execution wait can time out while its durable request remains identifiable. */
62
- export declare const appComputerExecTimeoutSchema: z.ZodObject<{
53
+ export declare const appComputerExecTimeoutSchema: z.ZodLazy<z.ZodObject<{
63
54
  error: z.ZodLiteral<"timeout">;
64
55
  requestId: z.ZodString;
65
- }, z.core.$strict>;
66
- /** The Workflow Git-pass owner has exactly these existing domain refusals. */
67
- export declare const workflowGitPassRefusalSchema: z.ZodObject<{
68
- error: z.ZodEnum<{
69
- unavailable: "unavailable";
70
- revoked: "revoked";
71
- not_granted: "not_granted";
72
- }>;
73
- }, z.core.$strict>;
56
+ }, z.core.$strict>>;
@@ -1,19 +1,4 @@
1
1
  import * as z from "zod";
2
- /** Public metadata for one host-admitted Workflow Git pass; parsing never grants authority. */
3
- export const workflowGitPassResultSchema = z.strictObject({
4
- passId: z.string().min(1),
5
- remote: z.url({ protocol: /^https$/, abort: true }).refine((value) => {
6
- const remote = new URL(value);
7
- return remote.username === "" && remote.password === "";
8
- }, "Workflow Git remotes must not contain credentials"),
9
- expiresAt: z.number().int().positive(),
10
- /**
11
- * Present when the host admits this pass by `Authorization: Bearer` on the
12
- * remote. It is never part of the remote; a command receives it only as
13
- * command-local Git config, never written to a repository config.
14
- */
15
- bearer: z.string().min(1).exactOptional(),
16
- });
17
2
  /** Existing attachment budget operation kinds shared by Computer and Media. */
18
3
  export const connectorOperationBudgetKindSchema = z.enum([
19
4
  "exec",
@@ -21,13 +6,13 @@ export const connectorOperationBudgetKindSchema = z.enum([
21
6
  "media",
22
7
  ]);
23
8
  /** Existing owner budget refusal; it remains a domain result, not a wire envelope. */
24
- export const connectorOperationBudgetRefusalSchema = z.strictObject({
9
+ export const connectorOperationBudgetRefusalSchema = z.lazy(() => z.strictObject({
25
10
  error: z.literal("attachment_budget_exceeded"),
26
11
  kind: connectorOperationBudgetKindSchema,
27
12
  retryAfterMs: z.number(),
28
- });
13
+ }));
29
14
  /** Computer admission refusals returned before native dispatch. */
30
- export const appComputerAdmissionRefusalSchema = z.union([
15
+ export const appComputerAdmissionRefusalSchema = z.lazy(() => z.union([
31
16
  z.strictObject({
32
17
  error: z.enum([
33
18
  "revoked",
@@ -37,21 +22,17 @@ export const appComputerAdmissionRefusalSchema = z.union([
37
22
  ]),
38
23
  }),
39
24
  connectorOperationBudgetRefusalSchema,
40
- ]);
25
+ ]));
41
26
  /** Existing Computer broker argument refusals. */
42
- export const appComputerInputRefusalSchema = z.strictObject({
27
+ export const appComputerInputRefusalSchema = z.lazy(() => z.strictObject({
43
28
  error: z.enum(["too_large", "invalid_path", "invalid"]),
44
- });
29
+ }));
45
30
  /** Existing Computer dispatch status/decoding refusals. */
46
- export const appComputerDispatchRefusalSchema = z.strictObject({
31
+ export const appComputerDispatchRefusalSchema = z.lazy(() => z.strictObject({
47
32
  error: z.enum(["not_granted", "too_large", "conflict", "unavailable"]),
48
- });
33
+ }));
49
34
  /** An execution wait can time out while its durable request remains identifiable. */
50
- export const appComputerExecTimeoutSchema = z.strictObject({
35
+ export const appComputerExecTimeoutSchema = z.lazy(() => z.strictObject({
51
36
  error: z.literal("timeout"),
52
37
  requestId: z.string(),
53
- });
54
- /** The Workflow Git-pass owner has exactly these existing domain refusals. */
55
- export const workflowGitPassRefusalSchema = z.strictObject({
56
- error: z.enum(["revoked", "not_granted", "unavailable"]),
57
- });
38
+ }));
package/dist/computer.js CHANGED
@@ -15,11 +15,11 @@ export const computerListingSchema = Schema.Struct({
15
15
  })),
16
16
  nextOffset: Schema.NullOr(Schema.Number),
17
17
  });
18
- export const computerList = defineCapability("computer.list", 1, rpcHandleSchema({
18
+ export const computerList = defineCapability("computer.list", 1, z.lazy(() => rpcHandleSchema({
19
19
  list: rpcMethod(rpcDataSchema(z
20
20
  .unknown()
21
21
  .transform((value) => Schema.decodeUnknownSync(computerListingSchema)(value)))),
22
- }));
22
+ })));
23
23
  export const computerRuntimeStateSchema = Schema.Union([
24
24
  Schema.Struct({
25
25
  state: Schema.Literals(["running", "sleeping", "starting"]),
@@ -50,12 +50,12 @@ export const computerInstrumentSnapshotSchema = Schema.Struct({
50
50
  })),
51
51
  internet: computerInternetAccessSchema,
52
52
  });
53
- export const computerInstrumentRead = defineCapability("computer.instrument.read", 1, rpcHandleSchema({
53
+ export const computerInstrumentRead = defineCapability("computer.instrument.read", 1, z.lazy(() => rpcHandleSchema({
54
54
  read: rpcMethod(rpcDataSchema(z
55
55
  .unknown()
56
56
  .transform((value) => Schema.decodeUnknownSync(computerInstrumentSnapshotSchema)(value)))),
57
- }));
58
- export const computerInstrumentControl = defineCapability("computer.instrument.control", 1, rpcHandleSchema({
57
+ })));
58
+ export const computerInstrumentControl = defineCapability("computer.instrument.control", 1, z.lazy(() => rpcHandleSchema({
59
59
  start: rpcMethod(rpcDataSchema(z
60
60
  .unknown()
61
61
  .transform((value) => Schema.decodeUnknownSync(computerRuntimeStateSchema)(value)))),
@@ -65,7 +65,7 @@ export const computerInstrumentControl = defineCapability("computer.instrument.c
65
65
  detachLocal: rpcMethod(rpcDataSchema(z
66
66
  .unknown()
67
67
  .transform((value) => Schema.decodeUnknownSync(Schema.Struct({ detached: Schema.Boolean }))(value)))),
68
- }));
68
+ })));
69
69
  export async function readComputerInstrument(capabilities) {
70
70
  return Schema.decodeUnknownSync(computerInstrumentSnapshotSchema)(await capabilities.require(computerInstrumentRead).read());
71
71
  }
@@ -106,7 +106,7 @@ export const computerPortRemovalSchema = Schema.Struct({
106
106
  removed: Schema.Boolean,
107
107
  });
108
108
  /** The selected Computer's admitted preview URLs and port mutations. */
109
- export const computerPorts = defineCapability("computer.ports", 2, rpcHandleSchema({
109
+ export const computerPorts = defineCapability("computer.ports", 2, z.lazy(() => rpcHandleSchema({
110
110
  list: rpcMethod(rpcDataSchema(z
111
111
  .unknown()
112
112
  .transform((value) => Schema.decodeUnknownSync(computerPortsResultSchema)(value)))),
@@ -116,7 +116,7 @@ export const computerPorts = defineCapability("computer.ports", 2, rpcHandleSche
116
116
  unexpose: rpcMethod(rpcDataSchema(z
117
117
  .unknown()
118
118
  .transform((value) => Schema.decodeUnknownSync(computerPortRemovalSchema)(value)))),
119
- }));
119
+ })));
120
120
  /** Decode exposed ports at the SDK boundary. */
121
121
  export async function listComputerPorts(capabilities) {
122
122
  const result = Schema.decodeUnknownSync(computerPortsResultSchema)(await capabilities.require(computerPorts).list());
@@ -178,7 +178,7 @@ export const computerProcessStartSchema = Schema.Struct({
178
178
  port: Schema.optionalKey(computerPortExposureSchema.fields.port),
179
179
  });
180
180
  /** Read the authoritative process inventory from the selected Computer. */
181
- export const computerProcesses = defineCapability("computer.processes", 1, rpcHandleSchema({
181
+ export const computerProcesses = defineCapability("computer.processes", 1, z.lazy(() => rpcHandleSchema({
182
182
  start: rpcMethod(rpcDataSchema(z
183
183
  .unknown()
184
184
  .transform((value) => Schema.decodeUnknownSync(computerProcessSchema)(value)))),
@@ -188,7 +188,7 @@ export const computerProcesses = defineCapability("computer.processes", 1, rpcHa
188
188
  kill: rpcMethod(rpcDataSchema(z
189
189
  .unknown()
190
190
  .transform((value) => Schema.decodeUnknownSync(computerProcessKillSchema)(value)))),
191
- }));
191
+ })));
192
192
  /** Decode the Computer owner's process inventory at the App boundary. */
193
193
  export async function listComputerProcesses(capabilities) {
194
194
  return Schema.decodeUnknownSync(computerProcessesSchema)(await capabilities.require(computerProcesses).list());
@@ -197,12 +197,12 @@ export async function listComputerProcesses(capabilities) {
197
197
  export async function killComputerProcess(capabilities, id) {
198
198
  return Schema.decodeUnknownSync(computerProcessKillSchema)(await capabilities.require(computerProcesses).kill(id));
199
199
  }
200
- export const computerReadFile = defineCapability("computer.readFile", 1, rpcHandleSchema({
200
+ export const computerReadFile = defineCapability("computer.readFile", 1, z.lazy(() => rpcHandleSchema({
201
201
  open: rpcMethod(rpcHandleSchema({
202
202
  response: rpcMethod(z.instanceof(Response)),
203
203
  close: rpcVoid,
204
204
  })),
205
- }));
205
+ })));
206
206
  /** The caller receives native file bytes; HTTP details stay in the SDK adapter. */
207
207
  export async function readComputerFile(capabilities, input, options) {
208
208
  return await readAppFile(await capabilities.require(computerReadFile).open(input), { ...options, name: input.path.split("/").at(-1) ?? "file" });
@@ -211,13 +211,13 @@ export async function readComputerFile(capabilities, input, options) {
211
211
  export async function listComputerFiles(capabilities, input) {
212
212
  return Schema.decodeUnknownSync(computerListingSchema)(await capabilities.require(computerList).list(input));
213
213
  }
214
- export const computerWriteFile = defineCapability("computer.writeFile", 3, rpcHandleSchema({
214
+ export const computerWriteFile = defineCapability("computer.writeFile", 3, z.lazy(() => rpcHandleSchema({
215
215
  open: rpcMethod(rpcHandleSchema({ write: rpcVoid, close: rpcVoid })),
216
216
  mkdir: rpcVoid,
217
217
  replace: rpcMethod(rpcDataSchema(z
218
218
  .unknown()
219
219
  .transform((value) => Schema.decodeUnknownSync(computerFileReplacementOutcomeSchema)(value)))),
220
- }));
220
+ })));
221
221
  const computerFileReplacementOutcomeSchema = Schema.Union([
222
222
  Schema.Struct({
223
223
  kind: Schema.Literal("replaced"),
@@ -244,7 +244,7 @@ export const computerTerminalInformationSchema = Schema.Struct({
244
244
  uptimeSeconds: Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isGreaterThanOrEqualTo(0))),
245
245
  version: Schema.NonEmptyString.pipe(Schema.check(Schema.isMaxLength(64))),
246
246
  });
247
- export const computerTerminal = defineCapability("computer.terminal", 2, rpcHandleSchema({
247
+ export const computerTerminal = defineCapability("computer.terminal", 2, z.lazy(() => rpcHandleSchema({
248
248
  open: rpcMethod(rpcHandleSchema({
249
249
  information: rpcMethod(rpcDataSchema(z
250
250
  .unknown()
@@ -258,7 +258,7 @@ export const computerTerminal = defineCapability("computer.terminal", 2, rpcHand
258
258
  resize: rpcVoid,
259
259
  close: rpcVoid,
260
260
  })),
261
- }));
261
+ })));
262
262
  /** Open one host-owned terminal and bind its lifetime to the mounted App. */
263
263
  export async function openComputerTerminal(capabilities, size, options = {}) {
264
264
  options.signal?.throwIfAborted();
package/dist/connect.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { AppRoot } from "./root.js";
2
2
  /** Options for connecting this iframe page to its authenticated host. */
3
3
  export type ConnectAppOptions = {
4
- /** The origin the host handshake message must come from; the connection
5
- * is refused when it comes from anywhere else. */
6
- readonly expectedHostOrigin: string;
4
+ /** The parent is the host. Serve the App with CSP `frame-ancestors` pinned
5
+ * to its allowed host so an unrelated site cannot become its parent. */
6
+ readonly host: "parent";
7
7
  /** Cancels a pending connection attempt. */
8
8
  readonly signal?: AbortSignal;
9
9
  };
package/dist/connect.js CHANGED
@@ -14,28 +14,23 @@ let liveConnection = false;
14
14
  class PendingAppConnection {
15
15
  options;
16
16
  #appWindow;
17
- #origin;
18
17
  #port;
19
18
  #settled = false;
20
19
  #readyRetry;
21
20
  #deadline;
22
21
  constructor(options) {
23
22
  this.options = options;
24
- this.#origin = expectedOrigin(options.expectedHostOrigin);
25
23
  this.#appWindow = globalThis.window;
26
24
  }
27
25
  start() {
28
26
  performance.mark("fungi.app-open.agar-connect-start");
29
- const origin = this.#origin;
30
- if (!origin)
31
- return Promise.reject(invalidOrigin());
32
27
  const error = this.#startError();
33
28
  if (error)
34
29
  return Promise.reject(error);
35
30
  liveConnection = true;
36
- return this.#awaitConnection(origin);
31
+ return this.#awaitConnection();
37
32
  }
38
- #awaitConnection(origin) {
33
+ #awaitConnection() {
39
34
  return new Promise((resolve, reject) => {
40
35
  const finish = (error, root) => {
41
36
  this.#finish(error, root, onMessage, onAbort, resolve, reject);
@@ -44,14 +39,14 @@ class PendingAppConnection {
44
39
  finish(cancelled());
45
40
  };
46
41
  const onMessage = (event) => {
47
- this.#onMessage(event, origin, finish, onMessage);
42
+ this.#onMessage(event, finish, onMessage);
48
43
  };
49
44
  this.#appWindow.addEventListener("message", onMessage);
50
45
  this.options.signal?.addEventListener("abort", onAbort, { once: true });
51
46
  const notify = () => {
52
47
  try {
53
48
  performance.mark("fungi.app-open.agar-ready-sent");
54
- this.#appWindow.parent.postMessage({ protocolVersion: APP_PROTOCOL_VERSION, type: APP_READY_TYPE }, origin);
49
+ this.#appWindow.parent.postMessage({ protocolVersion: APP_PROTOCOL_VERSION, type: APP_READY_TYPE }, "*");
55
50
  }
56
51
  catch (cause) {
57
52
  reportCatchFailure("connect.notify-host", cause);
@@ -82,11 +77,12 @@ class PendingAppConnection {
82
77
  }
83
78
  resolve(root);
84
79
  }
85
- #onMessage(event, origin, finish, onMessage) {
80
+ #onMessage(event, finish, onMessage) {
86
81
  if (!isConnectMessage(event.data))
87
82
  return;
88
83
  performance.mark("fungi.app-open.agar-connect-received");
89
- if (event.source !== this.#appWindow.parent || event.origin !== origin) {
84
+ if (event.source !== this.#appWindow.parent ||
85
+ expectedOrigin(event.origin) === undefined) {
90
86
  finish(identityError());
91
87
  return;
92
88
  }
@@ -176,12 +172,6 @@ function identityError() {
176
172
  retryable: false,
177
173
  });
178
174
  }
179
- function invalidOrigin() {
180
- return new AppError("expectedHostOrigin must be an origin", {
181
- code: "INVALID_ARGUMENT",
182
- retryable: false,
183
- });
184
- }
185
175
  const isConnectMessage = Schema.is(Schema.Struct({ type: Schema.Literal(APP_CONNECT_TYPE) }));
186
176
  function notifyError(cause) {
187
177
  return new AppError("Unable to notify the app host", {