esoul-sdk 0.16.0 → 0.18.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 (61) hide show
  1. package/api-reference.md +1227 -10
  2. package/bin/esoul-device-exec.mjs +99 -0
  3. package/dist/db/client-core.d.ts +10 -0
  4. package/dist/db/client-core.js +17 -0
  5. package/dist/db/compile-rules.d.ts +31 -0
  6. package/dist/db/compile-rules.js +67 -0
  7. package/dist/db/memory-client.d.ts +2 -0
  8. package/dist/db/memory-client.js +8 -6
  9. package/dist/db/schema-gen.d.ts +13 -3
  10. package/dist/db/schema-gen.js +59 -13
  11. package/dist/device.d.ts +136 -0
  12. package/dist/device.js +15 -0
  13. package/dist/failed-requests.d.ts +77 -0
  14. package/dist/failed-requests.js +127 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +2 -0
  17. package/dist/machine/capabilities.d.ts +81 -0
  18. package/dist/machine/capabilities.js +211 -0
  19. package/dist/machine/cli.d.ts +2 -0
  20. package/dist/machine/cli.js +232 -0
  21. package/dist/machine/client.d.ts +46 -0
  22. package/dist/machine/client.js +91 -0
  23. package/dist/machine/commands.d.ts +136 -0
  24. package/dist/machine/commands.js +229 -0
  25. package/dist/machine/digest.d.ts +14 -0
  26. package/dist/machine/digest.js +42 -0
  27. package/dist/machine/files.d.ts +188 -0
  28. package/dist/machine/files.js +511 -0
  29. package/dist/machine/grant.d.ts +49 -0
  30. package/dist/machine/grant.js +69 -0
  31. package/dist/machine/index.d.ts +19 -0
  32. package/dist/machine/index.js +19 -0
  33. package/dist/machine/protocol.d.ts +52 -0
  34. package/dist/machine/protocol.js +88 -0
  35. package/dist/machine/runner.d.ts +43 -0
  36. package/dist/machine/runner.js +255 -0
  37. package/dist/machine/service.d.ts +30 -0
  38. package/dist/machine/service.js +189 -0
  39. package/dist/machine/shell.d.ts +65 -0
  40. package/dist/machine/shell.js +474 -0
  41. package/dist/machine/state.d.ts +50 -0
  42. package/dist/machine/state.js +96 -0
  43. package/dist/machine/supervisor.d.ts +54 -0
  44. package/dist/machine/supervisor.js +259 -0
  45. package/dist/machine/wake.d.ts +31 -0
  46. package/dist/machine/wake.js +105 -0
  47. package/dist/manifest.d.ts +444 -0
  48. package/dist/manifest.js +51 -0
  49. package/dist/react.d.ts +110 -0
  50. package/dist/react.js +42 -0
  51. package/dist/server.d.ts +28 -0
  52. package/dist/server.js +20 -0
  53. package/docs/03-events-and-state.md +9 -4
  54. package/docs/05-ui.md +65 -0
  55. package/docs/14-database.md +21 -3
  56. package/docs/18-your-computer.md +235 -0
  57. package/llms-full.txt +333 -6
  58. package/llms.txt +1 -0
  59. package/package.json +10 -4
  60. package/schemas/plugin.schema.json +242 -0
  61. package/scripts/build-api-reference.mjs +1 -0
package/api-reference.md CHANGED
@@ -7,11 +7,11 @@ the complete list it teaches from. In a Forge workbench the source itself is rea
7
7
  Regenerate: `npm run docs:api`.
8
8
 
9
9
  ==============================================================================
10
- ## `esoul-sdk` — 185 exports
10
+ ## `esoul-sdk` — 196 exports
11
11
 
12
12
  Manifest, events, tools, ops, bindings, contracts, charts, access words — what app.tsx and the shared modules import.
13
13
 
14
- ### Functions and values (109)
14
+ ### Functions and values (115)
15
15
 
16
16
  #### `ASSET_APP_MAX_BYTES` — const · src/assets.ts
17
17
 
@@ -189,6 +189,14 @@ Every declared attribute a grant carries, typed; an undeclared or ill-typed one
189
189
  function coerceAttrs(types: Record<string, AttrType>, raw: Record<string, unknown> | undefined | null): Record<string, AttrValue>
190
190
  ```
191
191
 
192
+ #### `COMMAND_NAME_RE` — const · src/machine/commands.ts
193
+
194
+ What a command name may look like: lowercase, starts with a letter, up to 40 characters.
195
+
196
+ ```ts
197
+ const COMMAND_NAME_RE: RegExp
198
+ ```
199
+
192
200
  #### `compareFileNames` — function · src/files.ts
193
201
 
194
202
  Natural name order: "frame_2" before "frame_10".
@@ -245,6 +253,14 @@ What a mounted editor should do with the value the store now holds for the item
245
253
  function decideReconcile<T>(args: { incoming: T; /** What the editor last knew the server to hold (its merge base). */ base: T | undefined; /** What the editor shows right now. */ current: T; equal: (a: T, b: T) => boolean; /** A local save is scheduled and not yet dispatched. */ pending: boolean; /** The user is in the middle of editing (focus, an open field). */ editing: boolean; /** isOlderOwnWrite(...) for the incoming state. */ olderOwn: boolean; /** The editor supplies a three-way merge. */ canMerge: boolean; /** May a merge be applied while the person is editing? Default true. An editor that can only apply by * replacing its whole surface (an input in progress would be lost) sets false: it merges once idle. */ mergeWhileEditing?: boolean; }): ReconcileDecision
246
254
  ```
247
255
 
256
+ #### `DEFAULT_TIMEOUT_SECONDS` — const · src/machine/commands.ts
257
+
258
+ A command's time limit when it declares none: one hour.
259
+
260
+ ```ts
261
+ const DEFAULT_TIMEOUT_SECONDS: 3600
262
+ ```
263
+
248
264
  #### `defineBindingEvent` — function · src/bindings.ts
249
265
 
250
266
  The event definition for one app's slots. `knownSlots` comes from the manifest, so an event naming a slot the app never declared is ignored rather than inventing one.
@@ -277,6 +293,22 @@ Refold-stable id from event payload — the ONLY way a processor may mint an id
277
293
  function deterministicReducerId(prefix: string, seed: unknown): string
278
294
  ```
279
295
 
296
+ #### `deviceBlockProblems` — function · src/machine/commands.ts
297
+
298
+ Every problem with a `device` block, in words an author can act on. Empty = valid.
299
+
300
+ ```ts
301
+ function deviceBlockProblems(block: unknown): string[]
302
+ ```
303
+
304
+ #### `DeviceCommandError` — class · src/machine/commands.ts
305
+
306
+ Why a command was refused: `unknown_command` (not declared), `bad_param` (missing, extra or ill-typed), `bad_spec`. The message says which, in words.
307
+
308
+ ```ts
309
+ class DeviceCommandError extends Error { … }
310
+ ```
311
+
280
312
  #### `fenceMetaOf` — function · src/editor-sync.ts
281
313
 
282
314
  The fence record of `itemKey` in an app state (`__itemRevs`), if any.
@@ -509,6 +541,14 @@ Does an entry pass these listing options? The one rule every provider's listing
509
541
  function matchesListOptions(entry: Pick<FileEntry, "name" | "kind">, opts?: FileListOptions | null): boolean
510
542
  ```
511
543
 
544
+ #### `MAX_TIMEOUT_SECONDS` — const · src/machine/commands.ts
545
+
546
+ The longest time limit a command may declare: seven days.
547
+
548
+ ```ts
549
+ const MAX_TIMEOUT_SECONDS: number
550
+ ```
551
+
512
552
  #### `mergeFields` — function · src/editor-sync.ts
513
553
 
