@fungi.computer/agar 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # Agar (`@fungi.computer/agar`)
2
2
 
3
3
  Agar defines typed App backends and host capabilities. Most people want
4
- [Oyster](https://docs.fungi.computer/reference/packages/#oyster) to run Apps or
5
- [Shiitake](https://docs.fungi.computer/reference/packages/#shiitake) to run
6
- Agents.
4
+ [Oyster](https://docs.fungi.computer/oyster/) to run Apps or
5
+ [Shiitake](https://docs.fungi.computer/shiitake/) to run Agents.
7
6
 
8
7
  Agar is the App contract for fungi.computer. It defines an App's typed backend
9
8
  methods, connects its frontend to a host through typed capabilities, and gives
@@ -336,10 +335,15 @@ the declared methods separately.
336
335
 
337
336
  ## Connect and release
338
337
 
339
- connectApp takes an exact `expectedHostOrigin` and optional abort signal. It
340
- checks the parent window, origin, protocol and transferred port before returning
341
- an AppRoot. It permits one live connection per frontend page. Do not use a
342
- wildcard origin or a stranger-controlled query parameter as the expected host.
338
+ `connectApp({ host: "parent", signal })` discovers the host through the existing
339
+ parent handshake, without reading a referrer or accepting an origin parameter.
340
+ Serve the App document with CSP `frame-ancestors` pinned to its allowed host.
341
+ The browser checks the entire ancestor chain; without that policy an unrelated
342
+ site embedding the App could become its host. Hub and Oyster supply this policy
343
+ on their App document routes. Only the public, secret-free ready announcement
344
+ uses `"*"`; connection acceptance checks the actual parent Window, its
345
+ non-opaque origin, protocol and single transferred port before returning an
346
+ AppRoot. It permits one live connection per frontend page.
343
347
 
344
348
  The root supplies:
345
349
 
@@ -557,14 +561,18 @@ const dispose = mountApp(root, { name: "Bazaar" }, ({ app }) => (
557
561
  import.meta.hot?.dispose(dispose);
558
562
  ```
559
563
 
560
- `mountApp` authenticates the iframe host using the parsed referrer origin,
561
- applies and subscribes to its theme, and restores the original theme on
564
+ `mountApp` discovers its actual parent through Agar's existing handshake,
565
+ independently of referrer policy. The parent must have a non-opaque origin and
566
+ transfer exactly one port for the validated epoch. Top-level launches remain
567
+ standalone; embedded pages without a valid handshake report `not-hosted` with an
568
+ `AppError` and a visible alert rather than silently becoming standalone. It
569
+ applies and subscribes to the host's theme, and restores the original theme on
562
570
  disconnect or unmount. Its render callback receives `connecting`, `live`,
563
- `standalone` (no host or usable referrer), or `ended` (failed/closed
564
- connection). Only `live` carries an AppRoot. The returned disposer aborts a
565
- pending handshake, releases subscriptions, disposes the connection and unmounts
566
- React. A late connection is disposed too. Existing App-open React performance
567
- marks are emitted at root render and commits.
571
+ `standalone` (a top-level launch), `not-hosted` (an initial connection failure),
572
+ or `ended` (a closed live connection). Only `live` carries an AppRoot. The
573
+ returned disposer aborts a pending handshake, releases subscriptions, disposes
574
+ the connection and unmounts React. A late connection is disposed too. Existing
575
+ App-open React performance marks are emitted at root render and commits.
568
576
 
569
577
  Supply `queryClient` to wrap the App in `QueryClientProvider`. The caller owns
570
578
  that client, its cache policy and grant invalidation; mounting does not clear
@@ -751,6 +759,13 @@ The exported `mediaGenerationDescriptionSchema`,
751
759
  `mediaGenerationJsonReplySchema` and `mediaGenerationReferenceSchema` decode
752
760
  these boundaries with the existing Agar Media grammar.
753
761
 
762
+ Failures may include safe `provider` facts: an allowlisted category and bounded
763
+ code, or `result_download` and an HTTP status. JSON, RPC and
764
+ `MediaGenerationError.provider` preserve the same facts and retry truth.
765
+ `./errors` exposes declaration-safe diagnostic types and decoder helpers; one
766
+ internal Effect schema owns validation. Private provider messages, bodies, URLs
767
+ and causes are not diagnostic fields and are rejected at this boundary.
768
+
754
769
  Attempts retain their canonical request ID when restored or when polling
755
770
  Pending. The shared Agar Pending validator requires an integer `retryAfterMs`
756
771
  from 1000 to 60000. Cancellation aborts the attempt's active local requests;
@@ -761,7 +776,8 @@ the platform DOM types and `ESNext.Disposable`.
761
776
  See the [generated API](docs/api/README.md) for signatures and host ports.
762
777
 
763
778
  `@fungi.computer/agar/computer-contracts` is the runtime-neutral public home for
764
- `workflowGitPassResultSchema` and `WorkflowGitPassResult`. App backend
765
- definitions can validate host-issued Workflow pass metadata without importing
766
- the Computer RPC client. The parser preserves the complete credential-free HTTPS
767
- remote; admission, expiry and revocation remain host authority.
779
+ Computer admission, input, dispatch, timeout and attachment-budget outcomes. App
780
+ backend definitions can validate these outcomes without importing the Computer
781
+ RPC client. Apps request ordinary Computer execution; Workflow Git credential
782
+ issuance, carriage, renewal and revocation stay inside the trusted host and
783
+ native execution boundary, not the App contract.
@@ -3,6 +3,7 @@ import * as z from "zod";
3
3
  export declare const FungiAuthorSchema: z.ZodLazy<z.ZodObject<{
4
4
  id: z.ZodString;
5
5
  name: z.ZodString;
6
+ kind: z.ZodOptional<z.ZodLiteral<"agent">>;
6
7
  }, z.core.$strict>>;
7
8
  /** Who a Session line came from, as the admitting host authenticated them. */
8
9
  export type FungiAuthor = z.infer<typeof FungiAuthorSchema>;
@@ -34,7 +35,9 @@ export declare const FungiMessageMetaSchema: z.ZodObject<{
34
35
  author: z.ZodOptional<z.ZodLazy<z.ZodObject<{
35
36
  id: z.ZodString;
36
37
  name: z.ZodString;
38
+ kind: z.ZodOptional<z.ZodLiteral<"agent">>;
37
39
  }, z.core.$strict>>>;
40
+ sourceRequestId: z.ZodOptional<z.ZodLazy<z.ZodString>>;
38
41
  delegation: z.ZodOptional<z.ZodObject<{
39
42
  ancestors: z.ZodArray<z.ZodLazy<z.ZodString>>;
40
43
  maxDepth: z.ZodNumber;
@@ -68,7 +71,9 @@ export declare function messageAuthor(meta: MessageMetaValue | undefined): Fungi
68
71
  export declare function messageDelegationReply(meta: MessageMetaValue | undefined): FungiDelegationReply | undefined;
69
72
  /** The metadata the host attaches for one authenticated author, as JSON. */
70
73
  export type AuthorMeta = Readonly<{
71
- author: Readonly<FungiAuthor>;
74
+ author: Readonly<{
75
+ [K in keyof FungiAuthor]: Exclude<FungiAuthor[K], undefined>;
76
+ }>;
72
77
  }>;
73
78
  /** The metadata the host attaches for one authenticated author. */
74
79
  export declare function authorMeta(author: FungiAuthor): AuthorMeta;
@@ -3,6 +3,12 @@ import * as z from "zod";
3
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
+ * Present only when the author is another Agent the host admitted a
8
+ * delegated send or reply from; a line's text never sets it. Human
9
+ * authors carry no kind.
10
+ */
11
+ kind: z.literal("agent").optional(),
6
12
  }));
7
13
  const identifier = z.lazy(() => z.string().min(1).max(256));
8
14
  /**
@@ -31,6 +37,8 @@ export const FungiDelegationReplySchema = z.lazy(() => z.strictObject({
31
37
  */
32
38
  export const FungiMessageMetaSchema = z.strictObject({
33
39
  author: FungiAuthorSchema.optional(),
40
+ /** Original caller identity when the host translates its request into an owner key. */
41
+ sourceRequestId: identifier.optional(),
34
42
  /** Host-admitted delegation path and its inherited abuse bound. */
35
43
  delegation: z
36
44
  .strictObject({
@@ -59,5 +67,8 @@ export function messageDelegationReply(meta) {
59
67
  }
60
68
  /** The metadata the host attaches for one authenticated author. */
61
69
  export function authorMeta(author) {
62
- return { author: { id: author.id, name: author.name } };
70
+ const { id, name, kind } = author;
71
+ return kind === undefined
72
+ ? { author: { id, name } }
73
+ : { author: { id, name, kind } };
63
74
  }
@@ -0,0 +1,5 @@
1
+ import { type Approvals } from "@fungi.computer/approvals";
2
+ import { type CapabilityContract } from "./capabilities.js";
3
+ export type { Approvals } from "@fungi.computer/approvals";
4
+ /** Member-bound approval view. Authentication and current rights belong to the host. */
5
+ export declare const approvals: CapabilityContract<Approvals>;
@@ -0,0 +1,36 @@
1
+ import * as Schema from "effect/Schema";
2
+ import * as z from "zod";
3
+ import { ApprovalViewSchema, ApprovalRefusalReasonSchema, AutoApproveInputSchema, } from "@fungi.computer/approvals";
4
+ import { defineCapability } from "./capabilities.js";
5
+ import { rpcDataSchema, rpcHandleSchema, rpcMethod, } from "./internal/rpc-contract.js";
6
+ const refusal = Schema.Struct({
7
+ kind: Schema.Literal("refused"),
8
+ reason: ApprovalRefusalReasonSchema,
9
+ });
10
+ const page = Schema.Struct({
11
+ items: Schema.Array(ApprovalViewSchema),
12
+ nextCursor: Schema.NullOr(Schema.String),
13
+ });
14
+ const answer = Schema.Union([
15
+ Schema.Struct({ kind: Schema.Literal("answered"), view: ApprovalViewSchema }),
16
+ refusal,
17
+ ]);
18
+ const policy = Schema.Union([
19
+ Schema.Struct({
20
+ kind: Schema.Literal("updated"),
21
+ rule: AutoApproveInputSchema,
22
+ }),
23
+ refusal,
24
+ ]);
25
+ // Match the existing storage/Agent capability adapters: the maintained RPC
26
+ // helpers bind native stub receivers, decode data and release result envelopes.
27
+ const data = (schema) => rpcDataSchema(z
28
+ .unknown()
29
+ .transform((value) => Schema.decodeUnknownSync(schema, { onExcessProperty: "error" })(value)));
30
+ /** Member-bound approval view. Authentication and current rights belong to the host. */
31
+ export const approvals = defineCapability("approvals", 1, z.lazy(() => rpcHandleSchema({
32
+ list: rpcMethod(data(page)),
33
+ get: rpcMethod(data(Schema.NullOr(ApprovalViewSchema))),
34
+ answer: rpcMethod(data(answer)),
35
+ setAutoApprove: rpcMethod(data(policy)),
36
+ })));
@@ -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
  });
@@ -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.ZodLazy<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";
@@ -63,11 +54,3 @@ export declare const appComputerExecTimeoutSchema: z.ZodLazy<z.ZodObject<{
63
54
  error: z.ZodLiteral<"timeout">;
64
55
  requestId: z.ZodString;
65
56
  }, z.core.$strict>>;
66
- /** The Workflow Git-pass owner has exactly these existing domain refusals. */
67
- export declare const workflowGitPassRefusalSchema: z.ZodLazy<z.ZodObject<{
68
- error: z.ZodEnum<{
69
- unavailable: "unavailable";
70
- revoked: "revoked";
71
- not_granted: "not_granted";
72
- }>;
73
- }, 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.lazy(() => 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",
@@ -51,7 +36,3 @@ export const appComputerExecTimeoutSchema = z.lazy(() => z.strictObject({
51
36
  error: z.literal("timeout"),
52
37
  requestId: z.string(),
53
38
  }));
54
- /** The Workflow Git-pass owner has exactly these existing domain refusals. */
55
- export const workflowGitPassRefusalSchema = z.lazy(() => z.strictObject({
56
- error: z.enum(["revoked", "not_granted", "unavailable"]),
57
- }));
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", {
package/dist/errors.d.ts CHANGED
@@ -1,6 +1,44 @@
1
+ import * as z from "zod";
2
+ /** Declaration-safe projection of the canonical Media diagnostic grammar. */
3
+ export type MediaProviderDiagnostic = Readonly<{
4
+ /** Allowlisted Workers AI category; never a provider message. */
5
+ category: "InferenceUpstreamError" | "AiInternalError" | "TypeError" | "AbortError" | "TimeoutError" | "Error";
6
+ /** Four- or five-digit provider prefix, or null when absent. */
7
+ code: number | null;
8
+ }> | Readonly<{
9
+ /** A completed video result could not be downloaded. */
10
+ category: "result_download";
11
+ /** Native Response status, including zero. */
12
+ status: number;
13
+ }>;
14
+ /** Decode only safe provider facts, rejecting unmodeled private fields. */
15
+ export declare function decodeMediaProviderDiagnostic(input: unknown): MediaProviderDiagnostic;
16
+ /**
17
+ * Documentable wire projection of the canonical diagnostic grammar.
18
+ * Maintained converters preserve its closed objects and numeric bounds;
19
+ * decoding still returns the declaration-safe public diagnostic type.
20
+ */
21
+ export declare const mediaProviderDiagnosticWireSchema: z.ZodPipe<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodTransform<Readonly<{
22
+ /** Allowlisted Workers AI category; never a provider message. */
23
+ category: "InferenceUpstreamError" | "AiInternalError" | "TypeError" | "AbortError" | "TimeoutError" | "Error";
24
+ /** Four- or five-digit provider prefix, or null when absent. */
25
+ code: number | null;
26
+ }> | Readonly<{
27
+ /** A completed video result could not be downloaded. */
28
+ category: "result_download";
29
+ /** Native Response status, including zero. */
30
+ status: number;
31
+ }>, unknown>>;
32
+ /** Decode an ordinary Error name without retaining arbitrary upstream text. */
33
+ export declare function decodeMediaProviderErrorCategory(input: string | undefined): Exclude<MediaProviderDiagnostic["category"], "result_download">;
1
34
  /** The closed set of reasons an AppError can state; every SDK and
2
35
  * host-reported failure is tagged with exactly one of these codes. */
3
- export type AppErrorCode = "CANCELLED" | "CAPABILITY_DENIED" | "CAPABILITY_REVOKED" | "COMMAND_NOT_DECLARED" | "CONFLICT" | "DISCONNECTED" | "FILE_TOO_LARGE" | "HOST_FAILURE" | "IDENTITY" | "INSUFFICIENT_CREDIT" | "INVALID_ARGUMENT" | "PROTOCOL" | "SAFETY_FILTER_REFUSED" | "SELECTION_CHANGED" | "TIMEOUT";
36
+ export declare const appErrorCodes: readonly ["CANCELLED", "CAPABILITY_DENIED", "CAPABILITY_REVOKED", "COMMAND_NOT_DECLARED", "CONFLICT", "DELIVERY_PENDING", "DISCONNECTED", "FILE_TOO_LARGE", "HOST_FAILURE", "IDENTITY", "INSUFFICIENT_CREDIT", "INVALID_ARGUMENT", "PROTOCOL", "SAFETY_FILTER_REFUSED", "SELECTION_CHANGED", "TIMEOUT"];
37
+ export type AppErrorCode = (typeof appErrorCodes)[number];
38
+ /** One pending-delivery refusal shared by admission and the shell. */
39
+ export type AppDeliveryPending = Readonly<{
40
+ code: Extract<AppErrorCode, "DELIVERY_PENDING">;
41
+ }>;
4
42
  /** Required construction options for an AppError. */
5
43
  export type AppErrorOptions = {
6
44
  readonly cause?: unknown;
package/dist/errors.js CHANGED
@@ -1,3 +1,53 @@
1
+ import * as Schema from "effect/Schema";
2
+ import * as Option from "effect/Option";
3
+ import * as z from "zod";
4
+ import { mediaProviderDiagnosticSchema, mediaProviderErrorCategorySchema, } from "./internal/media-diagnostic.js";
5
+ /** Decode only safe provider facts, rejecting unmodeled private fields. */
6
+ export function decodeMediaProviderDiagnostic(input) {
7
+ // Bidirectional projection check, erased from the Promise declaration graph.
8
+ return Schema.decodeUnknownSync(mediaProviderDiagnosticSchema, {
9
+ onExcessProperty: "error",
10
+ })(input);
11
+ }
12
+ /**
13
+ * Documentable wire projection of the canonical diagnostic grammar.
14
+ * Maintained converters preserve its closed objects and numeric bounds;
15
+ * decoding still returns the declaration-safe public diagnostic type.
16
+ */
17
+ export const mediaProviderDiagnosticWireSchema = z
18
+ .fromJSONSchema(Schema.toJsonSchemaDocument(mediaProviderDiagnosticSchema, {
19
+ onExcessProperty: "error",
20
+ }).schema)
21
+ .transform(decodeMediaProviderDiagnostic);
22
+ /** Decode an ordinary Error name without retaining arbitrary upstream text. */
23
+ export function decodeMediaProviderErrorCategory(input) {
24
+ return (Option.getOrUndefined(Schema.decodeUnknownOption(mediaProviderErrorCategorySchema)(input)) ?? "Error");
25
+ }
26
+ /** The closed set of reasons an AppError can state; every SDK and
27
+ * host-reported failure is tagged with exactly one of these codes. */
28
+ export const appErrorCodes = [
29
+ "CANCELLED",
30
+ "CAPABILITY_DENIED",
31
+ "CAPABILITY_REVOKED",
32
+ "COMMAND_NOT_DECLARED",
33
+ "CONFLICT",
34
+ "DELIVERY_PENDING",
35
+ "DISCONNECTED",
36
+ "FILE_TOO_LARGE",
37
+ "HOST_FAILURE",
38
+ "IDENTITY",
39
+ "INSUFFICIENT_CREDIT",
40
+ "INVALID_ARGUMENT",
41
+ "PROTOCOL",
42
+ "SAFETY_FILTER_REFUSED",
43
+ "SELECTION_CHANGED",
44
+ "TIMEOUT",
45
+ ];
46
+ const describedAppErrorSchema = Schema.Struct({
47
+ name: Schema.Literal("AppError"),
48
+ code: Schema.Literals(appErrorCodes),
49
+ retryable: Schema.Boolean,
50
+ });
1
51
  /** The SDK's one typed failure. Carries a closed `code` naming the failure
2
52
  * kind and an explicit `retryable` verdict, instead of leaving callers to
3
53
  * guess either from the message or exception shape. */
@@ -35,6 +85,10 @@ const errorCopy = {
35
85
  title: "File changed",
36
86
  message: "This file changed elsewhere. Review it before saving again.",
37
87
  },
88
+ DELIVERY_PENDING: {
89
+ title: "App delivery pending",
90
+ message: "The App hasn't finished getting ready. Try opening it again.",
91
+ },
38
92
  DISCONNECTED: {
39
93
  title: "Connection lost",
40
94
  message: "Reopen the App to connect again.",
@@ -90,15 +144,18 @@ const errorCopy = {
90
144
  * prerequisites, so they can accompany a non-retryable operation.
91
145
  */
92
146
  export function describeAppError(cause, overrides) {
93
- if (!(cause instanceof AppError))
147
+ // The picked shell and host can be separate bundles of this same SDK.
148
+ const decoded = Schema.decodeUnknownOption(describedAppErrorSchema)(cause);
149
+ if (Option.isNone(decoded))
94
150
  return { ...errorCopy.UNKNOWN, retryable: false };
95
- const entry = errorCopy[cause.code];
96
- const copy = overrides?.[cause.code] ?? entry;
97
- const action = entry.action ?? (cause.retryable ? "retry" : undefined);
151
+ const error = decoded.value;
152
+ const entry = errorCopy[error.code];
153
+ const copy = overrides?.[error.code] ?? entry;
154
+ const action = entry.action ?? (error.retryable ? "retry" : undefined);
98
155
  const description = {
99
156
  title: copy.title,
100
157
  message: copy.message,
101
- retryable: cause.retryable,
158
+ retryable: error.retryable,
102
159
  };
103
160
  if (action)
104
161
  return { ...description, action };
package/dist/index.d.ts CHANGED
@@ -16,6 +16,8 @@ export type { ConnectAppOptions } from "./connect.js";
16
16
  export { APP_CONNECT_TYPE, APP_PROTOCOL_VERSION, APP_READY_TYPE, } from "./internal/protocol.js";
17
17
  export type { AppConnectMessage, AppReadyMessage, } from "./internal/protocol.js";
18
18
  export { AppError } from "./errors.js";
19
+ export { appDeliveryPendingSchema } from "./internal/schemas.js";
20
+ export type { AppDeliveryPending } from "./errors.js";
19
21
  export { statusActionAllowed, statusText } from "./status.js";
20
22
  export { APP_BROWSER_SAVE_MAX_BYTES, APP_BROWSER_COPY_MAX_BYTES, APP_STATUS_ITEMS_MAX, APP_STATUS_OPTIONS_MAX, APP_STATUS_TEXT_MAX, } from "./ui.js";
21
23
  export { parseStatusAction as parseAppStatusAction, parseStatusItems as parseAppStatusItems, } from "./internal/schemas.js";
@@ -30,3 +32,4 @@ export { APP_NAVIGATION_CONTRACT_VERSION, APP_NAVIGATION_CRUMBS_MAX, APP_NAVIGAT
30
32
  export type { AppBackHandler, AppCrumb, AppHeaderCommand, AppHeaderStatus, AppNavigationActivation, AppNavigationActivationHandler, AppNavigationState, AppWindow, BeforeAppClose, HostNavigation, } from "./window.js";
31
33
  export { appStorage, AppStorageError } from "./storage.js";
32
34
  export type { AppStorage, AppStorageWatch, AppStorageWrite, AppStorageReset, ResolvedAppStorage, } from "./storage.js";
35
+ export { approvals } from "./approvals.js";
package/dist/index.js CHANGED
@@ -8,9 +8,11 @@ export { connectApp } from "./connect.js";
8
8
  /** Data exchanged by iframe and host before the RPC MessagePort takes over. */
9
9
  export { APP_CONNECT_TYPE, APP_PROTOCOL_VERSION, APP_READY_TYPE, } from "./internal/protocol.js";
10
10
  export { AppError } from "./errors.js";
11
+ export { appDeliveryPendingSchema } from "./internal/schemas.js";
11
12
  export { statusActionAllowed, statusText } from "./status.js";
12
13
  export { APP_BROWSER_SAVE_MAX_BYTES, APP_BROWSER_COPY_MAX_BYTES, APP_STATUS_ITEMS_MAX, APP_STATUS_OPTIONS_MAX, APP_STATUS_TEXT_MAX, } from "./ui.js";
13
14
  export { parseStatusAction as parseAppStatusAction, parseStatusItems as parseAppStatusItems, } from "./internal/schemas.js";
14
15
  export { resultRowSchemaV1 } from "@fungi.computer/gills";
15
16
  export { APP_NAVIGATION_CONTRACT_VERSION, APP_NAVIGATION_CRUMBS_MAX, APP_NAVIGATION_LABEL_MAX, APP_NAVIGATION_MENU_MAX, APP_NAVIGATION_PRIMARY_MAX, APP_NAVIGATION_SIBLINGS_MAX, APP_NAVIGATION_STATUS_MAX, APP_NAVIGATION_TITLE_MAX, inertHostNavigation, navigationActivationAllowed, } from "./window.js";
16
17
  export { appStorage, AppStorageError } from "./storage.js";
18
+ export { approvals } from "./approvals.js";
@@ -0,0 +1,11 @@
1
+ import * as Schema from "effect/Schema";
2
+ /** Names emitted by the maintained Workers AI boundary, never arbitrary names. */
3
+ export declare const mediaProviderErrorCategorySchema: Schema.Literals<readonly ["InferenceUpstreamError", "AiInternalError", "TypeError", "AbortError", "TimeoutError", "Error"]>;
4
+ /** One canonical grammar for safe provider facts across Media transports. */
5
+ export declare const mediaProviderDiagnosticSchema: Schema.Union<readonly [Schema.Struct<{
6
+ readonly category: Schema.Literals<readonly ["InferenceUpstreamError", "AiInternalError", "TypeError", "AbortError", "TimeoutError", "Error"]>;
7
+ readonly code: Schema.NullOr<Schema.Int>;
8
+ }>, Schema.Struct<{
9
+ readonly category: Schema.Literal<"result_download">;
10
+ readonly status: Schema.Int;
11
+ }>]>;
@@ -0,0 +1,21 @@
1
+ import * as Schema from "effect/Schema";
2
+ /** Names emitted by the maintained Workers AI boundary, never arbitrary names. */
3
+ export const mediaProviderErrorCategorySchema = Schema.Literals([
4
+ "InferenceUpstreamError",
5
+ "AiInternalError",
6
+ "TypeError",
7
+ "AbortError",
8
+ "TimeoutError",
9
+ "Error",
10
+ ]);
11
+ /** One canonical grammar for safe provider facts across Media transports. */
12
+ export const mediaProviderDiagnosticSchema = Schema.Union([
13
+ Schema.Struct({
14
+ category: mediaProviderErrorCategorySchema,
15
+ code: Schema.NullOr(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 99_999 }))),
16
+ }),
17
+ Schema.Struct({
18
+ category: Schema.Literal("result_download"),
19
+ status: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 599 })),
20
+ }),
21
+ ]);
@@ -12,6 +12,10 @@ import type { CapabilityGrantWire, AppHostReconfigurationWire, CommandInvocation
12
12
  import type { RpcResult } from "./result.js";
13
13
  import { type AppNavigationActivation } from "../window.js";
14
14
  import { type AppConnectMessage, type AppReadyMessage, type CommandInvocationWire, type ConnectRequestWire, type ReservedChord, type VersionedFact } from "./protocol.js";
15
+ /** Effect projection of the error owner's declaration-safe pending refusal. */
16
+ export declare const appDeliveryPendingSchema: Schema.Struct<{
17
+ readonly code: Schema.Literals<readonly ["DELIVERY_PENDING"]>;
18
+ }>;
15
19
  export declare function appInvocation(value: CommandInvocationWire, signal: AbortSignal): Readonly<AppCommandInvocation>;
16
20
  export declare const parseCommand: (input: unknown, options?: import("effect/SchemaAST").ParseOptions | undefined) => Readonly<{
17
21
  readonly id: string;
@@ -275,6 +279,7 @@ export declare const parseNavigationState: (input: unknown, options?: import("ef
275
279
  primary?: readonly Readonly<{
276
280
  id: string;
277
281
  label: string;
282
+ icon?: "play";
278
283
  variant?: "primary" | "outline" | "ghost";
279
284
  tone?: "destructive";
280
285
  disabled?: boolean;
@@ -282,6 +287,7 @@ export declare const parseNavigationState: (input: unknown, options?: import("ef
282
287
  menu?: readonly Readonly<{
283
288
  id: string;
284
289
  label: string;
290
+ icon?: "play";
285
291
  variant?: "primary" | "outline" | "ghost";
286
292
  tone?: "destructive";
287
293
  disabled?: boolean;
@@ -289,6 +295,7 @@ export declare const parseNavigationState: (input: unknown, options?: import("ef
289
295
  status?: readonly Readonly<{
290
296
  id: string;
291
297
  label: string;
298
+ presentation?: "bead";
292
299
  tone?: "neutral" | "info" | "warning" | "success" | "error";
293
300
  }>[];
294
301
  }>;