514
554
  Three-way merge of a flat record of fields (a form: to/cc/subject/body; a node's config). A field changed differently on both sides is a conflict → `null`.
@@ -714,7 +754,7 @@ const PluginConnectionSchema: z.ZodEffects<z.ZodObject<{ key: z.ZodString; kind:
714
754
  The zod schema `plugin.json` must satisfy — the sync and `check_app` refuse a manifest that fails it, with the path.
715
755
 
716
756
  ```ts
717
- const PluginManifestSchema: z.ZodObject<{ manifestVersion: z.ZodLiteral<1>; id: z.ZodString; name: z.ZodString; version: z.ZodString; description: z.ZodString; applicationType: z.ZodString; entry: z.ZodString; icon: z.ZodOptional<z.ZodString>; author: z.ZodOptional<z.ZodObject<{ name: z.ZodString; email: z.ZodOptional<z.ZodString>; url: z.ZodOptional<z.ZodString>; }, "strip", z.ZodTypeAny, { name?: string; email?: string; url?: string; }, { name?: string; email?: string; url?: string; }>>; kickableTasks: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodRecord<z.ZodString, z.ZodObject<{ access: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodEnum<["write", "read", "public", "token"]>, z.ZodArray<z.ZodString, "many">]>>>; requires: z.ZodOptional<z.ZodLiteral<"account">>; }, "strict", z.ZodTypeAny, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>>]>>>; webhooks: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>; ops: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodRecord<z.ZodString, z.ZodObject<{ access: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodEnum<["write", "read", "public", "token"]>, z.ZodArray<z.ZodString, "many">]>>>; requires: z.ZodOptional<z.ZodLiteral<"account">>; }, "strict", z.ZodTypeAny, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>>]>>>; routes: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodRecord<z.ZodString, z.ZodObject<{ access: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodEnum<["write", "read", "public", "token"]>, z.ZodArray<z.ZodString, "many">]>>>; requires: z.ZodOptional<z.ZodLiteral<"account">>; }, "strict", z.ZodTypeAny, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>>]>>>; pollTasks: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{ task: z.ZodString; everyMinutes: z.ZodNumber; }, "strip", z.ZodTypeAny, { task?: string; everyMinutes?: number; }, { task?: string; everyMinutes?: number; }>, "many">>>; platformApi: z.ZodOptional<z.ZodObject<{ min: z.ZodString; max: z.ZodOptional<z.ZodString>; }, "strip", z.ZodTypeAny, { min?: string; max?: string; }, { min?: string; max?: string; }>>; scopes: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>; roles: z.ZodOptional<z.ZodEffects<z.ZodObject<{ vocabulary: z.ZodArray<z.ZodString, "many">; default: z.ZodDefault<z.ZodOptional<z.ZodObject<{ owner: z.ZodOptional<z.ZodString>; "member-edit": z.ZodOptional<z.ZodString>; "member-readonly": z.ZodOptional<z.ZodString>; visitor: z.ZodOptional<z.ZodString>; anonymous: z.ZodOptional<z.ZodString>; agent: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }, { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }>>>; describe: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>; attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<["string", "int", "boolean"]>>>; custom: z.ZodOptional<z.ZodObject<{ attributes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{ where: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; hide: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; update: z.ZodOptional<z.ZodObject<{ fields: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; transitions: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { fields?: string[]; transitions?: string; }, { fields?: string[]; transitions?: string; }>>; }, "strict", z.ZodTypeAny, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>>>; ops: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; }, "strict", z.ZodTypeAny, { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }, { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }>>; }, "strict", z.ZodTypeAny, { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }, { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }>, { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }, { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }>>; workspaceTools: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>; connections: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{ key: z.ZodString; kind: z.ZodEnum<["oauth2", "apiKey"]>; label: z.ZodString; description: z.ZodOptional<z.ZodString>; authorizeUrl: z.ZodOptional<z.ZodString>; tokenUrl: z.ZodOptional<z.ZodString>; oauthScopes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; clientIdEnv: z.ZodOptional<z.ZodString>; clientSecretEnv: z.ZodOptional<z.ZodString>; headerNames: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; }, "strict", z.ZodTypeAny, { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }, { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }>, { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }, { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }>, "many">>>; fileSources: z.ZodOptional<z.ZodEffects<z.ZodObject<{ workspace: z.ZodOptional<z.ZodEnum<["read", "readwrite"]>>; providers: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>; write: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; }, "strict", z.ZodTypeAny, { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }, { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }>, { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }, { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }>>; fileProviders: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{ key: z.ZodString; connectionKey: z.ZodString; label: z.ZodString; }, "strict", z.ZodTypeAny, { key?: string; label?: string; connectionKey?: string; }, { key?: string; label?: string; connectionKey?: string; }>, "many">>>; uses: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{ contract: z.ZodString; label: z.ZodOptional<z.ZodString>; optional: z.ZodOptional<z.ZodBoolean>; }, "strict", z.ZodTypeAny, { label?: string; contract?: string; optional?: boolean; }, { label?: string; contract?: string; optional?: boolean; }>>>; provides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{ tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; events: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; models: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; }, "strict", z.ZodTypeAny, { models?: string[]; tools?: string[]; events?: string[]; }, { models?: string[]; tools?: string[]; events?: string[]; }>>>; channel: z.ZodOptional<z.ZodObject<{ topics: z.ZodRecord<z.ZodString, z.ZodObject<{ audience: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"all">, z.ZodLiteral<"viewer">, z.ZodString]>>; mayAddress: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; description: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { description?: string; audience?: string; mayAddress?: string[]; }, { description?: string; audience?: string; mayAddress?: string[]; }>>; }, "strict", z.ZodTypeAny, { topics?: Record<string, { description?: string; audience?: string; mayAddress?: string[]; }>; }, { topics?: Record<string, { description?: string; audience?: string; mayAddress?: string[]; }>; }>>; db: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{ scope: z.ZodOptional<z.ZodEnum<["instance", "workspace", "user"]>>; owner: z.ZodOptional<z.ZodLiteral<"creator">>; fields: z.ZodRecord<z.ZodString, z.ZodString>; sealed: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; unique: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString, "many">, "many">>; indexes: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodObject<{ fields: z.ZodArray<z.ZodString, "many">; kind: z.ZodOptional<z.ZodEnum<["btree", "contains", "text"]>>; }, "strict", z.ZodTypeAny, { kind?: "btree" | "contains" | "text"; fields?: string[]; }, { kind?: "btree" | "contains" | "text"; fields?: string[]; }>]>, "many">>; rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>; }, "strict", z.ZodTypeAny, { owner?: "creator"; fields?: Record<string, string>; scope?: "workspace" | "instance" | "user"; sealed?: string[]; unique?: string[][]; indexes?: (string[] | { kind?: "btree" | "contains" | "text"; fields?: string[]; })[]; rules?: Record<string, unknown>; }, { owner?: "creator"; fields?: Record<string, string>; scope?: "workspace" | "instance" | "user"; sealed?: string[]; unique?: string[][]; indexes?: (string[] | { kind?: "btree" | "contains" | "text"; fields?: string[]; })[]; rules?: Record<string, unknown>; }>>>; }, "strict", z.ZodTypeAny, { roles?: { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }; description?: string; manifestVersion?: 1; id?: string; name?: string; version?: string; applicationType?: string; entry?: string; icon?: string; author?: { name?: string; email?: string; url?: string; }; kickableTasks?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; webhooks?: string[]; ops?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; routes?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; pollTasks?: { task?: string; everyMinutes?: number; }[]; platformApi?: { min?: string; max?: string; }; scopes?: string[]; workspaceTools?: string[]; connections?: { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }[]; fileSources?: { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }; fileProviders?: { key?: string; label?: string; connectionKey?: string; }[]; uses?: Record<string, { label?: string; contract?: string; optional?: boolean; }>; provides?: Record<string, { models?: string[]; tools?: string[]; events?: string[]; }>; channel?: { topics?: Record<string, { description?: string; audience?: string; mayAddress?: string[]; }>; }; db?: Record<string, { owner?: "creator"; fields?: Record<string, string>; scope?: "workspace" | "instance" | "user"; sealed?: string[]; unique?: string[][]; indexes?: (string[] | { kind?: "btree" | "contains" | "text"; fields?: string[]; })[]; rules?: Record<string, unknown>; }>; }, { roles?: { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }; description?: string; manifestVersion?: 1; id?: string; name?: string; version?: string; applicationType?: string; entry?: string; icon?: string; author?: { name?: string; email?: string; url?: string; }; kickableTasks?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; webhooks?: string[]; ops?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; routes?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; pollTasks?: { task?: string; everyMinutes?: number; }[]; platformApi?: { min?: string; max?: string; }; scopes?: string[]; workspaceTools?: string[]; connections?: { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }[]; fileSources?: { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }; fileProviders?: { key?: string; label?: string; connectionKey?: string; }[]; uses?: Record<string, { label?: string; contract?: string; optional?: boolean; }>; provides?: Record<string, { models?: string[]; tools?: string[]; events?: string[]; }>; channel?: { topics?: Record<string, { description?: string; audience?: string; mayAddress?: string[]; }>; }; db?: Record<string, { owner?: "creator"; fields?: Record<string, string>; scope?: "workspace" | "instance" | "user"; sealed?: string[]; unique?: string[][]; indexes?: (string[] | { kind?: "btree" | "contains" | "text"; fields?: string[]; })[]; rules?: Record<string, unknown>; }>; }>
757
+ const PluginManifestSchema: z.ZodObject<{ manifestVersion: z.ZodLiteral<1>; id: z.ZodString; name: z.ZodString; version: z.ZodString; description: z.ZodString; applicationType: z.ZodString; entry: z.ZodString; icon: z.ZodOptional<z.ZodString>; author: z.ZodOptional<z.ZodObject<{ name: z.ZodString; email: z.ZodOptional<z.ZodString>; url: z.ZodOptional<z.ZodString>; }, "strip", z.ZodTypeAny, { name?: string; email?: string; url?: string; }, { name?: string; email?: string; url?: string; }>>; kickableTasks: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodRecord<z.ZodString, z.ZodObject<{ access: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodEnum<["write", "read", "public", "token"]>, z.ZodArray<z.ZodString, "many">]>>>; requires: z.ZodOptional<z.ZodLiteral<"account">>; }, "strict", z.ZodTypeAny, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>>]>>>; webhooks: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>; ops: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodRecord<z.ZodString, z.ZodObject<{ access: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodEnum<["write", "read", "public", "token"]>, z.ZodArray<z.ZodString, "many">]>>>; requires: z.ZodOptional<z.ZodLiteral<"account">>; }, "strict", z.ZodTypeAny, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>>]>>>; routes: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodRecord<z.ZodString, z.ZodObject<{ access: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodEnum<["write", "read", "public", "token"]>, z.ZodArray<z.ZodString, "many">]>>>; requires: z.ZodOptional<z.ZodLiteral<"account">>; }, "strict", z.ZodTypeAny, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>>]>>>; pollTasks: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{ task: z.ZodString; everyMinutes: z.ZodNumber; }, "strip", z.ZodTypeAny, { task?: string; everyMinutes?: number; }, { task?: string; everyMinutes?: number; }>, "many">>>; platformApi: z.ZodOptional<z.ZodObject<{ min: z.ZodString; max: z.ZodOptional<z.ZodString>; }, "strip", z.ZodTypeAny, { min?: string; max?: string; }, { min?: string; max?: string; }>>; scopes: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>; roles: z.ZodOptional<z.ZodEffects<z.ZodObject<{ vocabulary: z.ZodArray<z.ZodString, "many">; default: z.ZodDefault<z.ZodOptional<z.ZodObject<{ owner: z.ZodOptional<z.ZodString>; "member-edit": z.ZodOptional<z.ZodString>; "member-readonly": z.ZodOptional<z.ZodString>; visitor: z.ZodOptional<z.ZodString>; anonymous: z.ZodOptional<z.ZodString>; agent: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }, { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }>>>; describe: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>; attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<["string", "int", "boolean"]>>>; custom: z.ZodOptional<z.ZodObject<{ attributes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{ where: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; hide: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; update: z.ZodOptional<z.ZodObject<{ fields: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; transitions: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { fields?: string[]; transitions?: string; }, { fields?: string[]; transitions?: string; }>>; }, "strict", z.ZodTypeAny, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>>>; ops: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; }, "strict", z.ZodTypeAny, { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }, { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }>>; }, "strict", z.ZodTypeAny, { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }, { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }>, { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }, { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }>>; workspaceTools: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>; device: z.ZodOptional<z.ZodEffects<z.ZodObject<{ commands: z.ZodRecord<z.ZodString, z.ZodObject<{ argv: z.ZodArray<z.ZodString, "many">; params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodEnum<["int", "number", "bool", "string"]>, z.ZodObject<{ type: z.ZodEnum<["int", "number"]>; min: z.ZodOptional<z.ZodNumber>; max: z.ZodOptional<z.ZodNumber>; describe: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { type?: "number" | "int"; min?: number; max?: number; describe?: string; }, { type?: "number" | "int"; min?: number; max?: number; describe?: string; }>, z.ZodObject<{ type: z.ZodLiteral<"string">; pattern: z.ZodOptional<z.ZodString>; maxLength: z.ZodOptional<z.ZodNumber>; allowDash: z.ZodOptional<z.ZodBoolean>; describe: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; }, { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; }>, z.ZodObject<{ type: z.ZodLiteral<"enum">; values: z.ZodArray<z.ZodString, "many">; describe: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { values?: string[]; type?: "enum"; describe?: string; }, { values?: string[]; type?: "enum"; describe?: string; }>, z.ZodObject<{ type: z.ZodLiteral<"bool">; describe: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { type?: "bool"; describe?: string; }, { type?: "bool"; describe?: string; }>]>>>; cwd: z.ZodOptional<z.ZodString>; env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>; config: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; timeoutSeconds: z.ZodOptional<z.ZodNumber>; onRestart: z.ZodOptional<z.ZodEnum<["report", "rerun"]>>; output: z.ZodOptional<z.ZodObject<{ mode: z.ZodOptional<z.ZodEnum<["live", "batched", "final"]>>; flushMs: z.ZodOptional<z.ZodNumber>; maxChunkKb: z.ZodOptional<z.ZodNumber>; keep: z.ZodOptional<z.ZodEnum<["head", "tail", "both"]>>; maxKb: z.ZodOptional<z.ZodNumber>; }, "strict", z.ZodTypeAny, { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }, { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }>>; result: z.ZodOptional<z.ZodEnum<["text", "json"]>>; describe: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { params?: Record<string, "string" | "number" | "int" | "bool" | { type?: "number" | "int"; min?: number; max?: number; describe?: string; } | { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; } | { values?: string[]; type?: "enum"; describe?: string; } | { type?: "bool"; describe?: string; }>; describe?: string; argv?: string[]; cwd?: string; env?: Record<string, string>; config?: string[]; timeoutSeconds?: number; onRestart?: "report" | "rerun"; output?: { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }; result?: "text" | "json"; }, { params?: Record<string, "string" | "number" | "int" | "bool" | { type?: "number" | "int"; min?: number; max?: number; describe?: string; } | { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; } | { values?: string[]; type?: "enum"; describe?: string; } | { type?: "bool"; describe?: string; }>; describe?: string; argv?: string[]; cwd?: string; env?: Record<string, string>; config?: string[]; timeoutSeconds?: number; onRestart?: "report" | "rerun"; output?: { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }; result?: "text" | "json"; }>>; config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{ secret: z.ZodOptional<z.ZodBoolean>; required: z.ZodOptional<z.ZodBoolean>; describe: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { required?: boolean; describe?: string; secret?: boolean; }, { required?: boolean; describe?: string; secret?: boolean; }>>>; redact: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; onAppDeleted: z.ZodOptional<z.ZodEnum<["kill", "finish"]>>; }, "strict", z.ZodTypeAny, { commands?: Record<string, { params?: Record<string, "string" | "number" | "int" | "bool" | { type?: "number" | "int"; min?: number; max?: number; describe?: string; } | { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; } | { values?: string[]; type?: "enum"; describe?: string; } | { type?: "bool"; describe?: string; }>; describe?: string; argv?: string[]; cwd?: string; env?: Record<string, string>; config?: string[]; timeoutSeconds?: number; onRestart?: "report" | "rerun"; output?: { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }; result?: "text" | "json"; }>; config?: Record<string, { required?: boolean; describe?: string; secret?: boolean; }>; redact?: string[]; onAppDeleted?: "kill" | "finish"; }, { commands?: Record<string, { params?: Record<string, "string" | "number" | "int" | "bool" | { type?: "number" | "int"; min?: number; max?: number; describe?: string; } | { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; } | { values?: string[]; type?: "enum"; describe?: string; } | { type?: "bool"; describe?: string; }>; describe?: string; argv?: string[]; cwd?: string; env?: Record<string, string>; config?: string[]; timeoutSeconds?: number; onRestart?: "report" | "rerun"; output?: { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }; result?: "text" | "json"; }>; config?: Record<string, { required?: boolean; describe?: string; secret?: boolean; }>; redact?: string[]; onAppDeleted?: "kill" | "finish"; }>, { commands?: Record<string, { params?: Record<string, "string" | "number" | "int" | "bool" | { type?: "number" | "int"; min?: number; max?: number; describe?: string; } | { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; } | { values?: string[]; type?: "enum"; describe?: string; } | { type?: "bool"; describe?: string; }>; describe?: string; argv?: string[]; cwd?: string; env?: Record<string, string>; config?: string[]; timeoutSeconds?: number; onRestart?: "report" | "rerun"; output?: { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }; result?: "text" | "json"; }>; config?: Record<string, { required?: boolean; describe?: string; secret?: boolean; }>; redact?: string[]; onAppDeleted?: "kill" | "finish"; }, { commands?: Record<string, { params?: Record<string, "string" | "number" | "int" | "bool" | { type?: "number" | "int"; min?: number; max?: number; describe?: string; } | { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; } | { values?: string[]; type?: "enum"; describe?: string; } | { type?: "bool"; describe?: string; }>; describe?: string; argv?: string[]; cwd?: string; env?: Record<string, string>; config?: string[]; timeoutSeconds?: number; onRestart?: "report" | "rerun"; output?: { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }; result?: "text" | "json"; }>; config?: Record<string, { required?: boolean; describe?: string; secret?: boolean; }>; redact?: string[]; onAppDeleted?: "kill" | "finish"; }>>; connections: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{ key: z.ZodString; kind: z.ZodEnum<["oauth2", "apiKey"]>; label: z.ZodString; description: z.ZodOptional<z.ZodString>; authorizeUrl: z.ZodOptional<z.ZodString>; tokenUrl: z.ZodOptional<z.ZodString>; oauthScopes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; clientIdEnv: z.ZodOptional<z.ZodString>; clientSecretEnv: z.ZodOptional<z.ZodString>; headerNames: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; }, "strict", z.ZodTypeAny, { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }, { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }>, { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }, { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }>, "many">>>; fileSources: z.ZodOptional<z.ZodEffects<z.ZodObject<{ workspace: z.ZodOptional<z.ZodEnum<["read", "readwrite"]>>; providers: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>; write: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; }, "strict", z.ZodTypeAny, { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }, { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }>, { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }, { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }>>; fileProviders: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{ key: z.ZodString; connectionKey: z.ZodString; label: z.ZodString; }, "strict", z.ZodTypeAny, { key?: string; label?: string; connectionKey?: string; }, { key?: string; label?: string; connectionKey?: string; }>, "many">>>; uses: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{ contract: z.ZodString; label: z.ZodOptional<z.ZodString>; optional: z.ZodOptional<z.ZodBoolean>; }, "strict", z.ZodTypeAny, { label?: string; contract?: string; optional?: boolean; }, { label?: string; contract?: string; optional?: boolean; }>>>; provides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{ tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; events: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; models: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; }, "strict", z.ZodTypeAny, { models?: string[]; tools?: string[]; events?: string[]; }, { models?: string[]; tools?: string[]; events?: string[]; }>>>; channel: z.ZodOptional<z.ZodObject<{ topics: z.ZodRecord<z.ZodString, z.ZodObject<{ audience: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"all">, z.ZodLiteral<"viewer">, z.ZodString]>>; mayAddress: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; description: z.ZodOptional<z.ZodString>; }, "strict", z.ZodTypeAny, { description?: string; audience?: string; mayAddress?: string[]; }, { description?: string; audience?: string; mayAddress?: string[]; }>>; }, "strict", z.ZodTypeAny, { topics?: Record<string, { description?: string; audience?: string; mayAddress?: string[]; }>; }, { topics?: Record<string, { description?: string; audience?: string; mayAddress?: string[]; }>; }>>; db: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{ scope: z.ZodOptional<z.ZodEnum<["instance", "workspace", "user"]>>; owner: z.ZodOptional<z.ZodLiteral<"creator">>; fields: z.ZodRecord<z.ZodString, z.ZodString>; sealed: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; unique: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString, "many">, "many">>; indexes: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodObject<{ fields: z.ZodArray<z.ZodString, "many">; kind: z.ZodOptional<z.ZodEnum<["btree", "contains", "text"]>>; }, "strict", z.ZodTypeAny, { kind?: "text" | "btree" | "contains"; fields?: string[]; }, { kind?: "text" | "btree" | "contains"; fields?: string[]; }>]>, "many">>; rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>; }, "strict", z.ZodTypeAny, { owner?: "creator"; fields?: Record<string, string>; scope?: "workspace" | "instance" | "user"; sealed?: string[]; unique?: string[][]; indexes?: (string[] | { kind?: "text" | "btree" | "contains"; fields?: string[]; })[]; rules?: Record<string, unknown>; }, { owner?: "creator"; fields?: Record<string, string>; scope?: "workspace" | "instance" | "user"; sealed?: string[]; unique?: string[][]; indexes?: (string[] | { kind?: "text" | "btree" | "contains"; fields?: string[]; })[]; rules?: Record<string, unknown>; }>>>; }, "strict", z.ZodTypeAny, { roles?: { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }; description?: string; manifestVersion?: 1; id?: string; name?: string; version?: string; applicationType?: string; entry?: string; icon?: string; author?: { name?: string; email?: string; url?: string; }; kickableTasks?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; webhooks?: string[]; ops?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; routes?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; pollTasks?: { task?: string; everyMinutes?: number; }[]; platformApi?: { min?: string; max?: string; }; scopes?: string[]; workspaceTools?: string[]; device?: { commands?: Record<string, { params?: Record<string, "string" | "number" | "int" | "bool" | { type?: "number" | "int"; min?: number; max?: number; describe?: string; } | { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; } | { values?: string[]; type?: "enum"; describe?: string; } | { type?: "bool"; describe?: string; }>; describe?: string; argv?: string[]; cwd?: string; env?: Record<string, string>; config?: string[]; timeoutSeconds?: number; onRestart?: "report" | "rerun"; output?: { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }; result?: "text" | "json"; }>; config?: Record<string, { required?: boolean; describe?: string; secret?: boolean; }>; redact?: string[]; onAppDeleted?: "kill" | "finish"; }; connections?: { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }[]; fileSources?: { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }; fileProviders?: { key?: string; label?: string; connectionKey?: string; }[]; uses?: Record<string, { label?: string; contract?: string; optional?: boolean; }>; provides?: Record<string, { models?: string[]; tools?: string[]; events?: string[]; }>; channel?: { topics?: Record<string, { description?: string; audience?: string; mayAddress?: string[]; }>; }; db?: Record<string, { owner?: "creator"; fields?: Record<string, string>; scope?: "workspace" | "instance" | "user"; sealed?: string[]; unique?: string[][]; indexes?: (string[] | { kind?: "text" | "btree" | "contains"; fields?: string[]; })[]; rules?: Record<string, unknown>; }>; }, { roles?: { default?: { owner?: string; "member-edit"?: string; "member-readonly"?: string; visitor?: string; anonymous?: string; agent?: string; }; custom?: { ops?: string[]; attributes?: string[]; models?: Record<string, { where?: string[]; hide?: string[]; update?: { fields?: string[]; transitions?: string; }; }>; }; vocabulary?: string[]; describe?: Record<string, string>; attributes?: Record<string, "string" | "boolean" | "int">; }; description?: string; manifestVersion?: 1; id?: string; name?: string; version?: string; applicationType?: string; entry?: string; icon?: string; author?: { name?: string; email?: string; url?: string; }; kickableTasks?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; webhooks?: string[]; ops?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; routes?: string[] | Record<string, { access?: "write" | "read" | "public" | "token" | string[]; requires?: "account"; }>; pollTasks?: { task?: string; everyMinutes?: number; }[]; platformApi?: { min?: string; max?: string; }; scopes?: string[]; workspaceTools?: string[]; device?: { commands?: Record<string, { params?: Record<string, "string" | "number" | "int" | "bool" | { type?: "number" | "int"; min?: number; max?: number; describe?: string; } | { type?: "string"; maxLength?: number; describe?: string; pattern?: string; allowDash?: boolean; } | { values?: string[]; type?: "enum"; describe?: string; } | { type?: "bool"; describe?: string; }>; describe?: string; argv?: string[]; cwd?: string; env?: Record<string, string>; config?: string[]; timeoutSeconds?: number; onRestart?: "report" | "rerun"; output?: { mode?: "live" | "batched" | "final"; flushMs?: number; maxChunkKb?: number; keep?: "head" | "tail" | "both"; maxKb?: number; }; result?: "text" | "json"; }>; config?: Record<string, { required?: boolean; describe?: string; secret?: boolean; }>; redact?: string[]; onAppDeleted?: "kill" | "finish"; }; connections?: { key?: string; kind?: "oauth2" | "apiKey"; label?: string; description?: string; authorizeUrl?: string; tokenUrl?: string; oauthScopes?: string[]; clientIdEnv?: string; clientSecretEnv?: string; headerNames?: string[]; }[]; fileSources?: { write?: string[]; workspace?: "read" | "readwrite"; providers?: string[]; }; fileProviders?: { key?: string; label?: string; connectionKey?: string; }[]; uses?: Record<string, { label?: string; contract?: string; optional?: boolean; }>; provides?: Record<string, { models?: string[]; tools?: string[]; events?: string[]; }>; channel?: { topics?: Record<string, { description?: string; audience?: string; mayAddress?: string[]; }>; }; db?: Record<string, { owner?: "creator"; fields?: Record<string, string>; scope?: "workspace" | "instance" | "user"; sealed?: string[]; unique?: string[][]; indexes?: (string[] | { kind?: "text" | "btree" | "contains"; fields?: string[]; })[]; rules?: Record<string, unknown>; }>; }>
718
758
  ```
719
759
 
720
760
  #### `PluginRolesSchema` — const · src/manifest.ts
@@ -765,6 +805,14 @@ The reducer behind `plugin/binding_set`. Deterministic: setting a slot replaces
765
805
  function reduceBinding( current: BindingMap, event: { slot?: unknown; nodeId?: unknown; applicationType?: unknown; via?: unknown; at?: unknown }, knownSlots: readonly string[], ): BindingMap
766
806
  ```
767
807
 
808
+ #### `renderCommand` — function · src/machine/commands.ts
809
+
810
+ Render one declared command. Throws `DeviceCommandError` with a sentence for anything undeclared or ill-typed.
811
+
812
+ ```ts
813
+ function renderCommand(block: DeviceBlock, name: string, params: Record<string, unknown> = {}): RenderedCommand
814
+ ```
815
+
768
816
  #### `resolveAppRole` — function · src/roles.ts
769
817
 
770
818
  The app's word for this caller.
@@ -885,7 +933,7 @@ A manifest with one entry removed (the bytes stay; they may be shared). Pure.
885
933
  function withoutAsset(assets: AssetsManifest | null | undefined, pluginId: string, name: string): AssetsManifest
886
934
  ```
887
935
 
888
- ### Types (76)
936
+ ### Types (81)
889
937
 
890
938
  #### `AgentRunToolContext` — interface · src/types.ts
891
939
 
@@ -1191,6 +1239,23 @@ interface CollapsibleConfig {
1191
1239
  }
1192
1240
  ```
1193
1241
 
1242
+ #### `CommandSpec` — interface · src/machine/commands.ts
1243
+
1244
+ ```ts
1245
+ interface CommandSpec {
1246
+ argv: string[];
1247
+ params?: Record<string, ParamSpec>;
1248
+ cwd?: string;
1249
+ env?: Record<string, string>;
1250
+ config?: string[];
1251
+ timeoutSeconds?: number;
1252
+ onRestart?: "report" | "rerun";
1253
+ output?: OutputSpec;
1254
+ result?: "text" | "json";
1255
+ describe?: string;
1256
+ }
1257
+ ```
1258
+
1194
1259
  #### `CompiledCustomRole` — interface · src/db/custom-roles.ts
1195
1260
 
1196
1261
  ```ts
@@ -1211,6 +1276,16 @@ interface CompiledCustomRole {
1211
1276
  }
1212
1277
  ```
1213
1278
 
1279
+ #### `ConfigKeySpec` — interface · src/machine/commands.ts
1280
+
1281
+ ```ts
1282
+ interface ConfigKeySpec {
1283
+ secret?: boolean;
1284
+ required?: boolean;
1285
+ describe?: string;
1286
+ }
1287
+ ```
1288
+
1214
1289
  #### `ContractDef` — interface · src/bindings.ts
1215
1290
 
1216
1291
  What a contract REQUIRES of whoever claims it. The platform ships these; an app may publish its own.
@@ -1321,6 +1396,17 @@ interface DerivedTool extends ApplicationTools {
1321
1396
  }
1322
1397
  ```
1323
1398
 
1399
+ #### `DeviceBlock` — interface · src/machine/commands.ts
1400
+
1401
+ ```ts
1402
+ interface DeviceBlock {
1403
+ commands: Record<string, CommandSpec>;
1404
+ config?: Record<string, ConfigKeySpec>;
1405
+ redact?: string[];
1406
+ onAppDeleted?: "kill" | "finish";
1407
+ }
1408
+ ```
1409
+
1324
1410
  #### `EventData` — interface · src/types.ts
1325
1411
 
1326
1412
  ```ts
@@ -1635,6 +1721,22 @@ interface OpToolConfig<S extends OpInput, R> {
1635
1721
  }
1636
1722
  ```
1637
1723
 
1724
+ #### `ParamSpec` — type · src/machine/commands.ts
1725
+
1726
+ DECLARED COMMANDS — the wall between an app and the person's computer.
1727
+
1728
+ ```ts
1729
+ type ParamSpec =
1730
+ | "int"
1731
+ | "number"
1732
+ | "bool"
1733
+ | "string"
1734
+ | { type: "int" | "number"; min?: number; max?: number; describe?: string }
1735
+ | { type: "string"; pattern?: string; maxLength?: number; allowDash?: boolean; describe?: string }
1736
+ | { type: "enum"; values: string[]; describe?: string }
1737
+ | { type: "bool"; describe?: string };
1738
+ ```
1739
+
1638
1740
  #### `ParsedSourceId` — type · src/files.ts
1639
1741
 
1640
1742
  ```ts
@@ -1733,6 +1835,20 @@ type ReconcileDecision =
1733
1835
  | { kind: "adopt" };
1734
1836
  ```
1735
1837
 
1838
+ #### `RenderedCommand` — interface · src/machine/commands.ts
1839
+
1840
+ ```ts
1841
+ interface RenderedCommand {
1842
+ name: string;
1843
+ argv: string[];
1844
+ cwd?: string;
1845
+ env: Record<string, string>;
1846
+ config: string[];
1847
+ timeoutSeconds: number;
1848
+ onRestart: "report" | "rerun";
1849
+ }
1850
+ ```
1851
+
1736
1852
  #### `RequiredStateShape` — interface · src/helpers.ts
1737
1853
 
1738
1854
  Fields whose ABSENCE means "did not load" (they are seeded at genesis).
@@ -1810,11 +1926,11 @@ interface UsesDecl {
1810
1926
  ```
1811
1927
 
1812
1928
  ==============================================================================
1813
- ## `esoul-sdk/server` — 67 exports
1929
+ ## `esoul-sdk/server` — 77 exports
1814
1930
 
1815
1931
  Server code only (server.ts, ops, routes, tasks): the viewer, the app's database, files, connections, machines, charts, route tokens.
1816
1932
 
1817
- ### Functions and values (27)
1933
+ ### Functions and values (29)
1818
1934
 
1819
1935
  #### `APPROVAL_WAIT` — const · src/computer.ts
1820
1936
 
@@ -1848,6 +1964,22 @@ COMPOSE A ROLE, as the owner: a name of its own, a base word from the vocabulary
1848
1964
  function defineAppRole( _ctx: { pluginId: string; workspaceId: string; nodeId: string; viewer: PluginViewer }, _definition: CustomRoleDefinitionRaw, ): Promise<{ ok: boolean; name?: string; error?: string }>
1849
1965
  ```
1850
1966
 
1967
+ #### `DEVICE_RUN_TERMINAL` — const · src/device.ts
1968
+
1969
+ The statuses a run ends in: done, expired, cancelled, lost.
1970
+
1971
+ ```ts
1972
+ const DEVICE_RUN_TERMINAL: readonly DeviceRunStatus[]
1973
+ ```
1974
+
1975
+ #### `devices` — function · src/server.ts
1976
+
1977
+ PARTS OF THE APP THAT RUN ON THE PERSON'S COMPUTER (plugin-device-arm.md). The app declares its commands in plugin.json `device.commands`; the owner connects a computer with <ConnectComputer/> (esoul-sdk/react) and approves that list; then server code runs a declared command by name:
1978
+
1979
+ ```ts
1980
+ function devices(_ctx: { pluginId: string; nodeId: string; viewer?: { canWrite: boolean; kind: string; userId: string | null } }): Devices
1981
+ ```
1982
+
1851
1983
  #### `emitPluginAppEvent` — function · src/server.ts
1852
1984
 
1853
1985
  Cross-app events: dispatch the TARGET app's own events through the platform spine (target's dataCreator mints; triggers fire; every event is actor-stamped `{kind:"plugin", pluginId, sourceNodeId}`). Same-workspace only. HOST-ONLY.
@@ -2032,7 +2164,7 @@ WHO IS THIS, IN WORDS. The caller's own name and email, for an app with a reason
2032
2164
  function viewerProfile(_viewer: PluginViewer): Promise<ViewerProfile | null>
2033
2165
  ```
2034
2166
 
2035
- ### Types (40)
2167
+ ### Types (48)
2036
2168
 
2037
2169
  #### `AppRolePerson` — interface · src/server.ts
2038
2170
 
@@ -2263,6 +2395,94 @@ interface CustomRolesEnvelope {
2263
2395
  }
2264
2396
  ```
2265
2397
 
2398
+ #### `DeviceRunOptions` — interface · src/device.ts
2399
+
2400
+ ```ts
2401
+ interface DeviceRunOptions {
2402
+ linkId?: string;
2403
+ expiresInSeconds?: number;
2404
+ }
2405
+ ```
2406
+
2407
+ #### `DeviceRunStarted` — interface · src/device.ts
2408
+
2409
+ ```ts
2410
+ interface DeviceRunStarted {
2411
+ commandId: string;
2412
+ status: DeviceRunStatus;
2413
+ machineOnline: boolean;
2414
+ expiresAt: number;
2415
+ }
2416
+ ```
2417
+
2418
+ #### `DeviceRunStatus` — type · src/device.ts
2419
+
2420
+ Types for `devices(ctx)` — parts of an app that run on the person's own computer (plugin-device-arm.md §10–§11). The functions live in server.ts (host-only); the hooks and <ConnectComputer/> in react.ts.
2421
+
2422
+ ```ts
2423
+ type DeviceRunStatus = "queued" | "claimed" | "running" | "done" | "expired" | "cancelled" | "lost";
2424
+ ```
2425
+
2426
+ #### `DeviceRunView` — interface · src/device.ts
2427
+
2428
+ ```ts
2429
+ interface DeviceRunView {
2430
+ commandId: string;
2431
+ linkId: string;
2432
+ machineId: string;
2433
+ capability?: string;
2434
+ agent?: string | null;
2435
+ name: string;
2436
+ params: Record<string, unknown>;
2437
+ argv: string[];
2438
+ status: DeviceRunStatus;
2439
+ exitCode: number | null;
2440
+ signal: string | null;
2441
+ reason: string | null;
2442
+ output?: string;
2443
+ tail?: string;
2444
+ result?: unknown;
2445
+ resultError?: string | null;
2446
+ outputMode?: "live" | "batched" | "final";
2447
+ outputBytes: number;
2448
+ outputSeq: number;
2449
+ truncated: boolean;
2450
+ createdAt: number;
2451
+ startedAt: number | null;
2452
+ endedAt: number | null;
2453
+ expiresAt: number;
2454
+ }
2455
+ ```
2456
+
2457
+ #### `Devices` — interface · src/device.ts
2458
+
2459
+ ```ts
2460
+ interface Devices {
2461
+ list(): Promise<DeviceSummary[]>;
2462
+ run(name: string, params?: Record<string, unknown>, opts?: DeviceRunOptions): Promise<DeviceRunStarted>;
2463
+ get(commandId: string): Promise<DeviceRunView>;
2464
+ wait(commandId: string, opts?: { timeoutSeconds?: number }): Promise<DeviceRunView>;
2465
+ cancel(commandId: string): Promise<{ status: string }>;
2466
+ sh(line: string, opts: { agent: string; stdin?: string; linkId?: string; waitSeconds?: number }): Promise<ShellResult & { commandId: string; status: DeviceRunStatus }>;
2467
+ }
2468
+ ```
2469
+
2470
+ #### `DeviceSummary` — interface · src/device.ts
2471
+
2472
+ One computer connected to this app instance.
2473
+
2474
+ ```ts
2475
+ interface DeviceSummary {
2476
+ linkId: string;
2477
+ status: "active" | "suspended";
2478
+ reason: string | null;
2479
+ commands: string[];
2480
+ pendingApproval: { added: string[]; changed: string[] };
2481
+ machine: { machineId: string; hostname: string; os: string; arch: string | null; runtimeVersion: string | null; lastSeenAt: number | null; online: boolean } | null;
2482
+ workspace?: WorkspaceGrant | null;
2483
+ }
2484
+ ```
2485
+
2266
2486
  #### `EmitPluginAppEventArgs` — interface · src/server.ts
2267
2487
 
2268
2488
  ```ts
@@ -2561,6 +2781,22 @@ interface RunOptions {
2561
2781
  }
2562
2782
  ```
2563
2783
 
2784
+ #### `ShellResult` — interface · src/device.ts
2785
+
2786
+ One line of the shared command line, answered (esoul-sdk/machine shell.ts): `text` is what a terminal would print (what an agent reads), `data` the same answer structured, `opId` the journal entry to `undo`.
2787
+
2788
+ ```ts
2789
+ interface ShellResult {
2790
+ ok: boolean;
2791
+ text: string;
2792
+ data?: unknown;
2793
+ exitCode?: number | null;
2794
+ error?: string;
2795
+ opId?: string;
2796
+ cwd: string;
2797
+ }
2798
+ ```
2799
+
2564
2800
  #### `ToEndOptions` — interface · src/computer.ts
2565
2801
 
2566
2802
  ```ts
@@ -2582,12 +2818,44 @@ interface ViewerProfile {
2582
2818
  }
2583
2819
  ```
2584
2820
 
2821
+ #### `WorkspaceGrant` — type · src/device.ts
2822
+
2823
+ What an app's agents may reach on a computer through the shared command line.
2824
+
2825
+ ```ts
2826
+ type WorkspaceGrant = { scope: "folder"; root: string } | { scope: "computer" };
2827
+ ```
2828
+
2585
2829
  ==============================================================================
2586
- ## `esoul-sdk/react` — 46 exports
2830
+ ## `esoul-sdk/react` — 64 exports
2587
2831
 
2588
2832
  The app's UI: hooks for the viewer, the app's state, realtime, workspace files and tools.
2589
2833
 
2590
- ### Functions and values (22)
2834
+ ### Functions and values (33)
2835
+
2836
+ #### `ConnectComputer` — function · src/react.ts
2837
+
2838
+ The connect panel for an app's config page: the computers already connected (online, last seen, Disconnect, Approve new commands) and "Connect a computer" — one command to paste in a terminal, the code the computer shows, Approve. Linux and macOS, Node 22+.
2839
+
2840
+ ```ts
2841
+ function ConnectComputer(_props: { workspaceId: string; nodeId: string; title?: string; className?: string }): any
2842
+ ```
2843
+
2844
+ #### `describeFailure` — function · src/failed-requests.ts
2845
+
2846
+ Words and retryability for a thrown request: `PluginCallError` carries status + code; a fetch that never reached the server throws a TypeError.
2847
+
2848
+ ```ts
2849
+ function describeFailure(error: unknown): { why: string; retryable: boolean }
2850
+ ```
2851
+
2852
+ #### `dismissFailedRequest` — function · src/failed-requests.ts
2853
+
2854
+ Take a request off the banner — it landed after all, or the person dismissed it.
2855
+
2856
+ ```ts
2857
+ function dismissFailedRequest(key: string): void
2858
+ ```
2591
2859
 
2592
2860
  #### `FilesBrowseError` — class · src/react.ts
2593
2861
 
@@ -2605,6 +2873,14 @@ A polygon labeller for one image — the Explorer's mask editor as a component:
2605
2873
  function ImageLabeler(_props: ImageLabelerProps): any
2606
2874
  ```
2607
2875
 
2876
+ #### `isRetryableFailure` — function · src/failed-requests.ts
2877
+
2878
+ Is this failure worth a Try again? A lost connection, a timeout, a server error or a rate limit — yes. A refusal (the server read the request and said no, with a reason) — retrying sends the same no; the app shows the reason instead.
2879
+
2880
+ ```ts
2881
+ function isRetryableFailure(r: { reason?: string; status?: number }): boolean
2882
+ ```
2883
+
2608
2884
  #### `listFileEntries` — function · src/react.ts
2609
2885
 
2610
2886
  One page of a folder, as a plain call (no hook).
@@ -2629,6 +2905,14 @@ Folders matching what is being typed: names that START with it, then names that
2629
2905
  function rankFolderSuggestions(entries: FileEntry[], partial: string, limit = 50): FileEntry[]
2630
2906
  ```
2631
2907
 
2908
+ #### `reportFailedRequest` — function · src/failed-requests.ts
2909
+
2910
+ Report a request that failed after the screen already showed it; the banner names it (and offers Try again when `retry` is given). The same `key` replaces, never stacks.
2911
+
2912
+ ```ts
2913
+ function reportFailedRequest(r: Omit<FailedRequest, "at" | "retrying"> & { at?: number }): void
2914
+ ```
2915
+
2632
2916
  #### `resolveFilePath` — function · src/react.ts
2633
2917
 
2634
2918
  Walk "a/b/c" from the source's root by exact names, as a plain call.
@@ -2637,6 +2921,14 @@ Walk "a/b/c" from the source's root by exact names, as a plain call.
2637
2921
  function resolveFilePath(_workspaceId: string, _sourceId: string, _path: string): Promise<ResolvedFilePath>
2638
2922
  ```
2639
2923
 
2924
+ #### `runOptimistic` — function · src/failed-requests.ts
2925
+
2926
+ The optimistic-UI rule in one call. `apply` puts the result on screen at once; `send` does the work; on success any earlier failure of the same `key` clears. On failure `revert` puts the screen back and the request is reported: a connection loss, timeout or server error gets Try again (which runs the whole thing again, apply included), a refusal shows its reason only — retrying would send the same no.
2927
+
2928
+ ```ts
2929
+ async function runOptimistic<T>(r: { key: string; what: string; apply?: () => void; revert?: () => void; send: () => Promise<T>; }): Promise<{ ok: true; result: T } | { ok: false; error: unknown }>
2930
+ ```
2931
+
2640
2932
  #### `splitTypedPath` — function · src/react.ts
2641
2933
 
2642
2934
  "sheets/quality/Ty" → `{ parentPath: "sheets/quality", partial: "Ty" }`; a trailing "/" means "inside it". Pure.
@@ -2653,6 +2945,30 @@ True when the current viewer may mutate this workspace.
2653
2945
  function useAppCanEdit(): boolean
2654
2946
  ```
2655
2947
 
2948
+ #### `useDeviceActions` — function · src/react.ts
2949
+
2950
+ The owner's buttons from the UI: run a declared command, stop a run, disconnect a computer, approve an update's new commands. Needs edit access.
2951
+
2952
+ ```ts
2953
+ function useDeviceActions(_where: { nodeId: string }): { run(name: string, params?: Record<string, unknown>, opts?: { linkId?: string; expiresInSeconds?: number }): Promise<DeviceRunStarted>; cancel(commandId: string): Promise<{ status: string }>; disconnect(linkId: string): Promise<{ ok: boolean }>; approveUpdate(linkId: string): Promise<unknown>; recent(limit?: number): Promise<{ commands: DeviceRunView[] }>; /** What the app's agents may reach on that computer: none, its folder, or the whole computer. */ setWorkspace(linkId: string, workspace: "none" | "folder" | "computer"): Promise<unknown>; /** One line of the shared command line as a named agent; `result` is the answer once it ends. */ sh(line: string, opts: { agent: string; stdin?: string; linkId?: string; waitSeconds?: number }): Promise<DeviceRunView & { result?: import("./device.js").ShellResult }>; }
2954
+ ```
2955
+
2956
+ #### `useDeviceRun` — function · src/react.ts
2957
+
2958
+ One run, live: status as it changes, output as it streams. `finished` once it ended.
2959
+
2960
+ ```ts
2961
+ function useDeviceRun(_where: { workspaceId: string; nodeId: string; commandId: string | null | undefined }): { run: DeviceRunView | null; error: string | null; refetch: () => Promise<void>; finished: boolean }
2962
+ ```
2963
+
2964
+ #### `useDevices` — function · src/react.ts
2965
+
2966
+ The computers connected to this app instance, live (presence refreshes every 20 s).
2967
+
2968
+ ```ts
2969
+ function useDevices(_where: { workspaceId: string; nodeId: string }): { devices: DeviceSummary[] | null; error: string | null; refresh: () => Promise<void> }
2970
+ ```
2971
+
2656
2972
  #### `useFileSourceEntries` — function · src/react.ts
2657
2973
 
2658
2974
  One folder's listing; a failing source reports error/errorKind, never []. `opts` narrows AT THE SOURCE — `{ only: "folder" }`, `{ nameContains }`, `{ orderBy: "name", pageSize: 1000 }` — and `loadMore()` appends the next page.
@@ -2685,6 +3001,14 @@ A folder box with autocomplete: give it the text being typed ("sheets/quality_da
2685
3001
  function useFolderAutocomplete(_workspaceId: string | null, _sourceId: string | null, _text: string): FolderAutocompleteState
2686
3002
  ```
2687
3003
 
3004
+ #### `useMyComputers` — function · src/react.ts
3005
+
3006
+ The account's computers (computers belong to the person, not to an app; an app gets one by assignment). `assign(machineId, nodeId)` gives a computer to an app (approving its declared commands); `disconnect(machineId)` removes a computer from the account entirely.
3007
+
3008
+ ```ts
3009
+ function useMyComputers(): { computers: MyComputer[] | null; error: string | null; refresh: () => Promise<void>; assign(machineId: string, nodeId: string): Promise<{ linkId: string }>; disconnect(machineId: string): Promise<{ ok: boolean }> }
3010
+ ```
3011
+
2688
3012
  #### `usePluginCurrentChatId` — function · src/react.ts
2689
3013
 
2690
3014
  Current open chat id, for `chatIdSource` attribution.
@@ -2765,7 +3089,88 @@ Invoke tools of OTHER apps in the workspace (append a spreadsheet row, add a cal
2765
3089
  function useWorkspaceTools(_identity: { workspaceId: string; nodeId: string }): WorkspaceTools
2766
3090
  ```
2767
3091
 
2768
- ### Types (24)
3092
+ #### `YourComputers` — function · src/react.ts
3093
+
3094
+ The account's computers as a panel: online, the apps each serves, Disconnect.
3095
+
3096
+ ```ts
3097
+ function YourComputers(_props: { className?: string }): any
3098
+ ```
3099
+
3100
+ ### Types (31)
3101
+
3102
+ #### `DeviceRunStarted` — interface · src/device.ts
3103
+
3104
+ ```ts
3105
+ interface DeviceRunStarted {
3106
+ commandId: string;
3107
+ status: DeviceRunStatus;
3108
+ machineOnline: boolean;
3109
+ expiresAt: number;
3110
+ }
3111
+ ```
3112
+
3113
+ #### `DeviceRunView` — interface · src/device.ts
3114
+
3115
+ ```ts
3116
+ interface DeviceRunView {
3117
+ commandId: string;
3118
+ linkId: string;
3119
+ machineId: string;
3120
+ capability?: string;
3121
+ agent?: string | null;
3122
+ name: string;
3123
+ params: Record<string, unknown>;
3124
+ argv: string[];
3125
+ status: DeviceRunStatus;
3126
+ exitCode: number | null;
3127
+ signal: string | null;
3128
+ reason: string | null;
3129
+ output?: string;
3130
+ tail?: string;
3131
+ result?: unknown;
3132
+ resultError?: string | null;
3133
+ outputMode?: "live" | "batched" | "final";
3134
+ outputBytes: number;
3135
+ outputSeq: number;
3136
+ truncated: boolean;
3137
+ createdAt: number;
3138
+ startedAt: number | null;
3139
+ endedAt: number | null;
3140
+ expiresAt: number;
3141
+ }
3142
+ ```
3143
+
3144
+ #### `DeviceSummary` — interface · src/device.ts
3145
+
3146
+ One computer connected to this app instance.
3147
+
3148
+ ```ts
3149
+ interface DeviceSummary {
3150
+ linkId: string;
3151
+ status: "active" | "suspended";
3152
+ reason: string | null;
3153
+ commands: string[];
3154
+ pendingApproval: { added: string[]; changed: string[] };
3155
+ machine: { machineId: string; hostname: string; os: string; arch: string | null; runtimeVersion: string | null; lastSeenAt: number | null; online: boolean } | null;
3156
+ workspace?: WorkspaceGrant | null;
3157
+ }
3158
+ ```
3159
+
3160
+ #### `FailedRequest` — interface · src/failed-requests.ts
3161
+
3162
+ The platform's list of requests that failed after the UI already showed their result (the optimistic-UI rule, app-style-guide.md "Interaction"): an app updates the screen at once, and when the request behind it fails the app puts the screen back and reports here — `<FailedRequestBanner/>` then names the request and offers Try again, in the same place and style as the "new version is available" pill. Pure and framework-free, so any app (built-in or Forge) and any test can use it. A Forge app imports it from `esoul-sdk/react`; the platform's `src/lib/failed-requests.ts` re-exports this very module, so both reach one list.
3163
+
3164
+ ```ts
3165
+ interface FailedRequest {
3166
+ key: string;
3167
+ what: string;
3168
+ why?: string;
3169
+ retry?: () => Promise<boolean> | boolean;
3170
+ at: number;
3171
+ retrying?: boolean;
3172
+ }
3173
+ ```
2769
3174
 
2770
3175
  #### `FileEntriesState` — interface · src/react.ts
2771
3176
 
@@ -2868,6 +3273,23 @@ interface LabelClass {
2868
3273
  }
2869
3274
  ```
2870
3275
 
3276
+ #### `MyComputer` — interface · src/react.ts
3277
+
3278
+ One computer connected to the signed-in account, and the apps it is assigned to.
3279
+
3280
+ ```ts
3281
+ interface MyComputer {
3282
+ machineId: string;
3283
+ hostname: string;
3284
+ os: string;
3285
+ arch: string | null;
3286
+ runtimeVersion: string | null;
3287
+ lastSeenAt: number | null;
3288
+ online: boolean;
3289
+ apps: { linkId: string; nodeId: string; workspaceId: string; status: string; appName: string | null }[];
3290
+ }
3291
+ ```
3292
+
2871
3293
  #### `PluginRealtime` — interface · src/react.ts
2872
3294
 
2873
3295
  ```ts
@@ -3013,6 +3435,22 @@ interface ResolvedFilePath {
3013
3435
  }
3014
3436
  ```
3015
3437
 
3438
+ #### `ShellResult` — interface · src/device.ts
3439
+
3440
+ One line of the shared command line, answered (esoul-sdk/machine shell.ts): `text` is what a terminal would print (what an agent reads), `data` the same answer structured, `opId` the journal entry to `undo`.
3441
+
3442
+ ```ts
3443
+ interface ShellResult {
3444
+ ok: boolean;
3445
+ text: string;
3446
+ data?: unknown;
3447
+ exitCode?: number | null;
3448
+ error?: string;
3449
+ opId?: string;
3450
+ cwd: string;
3451
+ }
3452
+ ```
3453
+
3016
3454
  #### `SignInWall` — interface · src/react.ts
3017
3455
 
3018
3456
  ```ts
@@ -3036,6 +3474,14 @@ interface WorkspaceAppSummary {
3036
3474
  }
3037
3475
  ```
3038
3476
 
3477
+ #### `WorkspaceGrant` — type · src/device.ts
3478
+
3479
+ What an app's agents may reach on a computer through the shared command line.
3480
+
3481
+ ```ts
3482
+ type WorkspaceGrant = { scope: "folder"; root: string } | { scope: "computer" };
3483
+ ```
3484
+
3039
3485
  #### `WorkspaceNav` — interface · src/react.ts
3040
3486
 
3041
3487
  ```ts
@@ -3289,3 +3735,774 @@ interface TestManifest {
3289
3735
  ```ts
3290
3736
  type ViewerKind = RuleViewer["kind"];
3291
3737
  ```
3738
+
3739
+ ==============================================================================
3740
+ ## `esoul-sdk/machine` — 82 exports
3741
+
3742
+ The side of an app that runs on the person's computer: the signed door protocol, the declared-command rules, the runtime behind the `esoul-device` command. Node built-ins only.
3743
+
3744
+ ### Functions and values (51)
3745
+
3746
+ #### `AGENT_NAME_RE` — const · src/machine/shell.ts
3747
+
3748
+ What an agent may be called: lowercase letters, digits, - and _, up to 32, starting with a letter.
3749
+
3750
+ ```ts
3751
+ const AGENT_NAME_RE: RegExp
3752
+ ```
3753
+
3754
+ #### `announce` — function · src/machine/capabilities.ts
3755
+
3756
+ What the hello announces: ["commands@1", "workspace@1"].
3757
+
3758
+ ```ts
3759
+ function announce(reg: Map<string, Capability>): string[]
3760
+ ```
3761
+
3762
+ #### `applyEdits` — function · src/machine/files.ts
3763
+
3764
+ Find-and-replace edits, applied in order; each must match exactly once (unless `all`). All or nothing.
3765
+
3766
+ ```ts
3767
+ function applyEdits(text: string, edits: Edit[]): { text: string; applied: number }
3768
+ ```
3769
+
3770
+ #### `applyPatch` — function · src/machine/files.ts
3771
+
3772
+ Apply a unified diff with context. Each hunk's old lines (context + removed) are located exactly, preferring the line the diff names and searching outward, so a file that moved on still takes a hunk whose context is intact. A hunk whose context is gone is reported, never forced. All-or-nothing unless `partial`.
3773
+
3774
+ ```ts
3775
+ function applyPatch(text: string, diff: string, opts: { partial?: boolean } = {}): { text: string; results: HunkResult[] }
3776
+ ```
3777
+
3778
+ #### `builtInCapabilities` — function · src/machine/capabilities.ts
3779
+
3780
+ The capabilities this runtime is built with, as one registry (the supervisor dispatches through it).
3781
+
3782
+ ```ts
3783
+ function builtInCapabilities(startRun: (job: Job, redact: string[], env: Record<string, string>) => Promise<void>): Map<string, Capability>
3784
+ ```
3785
+
3786
+ #### `canonical` — function · src/machine/protocol.ts
3787
+
3788
+ The exact bytes that are signed. Path INCLUDES the query string.
3789
+
3790
+ ```ts
3791
+ function canonical(method: string, pathAndQuery: string, ts: number | string, body: string | Uint8Array): string
3792
+ ```
3793
+
3794
+ #### `COMMAND_NAME_RE` — const · src/machine/commands.ts
3795
+
3796
+ What a command name may look like: lowercase, starts with a letter, up to 40 characters.
3797
+
3798
+ ```ts
3799
+ const COMMAND_NAME_RE: RegExp
3800
+ ```
3801
+
3802
+ #### `commandDigest` — function · src/machine/digest.ts
3803
+
3804
+ Stable digest of one command's spec — what the owner approved.
3805
+
3806
+ ```ts
3807
+ function commandDigest(spec: CommandSpec): string
3808
+ ```
3809
+
3810
+ #### `commandDigests` — function · src/machine/digest.ts
3811
+
3812
+ Every declared command's digest, by name — what an approval records.
3813
+
3814
+ ```ts
3815
+ function commandDigests(block: DeviceBlock): Record<string, string>
3816
+ ```
3817
+
3818
+ #### `commandsCapability` — function · src/machine/capabilities.ts
3819
+
3820
+ The app's declared commands; the detached runner owns and reports each run.
3821
+
3822
+ ```ts
3823
+ function commandsCapability(deps: { startRun: (job: Job, redact: string[], env: Record<string, string>) => Promise<void> }): Capability
3824
+ ```
3825
+
3826
+ #### `confinedArgv` — function · src/machine/shell.ts
3827
+
3828
+ argv that runs `line` in `cwd` under the scope's confinement.
3829
+
3830
+ ```ts
3831
+ function confinedArgv(ws: Workspace, cwd: string, line: string): string[]
3832
+ ```
3833
+
3834
+ #### `connect` — function · src/machine/supervisor.ts
3835
+
3836
+ Answer a connect link from the app's page with this computer's key: print the code and the commands, wait for the owner's approval.
3837
+
3838
+ ```ts
3839
+ async function connect(opts: ConnectOptions): Promise<ConnectResult>
3840
+ ```
3841
+
3842
+ #### `connectCode` — function · src/machine/protocol.ts
3843
+
3844
+ The short code both the machine and the connect page show, so the owner can see the request on screen is the one from the machine in front of them. Derived from the machine's public key + the offer, so neither side invents it.
3845
+
3846
+ ```ts
3847
+ function connectCode(publicKey: string, offerId: string): string
3848
+ ```
3849
+
3850
+ #### `DEFAULT_TIMEOUT_SECONDS` — const · src/machine/commands.ts
3851
+
3852
+ A command's time limit when it declares none: one hour.
3853
+
3854
+ ```ts
3855
+ const DEFAULT_TIMEOUT_SECONDS: 3600
3856
+ ```
3857
+
3858
+ #### `describeGrant` — function · src/machine/grant.ts
3859
+
3860
+ A grant in words: "the folder /home/me/proj", "the whole computer", or "no workspace access".
3861
+
3862
+ ```ts
3863
+ function describeGrant(g: WorkspaceGrant | null): string
3864
+ ```
3865
+
3866
+ #### `deviceBlockProblems` — function · src/machine/commands.ts
3867
+
3868
+ Every problem with a `device` block, in words an author can act on. Empty = valid.
3869
+
3870
+ ```ts
3871
+ function deviceBlockProblems(block: unknown): string[]
3872
+ ```
3873
+
3874
+ #### `DeviceCommandError` — class · src/machine/commands.ts
3875
+
3876
+ Why a command was refused: `unknown_command` (not declared), `bad_param` (missing, extra or ill-typed), `bad_spec`. The message says which, in words.
3877
+
3878
+ ```ts
3879
+ class DeviceCommandError extends Error { … }
3880
+ ```
3881
+
3882
+ #### `deviceHome` — function · src/machine/state.ts
3883
+
3884
+ The runtime's folder on this computer: `explicit`, else ESOUL_DEVICE_HOME, else ~/.esoul-device.
3885
+
3886
+ ```ts
3887
+ function deviceHome(explicit?: string): string
3888
+ ```
3889
+
3890
+ #### `DoorError` — class · src/machine/client.ts
3891
+
3892
+ A door call that failed: `status` 0 means the door never answered (network); otherwise the door's own `code` and message.
3893
+
3894
+ ```ts
3895
+ class DoorError extends Error { … }
3896
+ ```
3897
+
3898
+ #### `effectiveGrant` — function · src/machine/grant.ts
3899
+
3900
+ What the runtime actually allows: the grant, if it fits inside the ceiling. A folder grant inside a folder ceiling keeps the grant's (narrower) root; a computer grant needs a computer ceiling.
3901
+
3902
+ ```ts
3903
+ function effectiveGrant(grant: WorkspaceGrant | null, ceiling: WorkspaceGrant | null): { ok: true; grant: WorkspaceGrant; why?: undefined } | { ok: false; why: string; grant?: undefined }
3904
+ ```
3905
+
3906
+ #### `execute` — function · src/machine/shell.ts
3907
+
3908
+ Run one line of the shared command line as a named agent in a workspace; never throws — failures come back as `ok: false` with an `error` code.
3909
+
3910
+ ```ts
3911
+ async function execute(call: ShellCall): Promise<ShellResult>
3912
+ ```
3913
+
3914
+ #### `FileError` — class · src/machine/files.ts
3915
+
3916
+ Why a file operation was refused: outside_scope, not_found, changed, hard_link, edit_failed, patch_failed, … — with a sentence and details.
3917
+
3918
+ ```ts
3919
+ class FileError extends Error { … }
3920
+ ```
3921
+
3922
+ #### `generateDeviceKey` — function · src/machine/protocol.ts
3923
+
3924
+ A new Ed25519 keypair for a computer: the public key in base64url, the private key as PKCS#8 PEM (it never leaves the computer).
3925
+
3926
+ ```ts
3927
+ function generateDeviceKey(): DeviceKeyPair
3928
+ ```
3929
+
3930
+ #### `grantFromChoice` — function · src/machine/grant.ts
3931
+
3932
+ The owner's choice turned into a grant under this computer's ceiling, or why it cannot be.
3933
+
3934
+ ```ts
3935
+ function grantFromChoice(choice: WorkspaceChoice, ceiling: WorkspaceGrant | null): { ok: true; grant: WorkspaceGrant | null; why?: undefined } | { ok: false; why: string; grant?: undefined }
3936
+ ```
3937
+
3938
+ #### `H` — const · src/machine/protocol.ts
3939
+
3940
+ Request headers. `key` names the signer: a machine id, or `connect:<id>` before approval.
3941
+
3942
+ ```ts
3943
+ const H: { readonly key: "x-esoul-key"; readonly ts: "x-esoul-ts"; readonly sig: "x-esoul-sig"; readonly runtime: "x-esoul-runtime"; }
3944
+ ```
3945
+
3946
+ #### `isPublicKey` — function · src/machine/protocol.ts
3947
+
3948
+ Whether `s` is a base64url Ed25519 public key (43 characters).
3949
+
3950
+ ```ts
3951
+ function isPublicKey(s: unknown): s is string
3952
+ ```
3953
+
3954
+ #### `isTransient` — function · src/machine/client.ts
3955
+
3956
+ Transient = worth retrying later without telling anyone.
3957
+
3958
+ ```ts
3959
+ function isTransient(e: unknown): boolean
3960
+ ```
3961
+
3962
+ #### `Journal` — class · src/machine/files.ts
3963
+
3964
+ The undo journal of one workspace: previous contents (content-addressed), directory snapshots, and the append-only log of operations.
3965
+
3966
+ ```ts
3967
+ class Journal { … }
3968
+ ```
3969
+
3970
+ #### `machineFingerprint` — function · src/machine/state.ts
3971
+
3972
+ This computer, stably: the OS machine id (Linux /etc/machine-id, macOS the platform UUID), the user, and the runtime home. Sent when connecting, so the platform RECOGNISES a reconnect (a reinstall, a new key) instead of listing a second entry for the same computer. A different home is deliberately a different computer (a second runtime, a test run).
3973
+
3974
+ ```ts
3975
+ function machineFingerprint(home: string): string
3976
+ ```
3977
+
3978
+ #### `makeClient` — function · src/machine/client.ts
3979
+
3980
+ A signed door client for one key: every call carries the signature, retries only what the door never answered.
3981
+
3982
+ ```ts
3983
+ function makeClient(args: { base: string; keyId: string; privateKeyPem: string; runtimeVersion: string; fetch?: FetchLike; onTiming?: (t: CallTiming) => void }): DoorClient
3984
+ ```
3985
+
3986
+ #### `MAX_SKEW_MS` — const · src/machine/protocol.ts
3987
+
3988
+ How far a request's clock may be from the server's. Laptops drift; five minutes is generous and still bounds a replay.
3989
+
3990
+ ```ts
3991
+ const MAX_SKEW_MS: number
3992
+ ```
3993
+
3994
+ #### `MAX_TIMEOUT_SECONDS` — const · src/machine/commands.ts
3995
+
3996
+ The longest time limit a command may declare: seven days.
3997
+
3998
+ ```ts
3999
+ const MAX_TIMEOUT_SECONDS: number
4000
+ ```
4001
+
4002
+ #### `newAuthority` — function · src/machine/digest.ts
4003
+
4004
+ What an app update asks of the owner. Removing a command, or leaving one untouched, needs nothing; ADDING a command or CHANGING one is new authority and waits for a fresh approval.
4005
+
4006
+ ```ts
4007
+ function newAuthority(approved: Record<string, string>, current: DeviceBlock): { added: string[]; changed: string[] }
4008
+ ```
4009
+
4010
+ #### `openWorkspace` — function · src/machine/shell.ts
4011
+
4012
+ Open (or create) the workspace for a scope under the runtime's home.
4013
+
4014
+ ```ts
4015
+ function openWorkspace(home: string, id: string, scope: Scope): Workspace
4016
+ ```
4017
+
4018
+ #### `OUTPUT_LIMITS` — const · src/machine/commands.ts
4019
+
4020
+ The ceilings of the output options: chunk 3 MB, stored head 8 MB, gather 60 s, tail 64 KB.
4021
+
4022
+ ```ts
4023
+ const OUTPUT_LIMITS: { readonly maxChunkKb: 3072; readonly maxKb: 8192; readonly flushMs: 60000; readonly tailChars: number; }
4024
+ ```
4025
+
4026
+ #### `OutputUploader` — class · src/machine/capabilities.ts
4027
+
4028
+ Ordered, batched, retried upload of one job's output (the run directory is the outbox for detached runs; inline jobs hold theirs here). `finish` drains the output, then reports the end with the result.
4029
+
4030
+ ```ts
4031
+ class OutputUploader { … }
4032
+ ```
4033
+
4034
+ #### `parseGrant` — function · src/machine/grant.ts
4035
+
4036
+ A grant from untrusted JSON, or null. Roots must be absolute and plain.
4037
+
4038
+ ```ts
4039
+ function parseGrant(v: unknown): WorkspaceGrant | null
4040
+ ```
4041
+
4042
+ #### `parseUnifiedDiff` — function · src/machine/files.ts
4043
+
4044
+ The hunks of a unified diff (`@@ -a,b +c,d
4045
+
4046
+ ```ts
4047
+ function parseUnifiedDiff(diff: string): Hunk[]
4048
+ ```
4049
+
4050
+ #### `PROTOCOL` — const · src/machine/protocol.ts
4051
+
4052
+ The protocol version, the first line of every signed request.
4053
+
4054
+ ```ts
4055
+ const PROTOCOL: "esoul-device/1"
4056
+ ```
4057
+
4058
+ #### `registry` — function · src/machine/capabilities.ts
4059
+
4060
+ The capability table the runtime dispatches jobs through, by name.
4061
+
4062
+ ```ts
4063
+ function registry(caps: Capability[]): Map<string, Capability>
4064
+ ```
4065
+
4066
+ #### `renderCommand` — function · src/machine/commands.ts
4067
+
4068
+ Render one declared command. Throws `DeviceCommandError` with a sentence for anything undeclared or ill-typed.
4069
+
4070
+ ```ts
4071
+ function renderCommand(block: DeviceBlock, name: string, params: Record<string, unknown> = {}): RenderedCommand
4072
+ ```
4073
+
4074
+ #### `resolveInScope` — function · src/machine/files.ts
4075
+
4076
+ A path as the agent wrote it → an absolute path the scope allows. `cwd` is the agent's current directory (absolute, inside the scope). `~` means the home directory, and is allowed only when the scope contains it.
4077
+
4078
+ ```ts
4079
+ function resolveInScope(scope: Scope, cwd: string, p: string): string
4080
+ ```
4081
+
4082
+ #### `resolveOutput` — function · src/machine/commands.ts
4083
+
4084
+ The spec's output choice with its defaults filled in — what the runtime and the door both use.
4085
+
4086
+ ```ts
4087
+ function resolveOutput(spec: OutputSpec | undefined): ResolvedOutput
4088
+ ```
4089
+
4090
+ #### `RUNTIME_CAPABILITIES` — const · src/machine/capabilities.ts
4091
+
4092
+ What this runtime can do, said at CONNECT as well as in every hello, so the platform knows it from the approval on: work asked for before the runtime's first hello waits in the queue instead of being refused.
4093
+
4094
+ ```ts
4095
+ const RUNTIME_CAPABILITIES: string[]
4096
+ ```
4097
+
4098
+ #### `sandboxFor` — function · src/machine/shell.ts
4099
+
4100
+ How `run` and `term` are confined. The computer scope runs processes as they are. A folder scope needs an OS sandbox — bubblewrap on Linux, Seatbelt on macOS — or processes are REFUSED (a path check cannot keep a shell in a folder; `cd ..` would do).
4101
+
4102
+ ```ts
4103
+ function sandboxFor(scope: Scope): Sandbox
4104
+ ```
4105
+
4106
+ #### `sha256Hex` — function · src/machine/protocol.ts
4107
+
4108
+ Hex SHA-256 of a request body, as signed.
4109
+
4110
+ ```ts
4111
+ function sha256Hex(body: string | Uint8Array): string
4112
+ ```
4113
+
4114
+ #### `signRequest` — function · src/machine/protocol.ts
4115
+
4116
+ Sign one request (method, path with query, body) now; returns the `x-esoul-ts` and `x-esoul-sig` header values.
4117
+
4118
+ ```ts
4119
+ function signRequest( privateKeyPem: string, method: string, pathAndQuery: string, body: string | Uint8Array, now: number = Date.now(), ): { ts: string; sig: string }
4120
+ ```
4121
+
4122
+ #### `startSupervisor` — function · src/machine/supervisor.ts
4123
+
4124
+ The runtime: one per computer, for every connected app — hello, wake, claim, re-render and run, report, and the app's lifecycle.
4125
+
4126
+ ```ts
4127
+ function startSupervisor(opts: SupervisorOptions)
4128
+ ```
4129
+
4130
+ #### `tokenize` — function · src/machine/shell.ts
4131
+
4132
+ Shell-like words: quotes ('…' literal, "…" with \" escapes), backslash escapes, whitespace separation.
4133
+
4134
+ ```ts
4135
+ function tokenize(line: string): string[]
4136
+ ```
4137
+
4138
+ #### `verifyRequest` — function · src/machine/protocol.ts
4139
+
4140
+ Pure verification. `publicKey` comes from the platform's row for `x-esoul-key`, never from the request.
4141
+
4142
+ ```ts
4143
+ function verifyRequest(args: { publicKey: string; method: string; pathAndQuery: string; body: string | Uint8Array; ts: string | null | undefined; sig: string | null | undefined; now?: number; }): { ok: true } | { ok: false; reason: VerifyFailure }
4144
+ ```
4145
+
4146
+ #### `workspaceCapability` — function · src/machine/capabilities.ts
4147
+
4148
+ The shared command line, under grant ∩ ceiling, one working directory per named agent.
4149
+
4150
+ ```ts
4151
+ function workspaceCapability(): Capability
4152
+ ```
4153
+
4154
+ ### Types (31)
4155
+
4156
+ #### `CallTiming` — interface · src/machine/client.ts
4157
+
4158
+ ```ts
4159
+ interface CallTiming {
4160
+ method: string;
4161
+ path: string;
4162
+ status: number;
4163
+ ms: number;
4164
+ server: string | null;
4165
+ }
4166
+ ```
4167
+
4168
+ #### `Capability` — interface · src/machine/capabilities.ts
4169
+
4170
+ ```ts
4171
+ interface Capability {
4172
+ name: string;
4173
+ version: number;
4174
+ refuse(job: Job, ctx: JobContext): string | null;
4175
+ start(job: Job, ctx: JobContext): Promise<void>;
4176
+ }
4177
+ ```
4178
+
4179
+ #### `CommandSpec` — interface · src/machine/commands.ts
4180
+
4181
+ ```ts
4182
+ interface CommandSpec {
4183
+ argv: string[];
4184
+ params?: Record<string, ParamSpec>;
4185
+ cwd?: string;
4186
+ env?: Record<string, string>;
4187
+ config?: string[];
4188
+ timeoutSeconds?: number;
4189
+ onRestart?: "report" | "rerun";
4190
+ output?: OutputSpec;
4191
+ result?: "text" | "json";
4192
+ describe?: string;
4193
+ }
4194
+ ```
4195
+
4196
+ #### `ConfigKeySpec` — interface · src/machine/commands.ts
4197
+
4198
+ ```ts
4199
+ interface ConfigKeySpec {
4200
+ secret?: boolean;
4201
+ required?: boolean;
4202
+ describe?: string;
4203
+ }
4204
+ ```
4205
+
4206
+ #### `ConnectOptions` — interface · src/machine/supervisor.ts
4207
+
4208
+ ```ts
4209
+ interface ConnectOptions {
4210
+ url: string;
4211
+ home?: string;
4212
+ runtimeVersion: string;
4213
+ fetch?: FetchLike;
4214
+ pollMs?: number;
4215
+ print?: (line: string) => void;
4216
+ fingerprint?: string;
4217
+ ceiling?: WorkspaceGrant | null;
4218
+ }
4219
+ ```
4220
+
4221
+ #### `ConnectResult` — interface · src/machine/supervisor.ts
4222
+
4223
+ ```ts
4224
+ interface ConnectResult {
4225
+ machineId: string;
4226
+ linkId: string | null;
4227
+ base: string;
4228
+ appName: string | null;
4229
+ }
4230
+ ```
4231
+
4232
+ #### `DeviceBlock` — interface · src/machine/commands.ts
4233
+
4234
+ ```ts
4235
+ interface DeviceBlock {
4236
+ commands: Record<string, CommandSpec>;
4237
+ config?: Record<string, ConfigKeySpec>;
4238
+ redact?: string[];
4239
+ onAppDeleted?: "kill" | "finish";
4240
+ }
4241
+ ```
4242
+
4243
+ #### `DeviceKeyPair` — interface · src/machine/protocol.ts
4244
+
4245
+ ```ts
4246
+ interface DeviceKeyPair {
4247
+ publicKey: string;
4248
+ privateKeyPem: string;
4249
+ }
4250
+ ```
4251
+
4252
+ #### `DoorClient` — interface · src/machine/client.ts
4253
+
4254
+ ```ts
4255
+ interface DoorClient {
4256
+ call<T = Record<string, unknown>>(method: "GET" | "POST", path: string, body?: unknown, opts?: { timeoutMs?: number; tries?: number }): Promise<T>;
4257
+ base: string;
4258
+ }
4259
+ ```
4260
+
4261
+ #### `Edit` — interface · src/machine/files.ts
4262
+
4263
+ ```ts
4264
+ interface Edit {
4265
+ find: string;
4266
+ replace: string;
4267
+ all?: boolean;
4268
+ }
4269
+ ```
4270
+
4271
+ #### `FetchLike` — type · src/machine/client.ts
4272
+
4273
+ ```ts
4274
+ type FetchLike = (input: string, init: { method: string; headers: Record<string, string>; body?: string; signal?: AbortSignal }) => Promise<{ status: number; text(): Promise<string>; headers?: { get(name: string): string | null } }>;
4275
+ ```
4276
+
4277
+ #### `HunkResult` — interface · src/machine/files.ts
4278
+
4279
+ ```ts
4280
+ interface HunkResult {
4281
+ hunk: number;
4282
+ applied: boolean;
4283
+ at?: number;
4284
+ offset?: number;
4285
+ reason?: string;
4286
+ }
4287
+ ```
4288
+
4289
+ #### `Job` — interface · src/machine/capabilities.ts
4290
+
4291
+ ```ts
4292
+ interface Job extends ClaimedCommand {
4293
+ capability: string;
4294
+ agent: string | null;
4295
+ }
4296
+ ```
4297
+
4298
+ #### `JobContext` — interface · src/machine/capabilities.ts
4299
+
4300
+ ```ts
4301
+ interface JobContext {
4302
+ home: string;
4303
+ link: LocalLink;
4304
+ ceiling: WorkspaceGrant | null;
4305
+ client: DoorClient;
4306
+ signal: AbortSignal;
4307
+ log: (s: string) => void;
4308
+ }
4309
+ ```
4310
+
4311
+ #### `JobOutcome` — interface · src/machine/capabilities.ts
4312
+
4313
+ ```ts
4314
+ interface JobOutcome {
4315
+ exitCode: number | null;
4316
+ reason?: string | null;
4317
+ signal?: string | null;
4318
+ result?: unknown;
4319
+ }
4320
+ ```
4321
+
4322
+ #### `JournalEntry` — interface · src/machine/files.ts
4323
+
4324
+ ```ts
4325
+ interface JournalEntry {
4326
+ opId: string;
4327
+ at: number;
4328
+ agent: string;
4329
+ op: "write" | "edit" | "patch" | "rm" | "mv" | "mkdir" | "undo";
4330
+ path: string;
4331
+ to?: string;
4332
+ before: string | null;
4333
+ after: string | null;
4334
+ dirSnapshot?: string;
4335
+ }
4336
+ ```
4337
+
4338
+ #### `LocalLink` — interface · src/machine/state.ts
4339
+
4340
+ ```ts
4341
+ interface LocalLink {
4342
+ linkId: string;
4343
+ nodeId: string;
4344
+ workspaceId: string;
4345
+ pluginId: string;
4346
+ appName: string | null;
4347
+ status: "active" | "suspended" | "revoked";
4348
+ reason: string | null;
4349
+ commands: DeviceBlock | null;
4350
+ grants?: Record<string, unknown> | null;
4351
+ }
4352
+ ```
4353
+
4354
+ #### `LocalState` — interface · src/machine/state.ts
4355
+
4356
+ ```ts
4357
+ interface LocalState {
4358
+ base: string;
4359
+ machineId: string;
4360
+ links: Record<string, LocalLink>;
4361
+ ceiling?: { scope: "folder"; root: string } | { scope: "computer" } | null;
4362
+ }
4363
+ ```
4364
+
4365
+ #### `MachineShellResult` — interface · src/machine/shell.ts
4366
+
4367
+ ```ts
4368
+ interface ShellResult {
4369
+ ok: boolean;
4370
+ text: string;
4371
+ data?: unknown;
4372
+ exitCode?: number | null;
4373
+ error?: string;
4374
+ opId?: string;
4375
+ cwd: string;
4376
+ }
4377
+ ```
4378
+
4379
+ #### `OutputSpec` — interface · src/machine/commands.ts
4380
+
4381
+ The delivery tradeoff, chosen per command (esoul-sdk docs/18 §4):
4382
+
4383
+ ```ts
4384
+ interface OutputSpec {
4385
+ mode?: "live" | "batched" | "final";
4386
+ flushMs?: number;
4387
+ maxChunkKb?: number;
4388
+ keep?: "head" | "tail" | "both";
4389
+ maxKb?: number;
4390
+ }
4391
+ ```
4392
+
4393
+ #### `ParamSpec` — type · src/machine/commands.ts
4394
+
4395
+ DECLARED COMMANDS — the wall between an app and the person's computer.
4396
+
4397
+ ```ts
4398
+ type ParamSpec =
4399
+ | "int"
4400
+ | "number"
4401
+ | "bool"
4402
+ | "string"
4403
+ | { type: "int" | "number"; min?: number; max?: number; describe?: string }
4404
+ | { type: "string"; pattern?: string; maxLength?: number; allowDash?: boolean; describe?: string }
4405
+ | { type: "enum"; values: string[]; describe?: string }
4406
+ | { type: "bool"; describe?: string };
4407
+ ```
4408
+
4409
+ #### `RenderedCommand` — interface · src/machine/commands.ts
4410
+
4411
+ ```ts
4412
+ interface RenderedCommand {
4413
+ name: string;
4414
+ argv: string[];
4415
+ cwd?: string;
4416
+ env: Record<string, string>;
4417
+ config: string[];
4418
+ timeoutSeconds: number;
4419
+ onRestart: "report" | "rerun";
4420
+ }
4421
+ ```
4422
+
4423
+ #### `ResolvedOutput` — interface · src/machine/commands.ts
4424
+
4425
+ ```ts
4426
+ interface ResolvedOutput {
4427
+ mode: "live" | "batched" | "final";
4428
+ flushMs: number;
4429
+ maxChunkBytes: number;
4430
+ headMaxBytes: number;
4431
+ tailMaxChars: number;
4432
+ }
4433
+ ```
4434
+
4435
+ #### `Sandbox` — type · src/machine/shell.ts
4436
+
4437
+ ```ts
4438
+ type Sandbox = { kind: "none" } | { kind: "bwrap"; bin: string } | { kind: "seatbelt" } | { kind: "unavailable"; why: string };
4439
+ ```
4440
+
4441
+ #### `Scope` — type · src/machine/files.ts
4442
+
4443
+ ```ts
4444
+ type Scope = { kind: "folder"; root: string } | { kind: "computer" };
4445
+ ```
4446
+
4447
+ #### `ShellCall` — interface · src/machine/shell.ts
4448
+
4449
+ ```ts
4450
+ interface ShellCall {
4451
+ ws: Workspace;
4452
+ agent: string;
4453
+ line: string;
4454
+ stdin?: string;
4455
+ onOutput?: (chunk: string) => void;
4456
+ signal?: AbortSignal;
4457
+ }
4458
+ ```
4459
+
4460
+ #### `SupervisorOptions` — interface · src/machine/supervisor.ts
4461
+
4462
+ ```ts
4463
+ interface SupervisorOptions {
4464
+ home?: string;
4465
+ runtimeVersion: string;
4466
+ execScript: string;
4467
+ nodePath?: string;
4468
+ fetch?: FetchLike;
4469
+ wake?: boolean;
4470
+ pollSeconds?: number;
4471
+ flushMs?: number;
4472
+ helloEveryMs?: number;
4473
+ log?: (line: string) => void;
4474
+ onEmpty?: (why: string) => void | Promise<void>;
4475
+ }
4476
+ ```
4477
+
4478
+ #### `VerifyFailure` — type · src/machine/protocol.ts
4479
+
4480
+ ```ts
4481
+ type VerifyFailure = "missing" | "bad_key" | "skew" | "bad_signature";
4482
+ ```
4483
+
4484
+ #### `Workspace` — interface · src/machine/shell.ts
4485
+
4486
+ ```ts
4487
+ interface Workspace {
4488
+ id: string;
4489
+ scope: Scope;
4490
+ journal: Journal;
4491
+ stateDir: string;
4492
+ sandbox: Sandbox;
4493
+ }
4494
+ ```
4495
+
4496
+ #### `WorkspaceChoice` — type · src/machine/grant.ts
4497
+
4498
+ ```ts
4499
+ type WorkspaceChoice = "none" | "folder" | "computer";
4500
+ ```
4501
+
4502
+ #### `WorkspaceGrant` — type · src/machine/grant.ts
4503
+
4504
+ WORKSPACE GRANTS (plugin-device-arm.md §15): what an app's agents may reach on a computer through the shared command line — one FOLDER, or the whole COMPUTER. Two parties must agree:
4505
+
4506
+ ```ts
4507
+ type WorkspaceGrant = { scope: "folder"; root: string } | { scope: "computer" };
4508
+ ```