@skaile/workspaces 1.6.0 → 1.7.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 (156) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/dist/app-sdk/index.js +22 -7
  3. package/dist/app-sdk/index.js.map +1 -1
  4. package/dist/app-sdk/src/connection-params.d.ts +7 -0
  5. package/dist/app-sdk/src/connection-params.d.ts.map +1 -1
  6. package/dist/{asset-feeds-T35ELX2O.js → asset-feeds-Y66NCOQZ.js} +11 -11
  7. package/dist/asset-feeds-Y66NCOQZ.js.map +1 -0
  8. package/dist/asset-manager/index.js +6 -6
  9. package/dist/asset-manager/installer.js +4 -4
  10. package/dist/asset-manager/src/index.d.ts +18 -0
  11. package/dist/asset-manager/src/index.d.ts.map +1 -1
  12. package/dist/{chunk-HY33RMEA.js → chunk-5NZ56R63.js} +3 -3
  13. package/dist/{chunk-HY33RMEA.js.map → chunk-5NZ56R63.js.map} +1 -1
  14. package/dist/{chunk-ZBZQC5R7.js → chunk-7J7LQP6T.js} +3 -3
  15. package/dist/{chunk-ZBZQC5R7.js.map → chunk-7J7LQP6T.js.map} +1 -1
  16. package/dist/{chunk-WYGXYGJH.js → chunk-7VGRRH2L.js} +9 -9
  17. package/dist/{chunk-WYGXYGJH.js.map → chunk-7VGRRH2L.js.map} +1 -1
  18. package/dist/{chunk-RDYOU6N6.js → chunk-BSXBOKL6.js} +2 -2
  19. package/dist/{chunk-RDYOU6N6.js.map → chunk-BSXBOKL6.js.map} +1 -1
  20. package/dist/{chunk-UQKYT4V6.js → chunk-ISA7R7PK.js} +3 -3
  21. package/dist/{chunk-UQKYT4V6.js.map → chunk-ISA7R7PK.js.map} +1 -1
  22. package/dist/{chunk-J5TI55E7.js → chunk-MB6563TL.js} +37 -8
  23. package/dist/chunk-MB6563TL.js.map +1 -0
  24. package/dist/{chunk-FDE7XRQA.js → chunk-MWE7DEGC.js} +3 -3
  25. package/dist/{chunk-FDE7XRQA.js.map → chunk-MWE7DEGC.js.map} +1 -1
  26. package/dist/{chunk-KMY7HVQ6.js → chunk-O3D4KYFG.js} +3 -3
  27. package/dist/{chunk-KMY7HVQ6.js.map → chunk-O3D4KYFG.js.map} +1 -1
  28. package/dist/{chunk-JIDKX7GC.js → chunk-PP2LOT4W.js} +43 -7
  29. package/dist/chunk-PP2LOT4W.js.map +1 -0
  30. package/dist/{chunk-2DWXGUZA.js → chunk-QHU6TM7D.js} +2 -2
  31. package/dist/{chunk-2DWXGUZA.js.map → chunk-QHU6TM7D.js.map} +1 -1
  32. package/dist/{chunk-6NR2BEIY.js → chunk-STQOPWPS.js} +4 -4
  33. package/dist/{chunk-6NR2BEIY.js.map → chunk-STQOPWPS.js.map} +1 -1
  34. package/dist/{chunk-BEO6KN7P.js → chunk-SZY7X4M2.js} +5 -5
  35. package/dist/{chunk-BEO6KN7P.js.map → chunk-SZY7X4M2.js.map} +1 -1
  36. package/dist/{chunk-CBA2XIBF.js → chunk-TC3GLA5D.js} +31 -16
  37. package/dist/chunk-TC3GLA5D.js.map +1 -0
  38. package/dist/{chunk-RSYV7XEN.js → chunk-TITPPC54.js} +4 -4
  39. package/dist/{chunk-RSYV7XEN.js.map → chunk-TITPPC54.js.map} +1 -1
  40. package/dist/{chunk-DMG6NJH7.js → chunk-VBRXFLIP.js} +4 -4
  41. package/dist/{chunk-DMG6NJH7.js.map → chunk-VBRXFLIP.js.map} +1 -1
  42. package/dist/{chunk-2ZMO4CFG.js → chunk-VK24PIAB.js} +5 -4
  43. package/dist/chunk-VK24PIAB.js.map +1 -0
  44. package/dist/{chunk-3CHARYD4.js → chunk-Y3UCU4ZK.js} +99 -28
  45. package/dist/chunk-Y3UCU4ZK.js.map +1 -0
  46. package/dist/{chunk-KEQ4GLVM.js → chunk-Y6FV73IH.js} +58 -4
  47. package/dist/chunk-Y6FV73IH.js.map +1 -0
  48. package/dist/cli/index.js +589 -194
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/cli/src/asset-feeds.d.ts.map +1 -1
  51. package/dist/cli/src/commands/asset-cmd.d.ts.map +1 -1
  52. package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
  53. package/dist/cli/src/commands/library-index.d.ts +59 -0
  54. package/dist/cli/src/commands/library-index.d.ts.map +1 -0
  55. package/dist/cli/src/commands/library-publish.d.ts.map +1 -1
  56. package/dist/cli/src/commands/manage.d.ts +22 -1
  57. package/dist/cli/src/commands/manage.d.ts.map +1 -1
  58. package/dist/cli/src/commands/publish-core.d.ts +24 -0
  59. package/dist/cli/src/commands/publish-core.d.ts.map +1 -1
  60. package/dist/cli/src/commands/session-logs/remote-query.d.ts.map +1 -1
  61. package/dist/cli/src/commands/session-logs/remote-tail.d.ts.map +1 -1
  62. package/dist/cli/src/commands/session-logs/superjson-transformer.d.ts +12 -0
  63. package/dist/cli/src/commands/session-logs/superjson-transformer.d.ts.map +1 -0
  64. package/dist/cli/src/commands/source-manifest-publish.d.ts.map +1 -1
  65. package/dist/cli/src/commands/store.d.ts.map +1 -1
  66. package/dist/cli/src/paths.d.ts.map +1 -1
  67. package/dist/cli/src/store-auth.d.ts +11 -0
  68. package/dist/cli/src/store-auth.d.ts.map +1 -1
  69. package/dist/cli/src/store-client.d.ts.map +1 -1
  70. package/dist/cli/src/store-publisher.d.ts +24 -1
  71. package/dist/cli/src/store-publisher.d.ts.map +1 -1
  72. package/dist/connectors/config.js +4 -4
  73. package/dist/connectors/index.js +5 -5
  74. package/dist/connectors/src/config.d.ts +21 -0
  75. package/dist/connectors/src/config.d.ts.map +1 -1
  76. package/dist/connectors/src/index.d.ts +1 -1
  77. package/dist/connectors/src/index.d.ts.map +1 -1
  78. package/dist/core/index.js +3 -3
  79. package/dist/core/runtime-assets.js +2 -2
  80. package/dist/core/src/index.d.ts +1 -1
  81. package/dist/core/src/index.d.ts.map +1 -1
  82. package/dist/core/src/repo-manager.d.ts +4 -0
  83. package/dist/core/src/repo-manager.d.ts.map +1 -1
  84. package/dist/core/workspace-config.js +1 -1
  85. package/dist/deploy/index.js +3 -3
  86. package/dist/{ensure-sources-QDVFUIUP.js → ensure-sources-NXBYK5GP.js} +9 -9
  87. package/dist/{ensure-sources-QDVFUIUP.js.map → ensure-sources-NXBYK5GP.js.map} +1 -1
  88. package/dist/factory-assets/connectors/box.js +5 -5
  89. package/dist/factory-assets/connectors/deploy.js +5 -5
  90. package/dist/factory-assets/connectors/devserver.js +5 -5
  91. package/dist/factory-assets/connectors/flow/adapter.js +5 -5
  92. package/dist/factory-assets/connectors/flow/engine.js +3 -3
  93. package/dist/factory-assets/connectors/flow/run-flow.js +6 -6
  94. package/dist/factory-assets/connectors/flow.js +5 -5
  95. package/dist/factory-assets/connectors/git.js +5 -5
  96. package/dist/factory-assets/connectors/gmail.js +5 -5
  97. package/dist/factory-assets/connectors/googledrive.js +5 -5
  98. package/dist/factory-assets/connectors/local.js +5 -5
  99. package/dist/factory-assets/connectors/mattermost.js +5 -5
  100. package/dist/factory-assets/connectors/memory.js +5 -5
  101. package/dist/factory-assets/connectors/minio.js +5 -5
  102. package/dist/factory-assets/connectors/postgres.js +5 -5
  103. package/dist/factory-assets/connectors/s3.js +5 -5
  104. package/dist/factory-assets/connectors/sharepoint.js +5 -5
  105. package/dist/factory-assets/connectors/sqlite.js +5 -5
  106. package/dist/factory-assets/connectors/static-server.js +5 -5
  107. package/dist/factory-assets/connectors/tunnel.js +5 -5
  108. package/dist/factory-assets/connectors/webdav.js +5 -5
  109. package/dist/factory-assets/connectors/xstate-store.js +5 -5
  110. package/dist/factory-assets/connectors/xstate.js +5 -5
  111. package/dist/{flows-MRVXTYWC.js → flows-RR6R3WBL.js} +3 -3
  112. package/dist/{flows-MRVXTYWC.js.map → flows-RR6R3WBL.js.map} +1 -1
  113. package/dist/{helpers-TF7GCBRQ.js → helpers-DN4MWOHB.js} +9 -9
  114. package/dist/{helpers-TF7GCBRQ.js.map → helpers-DN4MWOHB.js.map} +1 -1
  115. package/dist/library/index.js +4 -4
  116. package/dist/library/install.js +3 -3
  117. package/dist/{library-publish-K5IBE44D.js → library-publish-TPWFSO3Q.js} +44 -20
  118. package/dist/library-publish-TPWFSO3Q.js.map +1 -0
  119. package/dist/{open-library-4HMIDK3N.js → open-library-ST2VFQTK.js} +10 -10
  120. package/dist/{open-library-4HMIDK3N.js.map → open-library-ST2VFQTK.js.map} +1 -1
  121. package/dist/paths-KOKCMBL5.js +21 -0
  122. package/dist/{paths-P3DWFUPS.js.map → paths-KOKCMBL5.js.map} +1 -1
  123. package/dist/{plugin-store-S7OMGI7N.js → plugin-store-QMJ2B3VM.js} +5 -5
  124. package/dist/{plugin-store-S7OMGI7N.js.map → plugin-store-QMJ2B3VM.js.map} +1 -1
  125. package/dist/runner/index.js +7 -7
  126. package/dist/runner/src/resources.d.ts +8 -2
  127. package/dist/runner/src/resources.d.ts.map +1 -1
  128. package/dist/runner/src/serve.d.ts.map +1 -1
  129. package/dist/runner/src/session-builder.d.ts +10 -0
  130. package/dist/runner/src/session-builder.d.ts.map +1 -1
  131. package/dist/sdk/asset-manager.js +6 -6
  132. package/dist/sdk/core.js +3 -3
  133. package/dist/sdk/flow.js +3 -3
  134. package/dist/sdk/index.js +7 -7
  135. package/dist/sdk/runner.js +7 -7
  136. package/dist/{setup-2CCS63L6.js → setup-ETGRXSZG.js} +5 -5
  137. package/dist/{setup-2CCS63L6.js.map → setup-ETGRXSZG.js.map} +1 -1
  138. package/dist/{source-LOAM7IIQ.js → source-R4TWF424.js} +11 -11
  139. package/dist/{source-LOAM7IIQ.js.map → source-R4TWF424.js.map} +1 -1
  140. package/dist/{source-manifest-publish-TGVZ6KMC.js → source-manifest-publish-FXDK2BCI.js} +17 -15
  141. package/dist/source-manifest-publish-FXDK2BCI.js.map +1 -0
  142. package/dist/tui/index.js +7 -7
  143. package/dist/workspace-plugin/adapters/mcp.js +1 -1
  144. package/dist/workspace-plugin/adapters/omp.js +3 -3
  145. package/dist/workspace-plugin/index.js +1 -1
  146. package/package.json +1 -1
  147. package/dist/asset-feeds-T35ELX2O.js.map +0 -1
  148. package/dist/chunk-2ZMO4CFG.js.map +0 -1
  149. package/dist/chunk-3CHARYD4.js.map +0 -1
  150. package/dist/chunk-CBA2XIBF.js.map +0 -1
  151. package/dist/chunk-J5TI55E7.js.map +0 -1
  152. package/dist/chunk-JIDKX7GC.js.map +0 -1
  153. package/dist/chunk-KEQ4GLVM.js.map +0 -1
  154. package/dist/library-publish-K5IBE44D.js.map +0 -1
  155. package/dist/paths-P3DWFUPS.js +0 -15
  156. package/dist/source-manifest-publish-TGVZ6KMC.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,123 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#371](https://github.com/skaile-ai/workspaces/pull/371) [`d7b6224`](https://github.com/skaile-ai/workspaces/commit/d7b62247e956af89ab5deae61c8919f1c2eba7ee) Thanks [@Frozen666](https://github.com/Frozen666)! - feat(runner): register connectors from the `session_init` resolvedConfig when disk declares none. New `resolvedConfigToDeclarations()` export in `@skaile/workspaces/connectors` maps the Protocol v3 wire mounts/connectors (flat `source`/`target`/`watch`) to `ConnectorDeclaration[]`; serve mode passes the result as the new optional `AgentSessionConfig.fallbackConnectorDeclarations`. Disk-first invariant: the fallback engages only when `skaile.yaml` + materialized assets yield zero declarations (broker mode, where no yaml file is bound into the container) — any disk declaration keeps behavior byte-identical to before. CLI/flow/REPL callers are unaffected.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#364](https://github.com/skaile-ai/workspaces/pull/364) [`53380bc`](https://github.com/skaile-ai/workspaces/commit/53380bc9dc836a4ae6ed994addc1be76458e58c9) Thanks [@Frozen666](https://github.com/Frozen666)! - fix(cli): configure the SuperJSON transformer on the remote `session logs` tRPC client (query + tail). The platform tRPC server uses SuperJSON, so without a matching transformer every remote call failed with "Unable to transform response from server", making `skaile session logs --platform-url …` unusable against the deployed platform.
12
+
13
+ - [#372](https://github.com/skaile-ai/workspaces/pull/372) [`8cea0fd`](https://github.com/skaile-ai/workspaces/commit/8cea0fde55ed5b9a91e84d31afa02f85b60a61d4) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Fix four issues surfaced in a CLI test round:
14
+
15
+ - `source manifest publish` now defaults the publisher namespace to the verified GitHub handle (`githubLogin ?? username`), matching the `library create` / publish-preflight precedence. A user whose store login differs from their GitHub handle no longer defaults to a reserved `@<login>` namespace (`--as` still overrides).
16
+ - `install --locked` now restores bundled factory (`@skaile-ai/*`) assets. The bundled factory source shares the ai-assets identity URL but ships its bytes in the package; lock verification now resolves it to its bundled tree (and skips the fetch) instead of the ai-assets clone, whose tree never contained the bundled paths — which made `verifyLock` false-report every factory asset as `missing`.
17
+ - `skaile manage` now exits with a clear "requires an interactive terminal" message when stdin is not a TTY, instead of an uncaught `setRawMode` TypeError.
18
+ - The `preset init` template declares placeholders per-item (schema-valid) instead of at the top level, so authored placeholders resolve and `preset validate` counts them.
19
+
20
+ - [#357](https://github.com/skaile-ai/workspaces/pull/357) [`6b90565`](https://github.com/skaile-ai/workspaces/commit/6b905650ae8b8ea474dd8fff56f96695ee63c78c) Thanks [@P1umPudding](https://github.com/P1umPudding)! - `library publish --dry-run` now surfaces a namespace-availability status line
21
+ when you are signed in to the store. Before committing a publish you can see
22
+ whether the manifest's `publisher` namespace is yours, claimable-by-you,
23
+ already reserved, or freely available — reusing the store's own read-only
24
+ availability probe so the advisory can't drift from what a real publish
25
+ enforces. The check is best-effort: it makes a single read-only GET and is
26
+ silently skipped when offline or not signed in, so the dry-run stays fully
27
+ offline-safe.
28
+
29
+ - [#359](https://github.com/skaile-ai/workspaces/pull/359) [`ee301ed`](https://github.com/skaile-ai/workspaces/commit/ee301ed8d18d2f00e69f0f3ac3b4acc6c536871a) Thanks [@P1umPudding](https://github.com/P1umPudding)! - `library create` now stamps the author's verified store identity (GitHub handle,
30
+ else Keycloak username) as the asset publisher instead of the library name — the
31
+ library name was almost never a namespace the author could publish, so publishes
32
+ only failed at the store. `resolveLibraryPublisher` gains an injected store-identity
33
+ fallback (precedence: `--publisher` → domain → store identity → library name), and
34
+ a new `getStoreIdentity()` decodes it locally from the cached token (no network).
35
+
36
+ `library publish` / `register` now preflight the target namespace against the
37
+ store's availability probe **before** the public git push: a `reserved` or
38
+ `claimable-by-you` namespace aborts fast with guided next steps (claim it / connect
39
+ GitHub) instead of pushing bytes and then failing with a raw 422. The probe is
40
+ advisory — an offline failure never blocks a valid publish.
41
+
42
+ - [#355](https://github.com/skaile-ai/workspaces/pull/355) [`5cade1f`](https://github.com/skaile-ai/workspaces/commit/5cade1f812ea08d50cfccfff06e41aede9d794f1) Thanks [@P1umPudding](https://github.com/P1umPudding)! - `library publish` / `register` / `source manifest publish` now send the manifest's
43
+ `publisher` as the target `namespace` to the store's `sources.add`. When it
44
+ matches your store login the store takes the personal path (unchanged); when it's
45
+ a **claimed** namespace (e.g. `@P1umPudding` ≠ your login), the store resolves the
46
+ claimed Publisher, so you can publish under a namespace that isn't your login
47
+ username. Requires the namespace to be claimed in the storefront first.
48
+
49
+ - [#367](https://github.com/skaile-ai/workspaces/pull/367) [`9f86b3a`](https://github.com/skaile-ai/workspaces/commit/9f86b3a17146bbe9508eb79de132c9651bd23f77) Thanks [@P1umPudding](https://github.com/P1umPudding)! - `skaile store login` / `whoami` no longer conflate your login name with your
50
+ publisher namespace. Login showed `Signed in as @plumpudding` and `whoami`
51
+ labelled `@plumpudding` as your **Publisher** — but `@plumpudding` is your
52
+ Keycloak login, not a namespace you publish under (your namespace is your GitHub
53
+ handle, e.g. `@P1umPudding`). Now: login says `Signed in as plumpudding —
54
+ publishing as @P1umPudding`, and `whoami` shows `Login` and `Publisher` (the
55
+ connected GitHub handle, or "connect GitHub to publish") as separate lines.
56
+
57
+ ## 1.6.1
58
+
59
+ ### Patch Changes
60
+
61
+ - [#354](https://github.com/skaile-ai/workspaces/pull/354) [`2e88f72`](https://github.com/skaile-ai/workspaces/commit/2e88f722b4b2df4888601ebfa3cf635264a004e7) Thanks [@henkbla](https://github.com/henkbla)! - fix(app-sdk): re-send the ready ping until the host answers
62
+
63
+ `resolveAppConnectionParams` posted `skaile_app_ready` exactly once and then waited
64
+ out its timeout. The host only mints a connection-param ticket in response to a ping
65
+ and never speaks unprompted, so an app that booted before the host's own state had
66
+ settled lost the race permanently: its single ping was dropped, nothing re-asked, and
67
+ the app failed with `timed out after 10000ms waiting for connection params from the
68
+ host` against a healthy, minting host.
69
+
70
+ The ping now repeats until answered, backing off 100 ms → 1 s within the same
71
+ `timeoutMs` deadline (the host mints a single-use ticket per answered ping, so the
72
+ backoff keeps an unlucky boot from churning tickets). Behaviour is unchanged when the
73
+ host is already ready — still one ping, one ticket.
74
+
75
+ - [#347](https://github.com/skaile-ai/workspaces/pull/347) [`42f1285`](https://github.com/skaile-ai/workspaces/commit/42f1285b145aa055d2956c753461e4d71c2567c1) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Make locally-authored library assets consumable without publishing. `skaile add` /
76
+ `skaile install` now fall back to the user's registered libraries when the
77
+ source/store pass misses: each library is scanned on disk under its declared
78
+ publisher and a `kind:@publisher/name` match is deployed through the normal copy
79
+ pipeline and recorded as a dependency (never written to the lock — a local
80
+ authoring place is not a reproducible source). Version pins are honoured with the
81
+ same `matchPin` SemVer-range matcher the source resolver uses.
82
+
83
+ `skaile asset install` / `asset show` additionally normalize the `kind:@pub/name`
84
+ wire ref (with or without a `#version` range/exact/SHA pin) instead of requiring
85
+ the fully-canonical stored id, and `asset install --from <library>` now scopes
86
+ resolution to that library rather than only warning on a mismatch. Closes the
87
+ `library create` → `add` loop end-to-end.
88
+
89
+ `coerceVersion` and `matchPin` are now exported from `@skaile/workspaces/core`.
90
+
91
+ - [#350](https://github.com/skaile-ai/workspaces/pull/350) [`c786467`](https://github.com/skaile-ai/workspaces/commit/c7864673bcdb29e013649c8fb93ac0d4691064ee) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Fix two gaps that blocked the `library create → publish` round-trip:
92
+
93
+ - `library publish --create-repo --dry-run` no longer actually creates a GitHub
94
+ repo — the `gh repo create` step was running before the dry-run guard. A
95
+ dry-run now just shows the would-be remote.
96
+ - `library create` now registers the scaffolded asset in the manifest `assets[]`
97
+ (the inventory `library publish` filters by), so a freshly-created asset is
98
+ actually publishable instead of yielding "0 assets". `library index` reconciles
99
+ the inventory with disk, so a hand-added asset (created outside `library
100
+ create`) becomes publishable too — without dropping curated entries.
101
+
102
+ - [#347](https://github.com/skaile-ai/workspaces/pull/347) [`42f1285`](https://github.com/skaile-ai/workspaces/commit/42f1285b145aa055d2956c753461e4d71c2567c1) Thanks [@P1umPudding](https://github.com/P1umPudding)! - `skaile manage` TUI: make library assets first-class in the browser.
103
+
104
+ - **Libraries tab** is now an expandable, installable drill-in tree (`Libraries → library → domain → asset`) mirroring the Sources tab — expand/collapse with `←/→`, bulk-select a whole library from its header, install/remove per asset — instead of a read-only management list.
105
+ - **Assets tab**: library assets group under a clean `<library-name>` band with no `library:` prefix and no redundant same-named domain level (previously `libraries > library:my-lib > library:my-lib > greet`).
106
+ - **Assets tab**: every origin band except the built-in `factory` now default-collapses (one-shot, a user expand survives reloads), so a project with many sources/libraries opens compact.
107
+
108
+ - [#349](https://github.com/skaile-ai/workspaces/pull/349) [`64eeaa4`](https://github.com/skaile-ai/workspaces/commit/64eeaa4a3101e31934ec19d554a4c7dd44eef261) Thanks [@P1umPudding](https://github.com/P1umPudding)! - `skaile store login`: implement PKCE (RFC 7636, S256) in the Keycloak device
109
+ flow. The `skaile-store-cli` client enforces PKCE, so the device-authorization
110
+ request now sends a `code_challenge` + `code_challenge_method=S256` and the token
111
+ poll sends the matching `code_verifier`. Without it Keycloak rejected the request
112
+ with `invalid_request: Missing parameter: code_challenge_method`.
113
+
114
+ - [#352](https://github.com/skaile-ai/workspaces/pull/352) [`b8c30ce`](https://github.com/skaile-ai/workspaces/commit/b8c30ce9ad597244264c30a61a07819a1325503b) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Surface the store's human-readable error on `library publish` / `register` /
115
+ `yank`. The store returns `{ error, message }` on failures; the CLI threw the
116
+ bare `error` code (`UNPROCESSABLE_CONTENT`) and — because the publish commands
117
+ didn't catch it — crashed with a raw stack trace. Now `storeFetch` prefers the
118
+ actionable `message` (e.g. "Namespace '@x' is reserved for the GitHub identity …
119
+ Sign in with GitHub to claim it") and the commands print it cleanly via `logErr`.
120
+
3
121
  ## 1.6.0
4
122
 
5
123
  ### Minor Changes
@@ -290,6 +290,9 @@ function createSkaileApp(options) {
290
290
  // app-sdk/src/connection-params.ts
291
291
  var PARENT_ORIGIN_QUERY_PARAM = "skaileParentOrigin";
292
292
  var DEFAULT_PARAMS_TIMEOUT_MS = 1e4;
293
+ var INITIAL_PING_INTERVAL_MS = 100;
294
+ var MAX_PING_INTERVAL_MS = 1e3;
295
+ var PING_BACKOFF_FACTOR = 2;
293
296
  function readParentOriginFromQuery(search) {
294
297
  const value = new URLSearchParams(search).get(PARENT_ORIGIN_QUERY_PARAM);
295
298
  return value && value.length > 0 ? value : null;
@@ -324,10 +327,15 @@ function resolveAppConnectionParams(options) {
324
327
  if (parentOrigin === "*" || parentOrigin === "null") {
325
328
  return Promise.reject(new Error(`refusing to use an unpinnable host origin: ${parentOrigin}`));
326
329
  }
330
+ const peer = parent;
331
+ const peerOrigin = parentOrigin;
327
332
  return new Promise((resolve, reject) => {
328
333
  let settled = false;
334
+ let pingTimer;
335
+ let pingInterval = INITIAL_PING_INTERVAL_MS;
329
336
  const cleanup = () => {
330
337
  clearTimeout(timer);
338
+ clearTimeout(pingTimer);
331
339
  win.removeEventListener("message", onMessage);
332
340
  };
333
341
  const timer = setTimeout(() => {
@@ -340,7 +348,7 @@ function resolveAppConnectionParams(options) {
340
348
  }, timeoutMs);
341
349
  function onMessage(event) {
342
350
  if (settled) return;
343
- if (event.origin !== parentOrigin || event.source !== parent) return;
351
+ if (event.origin !== peerOrigin || event.source !== peer) return;
344
352
  if (!isParamsMessage(event.data, appId)) return;
345
353
  settled = true;
346
354
  cleanup();
@@ -348,13 +356,20 @@ function resolveAppConnectionParams(options) {
348
356
  }
349
357
  win.addEventListener("message", onMessage);
350
358
  const ready = { type: "skaile_app_ready", appId };
351
- try {
352
- parent.postMessage(ready, parentOrigin);
353
- } catch (err) {
354
- settled = true;
355
- cleanup();
356
- reject(err instanceof Error ? err : new Error(String(err)));
359
+ function ping() {
360
+ if (settled) return;
361
+ try {
362
+ peer.postMessage(ready, peerOrigin);
363
+ } catch (err) {
364
+ settled = true;
365
+ cleanup();
366
+ reject(err instanceof Error ? err : new Error(String(err)));
367
+ return;
368
+ }
369
+ pingTimer = setTimeout(ping, pingInterval);
370
+ pingInterval = Math.min(pingInterval * PING_BACKOFF_FACTOR, MAX_PING_INTERVAL_MS);
357
371
  }
372
+ ping();
358
373
  });
359
374
  }
360
375
  function defineAppCapability(args) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../app-sdk/src/logger.ts","../../app-sdk/src/app.ts","../../app-sdk/src/connection-params.ts","../../app-sdk/src/define-app-capability.ts","../../app-sdk/src/ws-transport.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Minimal logger for the app SDK.\n *\n * The SDK is meant to run inside an embedded app (browser or a lightweight\n * Node service), so it deliberately does not pull in the framework's\n * `LogStore` / SQLite logging stack. Authors can pass their own {@link AppLogger}\n * (e.g. a bridge to their app's telemetry) or accept the console-backed default.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\n/** Structured leveled logger the SDK and capability handlers emit through. */\nexport interface AppLogger {\n debug(message: string, data?: unknown): void;\n info(message: string, data?: unknown): void;\n warn(message: string, data?: unknown): void;\n error(message: string, data?: unknown): void;\n}\n\n/** A logger that discards everything. The default when none is supplied. */\nexport const noopLogger: AppLogger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n};\n\n/**\n * Console-backed logger, prefixing every line with `[skaile-app]`. Opt in by\n * passing `logger: consoleLogger` to {@link createSkaileApp} during development.\n */\nexport const consoleLogger: AppLogger = {\n debug(message, data) {\n console.debug(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n info(message, data) {\n console.info(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n warn(message, data) {\n console.warn(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n error(message, data) {\n console.error(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n};\n","/**\n * `createSkaileApp` — the thin app SDK that makes an embedded app\n * agent-controllable.\n *\n * An app becomes a *capability peer*, mirroring how the runner behaves toward\n * the platform:\n *\n * 1. Opens an authenticated back-connection (via an {@link AppTransport}).\n * 2. Performs the app handshake — sends `app_hello`, awaits `app_hello_ack`\n * (binding the connection to `session + appId`) or `app_hello_nack` (refusal).\n * 3. Registers its `app.*` capabilities via `capability_register`, stamping\n * each with `origin:{kind:'app', appId}` and `side:'app'`. Register /\n * deregister mid-session is supported.\n * 4. Answers inbound `capability_invoked` events by running the local handler\n * and replying with `capability_result` (a thrown handler rides back as\n * `{ error }`).\n *\n * The approval round-trip for `requiresApproval` capabilities is handled\n * entirely platform-side: the gateway parks the invocation for user approval\n * and only forwards `capability_invoked` to the app *after* approval, so the\n * app just marks the capability and answers when asked.\n *\n * The peer reconnects and replays its registrations on a dropped socket, and on\n * a *retryable* refusal — the app boots independently of the session's agent\n * container and can win that race. See {@link ReconnectOptions}.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { Capability } from \"@skaile/workspaces/types\";\nimport { PROTOCOL_VERSION_STRING } from \"@skaile/workspaces/types\";\nimport type { DefinedAppCapability } from \"./define-app-capability.js\";\nimport { type AppLogger, noopLogger } from \"./logger.js\";\nimport type { AppServerMessage, AppTransport } from \"./transport.js\";\n\n/** Default timeout waiting for the gateway's `app_hello_ack`. */\nexport const DEFAULT_HELLO_TIMEOUT_MS = 10_000;\n\n/** Defaults mirroring the agent `WebSocketClientTransport`'s backoff. */\nconst DEFAULT_RECONNECT: Required<ReconnectOptions> = {\n maxAttempts: 5,\n baseMs: 1_000,\n maxMs: 30_000,\n};\n\n/**\n * Reconnect / retry policy. Set `reconnect: false` on {@link SkaileAppOptions}\n * to disable entirely (one attempt, no replay).\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport interface ReconnectOptions {\n /** Attempts before giving up. Default 5. */\n maxAttempts?: number;\n /** First backoff delay in ms; doubles each attempt. Default 1000. */\n baseMs?: number;\n /** Backoff ceiling in ms. Default 30000. */\n maxMs?: number;\n}\n\n/**\n * The gateway refused the handshake. `retryable` mirrors the wire field: `true`\n * for a transient refusal (the agent container is not up yet), `false` for a\n * permanent one (bad token, `appId` not permitted).\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport class AppHandshakeRefusedError extends Error {\n readonly retryable: boolean;\n constructor(reason: string, retryable: boolean) {\n super(`app handshake refused: ${reason}`);\n this.name = \"AppHandshakeRefusedError\";\n this.retryable = retryable;\n }\n}\n\n/**\n * Options for {@link createSkaileApp}.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface SkaileAppOptions {\n /** The embedded app's stable id. Every registered capability is bound to it. */\n appId: string;\n /**\n * The app back-connection. Inject {@link createWebSocketAppTransport} in\n * production or a fake in tests. The transport carries the injected session\n * identity as its connection credential.\n *\n * With `reconnect` enabled the transport must be able to produce a **fresh**\n * token per attempt — the gateway consumes it at `app_hello`. Pass a\n * `SessionTokenProvider` rather than a bare string.\n */\n transport: AppTransport;\n /** Protocol version the SDK announces in `app_hello`. Defaults to the SDK's. */\n protocolVersion?: string;\n /** Timeout waiting for `app_hello_ack`. Defaults to {@link DEFAULT_HELLO_TIMEOUT_MS}. */\n helloTimeoutMs?: number;\n /** Logger for the SDK and (by default) capability handlers. Defaults to {@link noopLogger}. */\n logger?: AppLogger;\n /**\n * Reconnect policy, or `false` to disable. Defaults to\n * `{maxAttempts:5, baseMs:1000, maxMs:30000}`.\n *\n * @since 3.5.0\n */\n reconnect?: ReconnectOptions | false;\n}\n\n/** Result of a successful handshake — the bound session context. */\nexport interface AppSession {\n sessionId: string;\n userId?: string;\n}\n\n/**\n * A connected (or connectable) Skaile app. Register capabilities before or\n * after {@link connect}; post-connect changes are pushed to the gateway\n * immediately.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface SkaileApp {\n /**\n * Add (or replace, by name) an app capability. Registering the same name\n * again replaces it — mirrors the registry's replace-by-name semantics. When\n * already connected, a `capability_register` is sent immediately.\n */\n registerCapability(cap: DefinedAppCapability): void;\n /**\n * Remove a capability by name. No-op when unknown. When connected, a\n * `capability_deregister` is sent immediately so the tool disappears from the\n * agent's catalog.\n */\n deregisterCapability(name: string): void;\n /** Wire-format snapshot of the currently registered capabilities (origin stamped). */\n readonly capabilities: readonly Capability[];\n /** The bound session once connected; `null` before the handshake and while reconnecting. */\n readonly session: AppSession | null;\n /**\n * Open the connection, perform the handshake, and register the current\n * capabilities. Resolves with the bound {@link AppSession}. Rejects if the\n * gateway refuses permanently, the handshake times out, or the transport\n * disconnects mid-handshake. A *retryable* refusal is retried per the\n * reconnect policy before rejecting.\n */\n connect(): Promise<AppSession>;\n /**\n * Subscribe to session binding changes. Fires on **every** bind — including\n * the initial `connect()` — with the new {@link AppSession}, and with `null`\n * whenever the connection drops. Returns an unsubscribe function.\n *\n * @since 3.5.0\n */\n onSessionChange(handler: (session: AppSession | null) => void): () => void;\n /** Disconnect and tear down. Idempotent. Cancels any pending reconnect. */\n close(): Promise<void>;\n}\n\nconst delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/**\n * Create a {@link SkaileApp}. See the module doc for the peer lifecycle.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function createSkaileApp(options: SkaileAppOptions): SkaileApp {\n const {\n appId,\n transport,\n protocolVersion = PROTOCOL_VERSION_STRING,\n helloTimeoutMs = DEFAULT_HELLO_TIMEOUT_MS,\n logger = noopLogger,\n } = options;\n\n const reconnectEnabled = options.reconnect !== false;\n const policy: Required<ReconnectOptions> = {\n ...DEFAULT_RECONNECT,\n ...(options.reconnect === false ? {} : options.reconnect),\n };\n\n const registry = new Map<string, DefinedAppCapability>();\n const sessionListeners = new Set<(s: AppSession | null) => void>();\n let session: AppSession | null = null;\n let closed = false;\n let connecting = false;\n let subscribed = false;\n // Bumped by close(). A reconnect loop parked on its backoff timer captures the\n // epoch it started in and abandons if close() (or a close/connect cycle) has\n // since happened — otherwise it would resurrect a torn-down app.\n let epoch = 0;\n let unsubscribeMessage: (() => void) | null = null;\n let unsubscribeDisconnect: (() => void) | null = null;\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n /** Resolver of the backoff sleep, so close() can wake a parked loop. */\n let reconnectWake: (() => void) | null = null;\n\n /** Non-null only while a handshake is in flight. */\n let pending: { settle: (err: Error | null, s?: AppSession) => void } | null = null;\n\n function toWire(cap: DefinedAppCapability): Capability {\n return {\n name: cap.name,\n displayName: cap.displayName,\n description: cap.description,\n inputSchema: cap.inputSchema,\n outputSchema: cap.outputSchema,\n side: \"app\",\n origin: { kind: \"app\", appId },\n scope: cap.scope,\n requiresApproval: cap.requiresApproval,\n kind: cap.kind,\n audience: cap.audience,\n promptFragment: cap.promptFragment,\n callTimeoutMs: cap.callTimeoutMs,\n render: cap.render,\n };\n }\n\n function setSession(next: AppSession | null): void {\n session = next;\n for (const h of sessionListeners) h(next);\n }\n\n function registerCapability(cap: DefinedAppCapability): void {\n registry.set(cap.name, cap);\n if (session) {\n transport.send({ type: \"capability_register\", capabilities: [toWire(cap)] });\n logger.debug(\"registered capability (live)\", { name: cap.name });\n }\n }\n\n function deregisterCapability(name: string): void {\n if (!registry.delete(name)) return;\n if (session) {\n transport.send({ type: \"capability_deregister\", names: [name] });\n logger.debug(\"deregistered capability (live)\", { name });\n }\n }\n\n /** Dispatch an inbound invocation to the local handler; reply with the result. */\n async function handleInvocation(callId: string, name: string, input: unknown): Promise<void> {\n const cap = registry.get(name);\n if (!cap) {\n transport.send({\n type: \"capability_result\",\n callId,\n result: { error: `Unknown app capability: ${name}` },\n });\n return;\n }\n try {\n const validated = cap.inputZod.parse(input);\n const ctx = { sessionId: session?.sessionId ?? \"\", appId, log: logger };\n const result = await cap.handler(validated, ctx);\n transport.send({ type: \"capability_result\", callId, result });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn(\"capability handler failed\", { name, error: message });\n transport.send({ type: \"capability_result\", callId, result: { error: message } });\n }\n }\n\n function onMessage(message: AppServerMessage): void {\n switch (message.type) {\n case \"app_hello_ack\": {\n const bound: AppSession = { sessionId: message.sessionId, userId: message.userId };\n pending?.settle(null, bound);\n return;\n }\n case \"app_hello_nack\":\n pending?.settle(new AppHandshakeRefusedError(message.error, message.retryable === true));\n return;\n case \"capability_invoked\":\n void handleInvocation(message.callId, message.name, message.input);\n return;\n }\n }\n\n function onDisconnect(): void {\n if (pending) {\n pending.settle(new Error(\"transport disconnected during handshake\"));\n return;\n }\n if (closed || !session) return;\n logger.warn(\"app connection lost\", { appId });\n setSession(null);\n if (!reconnectEnabled) return;\n // The loop holds the same guard connect() uses: a manual connect() while it\n // runs would start a second handshake, and both would overwrite `pending`.\n connecting = true;\n const loopEpoch = epoch;\n void reconnectLoop().finally(() => {\n // A close()/connect() cycle since this loop started owns the guard now.\n if (epoch === loopEpoch) connecting = false;\n });\n }\n\n function ensureSubscribed(): void {\n if (subscribed) return;\n unsubscribeMessage = transport.onMessage(onMessage);\n unsubscribeDisconnect = transport.onDisconnect(onDisconnect);\n subscribed = true;\n }\n\n /** One attempt: open the socket, exchange hello/ack, replay the registry. */\n function performHandshake(): Promise<AppSession> {\n ensureSubscribed();\n return transport.connect().then(\n () =>\n new Promise<AppSession>((resolve, reject) => {\n if (closed) {\n reject(new Error(\"app closed during handshake\"));\n return;\n }\n let settled = false;\n const timer = setTimeout(() => {\n pending?.settle(new Error(`app handshake timed out after ${helloTimeoutMs}ms`));\n }, helloTimeoutMs);\n\n pending = {\n settle: (err, bound) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n pending = null;\n if (err || !bound) {\n reject(err ?? new Error(\"app handshake failed\"));\n return;\n }\n setSession(bound);\n logger.info(\"app connection established\", { appId, sessionId: bound.sessionId });\n // Replay the full surface — on a reconnect the gateway dropped our\n // caps when the previous connection closed.\n const wire = Array.from(registry.values()).map(toWire);\n if (wire.length > 0) {\n transport.send({ type: \"capability_register\", capabilities: wire });\n }\n resolve(bound);\n },\n };\n\n transport.send({ type: \"app_hello\", appId, protocolVersion });\n }),\n (err) => Promise.reject(err instanceof Error ? err : new Error(String(err))),\n );\n }\n\n const backoffMs = (attempt: number) =>\n Math.min(policy.maxMs, policy.baseMs * 2 ** Math.max(0, attempt - 1));\n\n /** Rebind after a dropped socket. Stops on a permanent refusal or close(). */\n async function reconnectLoop(): Promise<void> {\n const myEpoch = epoch;\n for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {\n const wait = backoffMs(attempt);\n logger.debug(\"scheduling app reconnect\", { appId, attempt, waitMs: wait });\n // close() cancels the timer, so it also has to hand us the resolver —\n // otherwise this await never settles and the loop frame parks forever,\n // pinning the transport and every other captured binding.\n await new Promise<void>((r) => {\n reconnectWake = r;\n reconnectTimer = setTimeout(r, wait);\n });\n reconnectWake = null;\n if (closed || epoch !== myEpoch) {\n logger.debug(\"app reconnect cancelled\", { appId, attempt });\n return;\n }\n try {\n await performHandshake();\n logger.info(\"app connection re-established\", { appId });\n return;\n } catch (err) {\n if (err instanceof AppHandshakeRefusedError && !err.retryable) {\n logger.error(\"app reconnect refused permanently\", { appId, error: err.message });\n return;\n }\n logger.warn(\"app reconnect attempt failed\", {\n appId,\n attempt,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n logger.error(\"app reconnect gave up\", { appId, attempts: policy.maxAttempts });\n }\n\n async function connect(): Promise<AppSession> {\n if (session) return session;\n if (connecting) throw new Error(\"app connect() is already in progress\");\n connecting = true;\n // A closed app is reusable: connect() reopens it. Any reconnect loop still\n // parked from the previous life is fenced off by the epoch close() bumped.\n closed = false;\n try {\n for (let attempt = 1; ; attempt++) {\n try {\n return await performHandshake();\n } catch (err) {\n // Only a *retryable* refusal is worth another attempt on the initial\n // connect: the app raced the session's agent container on boot. Every\n // other failure (bad token, timeout, transport error) is terminal here.\n const transient = err instanceof AppHandshakeRefusedError && err.retryable;\n if (!transient || !reconnectEnabled || attempt >= policy.maxAttempts || closed) {\n throw err;\n }\n logger.debug(\"app handshake refused (retryable), retrying\", { appId, attempt });\n await delay(backoffMs(attempt));\n }\n }\n } finally {\n connecting = false;\n }\n }\n\n async function close(): Promise<void> {\n closed = true;\n epoch++;\n clearTimeout(reconnectTimer);\n // Wake a loop parked on its backoff so it observes `closed` and returns,\n // rather than awaiting a timer that will now never fire.\n reconnectWake?.();\n reconnectWake = null;\n connecting = false;\n // Abort an in-flight handshake — rejects the pending connect and clears its\n // timer so nothing leaks past close().\n pending?.settle(new Error(\"app closed during handshake\"));\n unsubscribeMessage?.();\n unsubscribeDisconnect?.();\n unsubscribeMessage = null;\n unsubscribeDisconnect = null;\n subscribed = false;\n session = null;\n await transport.disconnect();\n }\n\n return {\n registerCapability,\n deregisterCapability,\n get capabilities() {\n return Array.from(registry.values()).map(toWire);\n },\n get session() {\n return session;\n },\n connect,\n onSessionChange(handler) {\n sessionListeners.add(handler);\n return () => sessionListeners.delete(handler);\n },\n close,\n };\n}\n","/**\n * Connection-param handshake between the embedding host and the app frame.\n *\n * An embedded app needs three things to open its back-connection: the gateway\n * URL, its own `appId`, and a session identity token. The token is a **secret**,\n * so it must not ride in the iframe `src` — the preview surface that serves the\n * app is unauthenticated, and a URL lands in access logs, `Referer` headers, and\n * browser history. Instead the host posts it over `postMessage`, and the app\n * asks for it with a ready ping once its script is running.\n *\n * The host cannot simply post on iframe `load`: it has no way to know when the\n * app's script is listening. So the app speaks first.\n *\n * ```text\n * app → host { type: 'skaile_app_ready', appId } targetOrigin = parentOrigin\n * host → app { type: 'skaile_app_params', appId, url, token } targetOrigin = appOrigin\n * ```\n *\n * Both sides pin the peer: the app checks `event.origin === parentOrigin` and\n * that the message came from its own parent; the host checks the origin and that\n * `event.source` is the very iframe it rendered. The app learns the expected\n * parent origin from a **non-secret** query param on its `src`\n * ({@link PARENT_ORIGIN_QUERY_PARAM}), which is safe to leak precisely because it\n * is not a credential.\n *\n * @category App SDK\n * @since 3.5.0\n */\n\n/**\n * Query param on the app's iframe `src` carrying the embedder's origin. Not a\n * secret — it is the value the app pins its `postMessage` peer to.\n */\nexport const PARENT_ORIGIN_QUERY_PARAM = \"skaileParentOrigin\";\n\n/** Default timeout waiting for the host's params reply. */\nexport const DEFAULT_PARAMS_TIMEOUT_MS = 10_000;\n\n/** App → host. Announces the app frame is listening and wants its params. */\nexport type SkaileAppReadyMessage = {\n type: \"skaile_app_ready\";\n /** The app asking. The host answers only for an app it actually embedded. */\n appId: string;\n};\n\n/** Host → app. Carries the gateway URL and the single-use session identity token. */\nexport type SkaileAppParamsMessage = {\n type: \"skaile_app_params\";\n /** Echoed back so a frame embedding several apps can disambiguate. */\n appId: string;\n /** The gateway app-channel URL, e.g. `wss://platform/app-connect`. */\n url: string;\n /** Single-use session identity token. Consumed by the gateway at `app_hello`. */\n token: string;\n};\n\n/** Resolved params, ready to hand to `createWebSocketAppTransport` + `createSkaileApp`. */\nexport type AppConnectionParams = {\n url: string;\n appId: string;\n token: string;\n};\n\n/** The subset of `postMessage` targets this module needs. */\nexport type PostMessageTarget = {\n postMessage(message: unknown, targetOrigin: string): void;\n};\n\n/** Structural view of a `MessageEvent`, so tests need no DOM. */\nexport type MessageEventLike = {\n readonly origin: string;\n readonly source: unknown;\n readonly data: unknown;\n};\n\n/** Structural view of the `window` bits this module needs. Injectable for tests. */\nexport type AppWindowLike = {\n readonly parent: PostMessageTarget | null;\n readonly location: { readonly search: string };\n addEventListener(type: \"message\", handler: (event: MessageEventLike) => void): void;\n removeEventListener(type: \"message\", handler: (event: MessageEventLike) => void): void;\n};\n\n/**\n * Options for {@link resolveAppConnectionParams}.\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport interface ResolveAppConnectionParamsOptions {\n /** The embedded app's stable id. Must match the params the host replies with. */\n appId: string;\n /**\n * Expected origin of the embedding host. Defaults to the\n * {@link PARENT_ORIGIN_QUERY_PARAM} query param on the current URL. Never `\"*\"`\n * or `\"null\"` — an unpinned peer defeats the point of not putting the token in\n * the URL.\n */\n parentOrigin?: string;\n /** How long to wait for the host's reply. Defaults to {@link DEFAULT_PARAMS_TIMEOUT_MS}. */\n timeoutMs?: number;\n /** Window to listen and post on. Defaults to the global. Injectable for tests. */\n windowRef?: AppWindowLike;\n}\n\nfunction readParentOriginFromQuery(search: string): string | null {\n const value = new URLSearchParams(search).get(PARENT_ORIGIN_QUERY_PARAM);\n return value && value.length > 0 ? value : null;\n}\n\nfunction isParamsMessage(data: unknown, appId: string): data is SkaileAppParamsMessage {\n if (!data || typeof data !== \"object\") return false;\n const m = data as Partial<SkaileAppParamsMessage>;\n // Trim before the emptiness check: a whitespace-only token would survive to\n // the gateway and be refused there. Failing here is the clearer error.\n return (\n m.type === \"skaile_app_params\" &&\n m.appId === appId &&\n typeof m.url === \"string\" &&\n m.url.trim().length > 0 &&\n typeof m.token === \"string\" &&\n m.token.trim().length > 0\n );\n}\n\n/**\n * Ask the embedding host for this app's connection params.\n *\n * Posts a {@link SkaileAppReadyMessage} to the parent and resolves with the\n * matching {@link SkaileAppParamsMessage}. Messages from any other origin, from\n * any window other than the parent, or for another `appId` are ignored — not\n * rejected — because a page may legitimately receive unrelated `postMessage`\n * traffic.\n *\n * Call it again to re-request a fresh token: the token is single-use, so a\n * reconnecting transport needs a new one (see `createSkaileApp`'s `reconnect`).\n *\n * @throws when not embedded, when the parent origin cannot be determined or is\n * unpinnable, or when the host does not reply within `timeoutMs`.\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport function resolveAppConnectionParams(\n options: ResolveAppConnectionParamsOptions,\n): Promise<AppConnectionParams> {\n const { appId, timeoutMs = DEFAULT_PARAMS_TIMEOUT_MS } = options;\n const win =\n options.windowRef ?? (globalThis as unknown as { window?: AppWindowLike }).window ?? null;\n if (!win) {\n return Promise.reject(\n new Error(\"resolveAppConnectionParams requires a window (pass `windowRef` off-browser)\"),\n );\n }\n\n const parent = win.parent;\n // A top-level page's `parent` is itself. An app that is not embedded has no\n // host to ask, and posting to itself would hang until the timeout.\n if (!parent || (parent as unknown) === (win as unknown)) {\n return Promise.reject(\n new Error(\"app is not embedded — no parent frame to request params from\"),\n );\n }\n\n const parentOrigin = options.parentOrigin ?? readParentOriginFromQuery(win.location.search);\n if (!parentOrigin) {\n return Promise.reject(\n new Error(\n `cannot determine the host origin: pass \\`parentOrigin\\` or set the \\`${PARENT_ORIGIN_QUERY_PARAM}\\` query param`,\n ),\n );\n }\n // `*` would broadcast the ready ping to whatever is above us, and `null` (an\n // opaque, sandboxed origin) cannot be pinned at all. Both make the peer check\n // meaningless, and the token would then be posted to an unverified window.\n if (parentOrigin === \"*\" || parentOrigin === \"null\") {\n return Promise.reject(new Error(`refusing to use an unpinnable host origin: ${parentOrigin}`));\n }\n\n return new Promise<AppConnectionParams>((resolve, reject) => {\n let settled = false;\n\n const cleanup = () => {\n clearTimeout(timer);\n win.removeEventListener(\"message\", onMessage);\n };\n\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(\n new Error(`timed out after ${timeoutMs}ms waiting for connection params from the host`),\n );\n }, timeoutMs);\n\n function onMessage(event: MessageEventLike): void {\n if (settled) return;\n // Pin the peer on both axes. Origin alone is not enough: any frame served\n // from the host's origin could otherwise answer.\n if (event.origin !== parentOrigin || event.source !== parent) return;\n if (!isParamsMessage(event.data, appId)) return;\n settled = true;\n cleanup();\n resolve({ url: event.data.url, appId, token: event.data.token });\n }\n\n win.addEventListener(\"message\", onMessage);\n\n const ready: SkaileAppReadyMessage = { type: \"skaile_app_ready\", appId };\n try {\n parent.postMessage(ready, parentOrigin);\n } catch (err) {\n settled = true;\n cleanup();\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n });\n}\n","/**\n * `defineAppCapability` — author-friendly factory for an embedded app's\n * agent-controllable capabilities.\n *\n * It mirrors the runner's `defineCapability` (`@skaile/workspaces/runner`) but\n * is scoped to the app side: the handler always runs *in the app*, so `side` is\n * fixed to `'app'` and the author never passes it. The originating `appId` is\n * known to the SDK, not the individual capability, so `origin` is stamped by\n * {@link createSkaileApp} at registration time — not here.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { CapabilityAudience, CapabilityOrigin, RenderSpec } from \"@skaile/workspaces/types\";\nimport * as z from \"zod\";\nimport type { AppLogger } from \"./logger.js\";\n\n/**\n * Per-invocation context passed to an app capability handler. Analogous to the\n * runner's `HandlerContext`, but carries the `appId` and the SDK's\n * {@link AppLogger} (there is no server-side `capability:<origin>:<name>` log\n * slice inside the app process).\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type AppHandlerContext = {\n /** The session this app connection is bound to (from the hello ack). */\n sessionId: string;\n /** The embedded app's id. */\n appId: string;\n /** Logger to emit through; defaults to the SDK's configured logger. */\n log: AppLogger;\n};\n\n/**\n * Author-facing arguments to {@link defineAppCapability}. Mirrors the wire\n * {@link import(\"@skaile/workspaces/types\").Capability} fields an app may set,\n * minus `side` / `origin` (fixed / stamped by the SDK) and the runtime-only\n * fields that only make sense platform-side.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type DefineAppCapabilityArgs<\n T extends z.ZodTypeAny,\n R extends z.ZodTypeAny | undefined = undefined,\n> = {\n /**\n * Stable, session-unique name. Convention: `app.<sector>.<verb>` — e.g.\n * `app.ui.switch_contract` (UI sector) or `app.backend.insert_contract`\n * (backend sector). v1 distinguishes sectors by this naming convention.\n */\n name: string;\n /** Human-readable label for user-facing surfaces; falls back to `name`. */\n displayName?: string;\n /** Human/LLM-readable summary; shown to the agent at tool registration. */\n description: string;\n /** Zod schema validating the invocation arguments. Converted to JSON Schema. */\n input: T;\n /** Optional Zod schema for the return value. */\n output?: R;\n /** Lifetime: `session` survives the whole session, `turn` is dropped at end-of-turn. */\n scope?: \"session\" | \"turn\";\n /**\n * When true, the platform shows a confirm UI and waits for user approval\n * before the invocation is forwarded to the app. Mark every mutation\n * (backend writes, destructive UI actions) with this.\n */\n requiresApproval?: boolean;\n /** Documentation hint: side-effect, render, or pure query. */\n kind?: \"effect\" | \"render\" | \"query\";\n /**\n * Audience scoping. Omit for the default (`['llm','user']`). App capabilities\n * default to LLM-only in the command palette (`isUserInvokable`); set\n * `['llm','user']` on the user-facing actions you want to appear in cmdK.\n */\n audience?: CapabilityAudience[];\n /** Appended to the system prompt's `<CAPABILITIES>` section. */\n promptFragment?: string;\n /** Per-capability override for the platform's result round-trip timeout. */\n callTimeoutMs?: number;\n /** Render contract for capabilities that produce a UI surface. */\n render?: RenderSpec;\n /** The handler — runs in the app. Receives validated input + {@link AppHandlerContext}. */\n handler: (\n args: z.infer<T>,\n ctx: AppHandlerContext,\n ) => Promise<R extends z.ZodTypeAny ? z.infer<R> : unknown>;\n};\n\n/**\n * A defined app capability: the wire-format fields the SDK registers (minus the\n * SDK-stamped `origin`), plus the Zod schemas and type-erased handler used for\n * runtime input validation and inbound dispatch.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type DefinedAppCapability = {\n name: string;\n displayName?: string;\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema?: Record<string, unknown>;\n /** Always `'app'` — the handler runs in the embedded app. */\n side: \"app\";\n scope?: \"session\" | \"turn\";\n requiresApproval?: boolean;\n kind?: \"effect\" | \"render\" | \"query\";\n audience?: CapabilityAudience[];\n promptFragment?: string;\n callTimeoutMs?: number;\n render?: RenderSpec;\n /** Original Zod schema for runtime input validation. Not serialized to the wire. */\n readonly inputZod: z.ZodTypeAny;\n /** Original Zod schema for the optional output, if declared. Not serialized. */\n readonly outputZod?: z.ZodTypeAny;\n /** Type-erased handler dispatched on an inbound `capability_invoked`. */\n handler: (args: unknown, ctx: AppHandlerContext) => Promise<unknown>;\n};\n\n/** Origin kind an app capability always carries once stamped by the SDK. */\nexport type AppCapabilityOrigin = Extract<CapabilityOrigin, { kind: \"app\" }>;\n\n/**\n * Build a {@link DefinedAppCapability} from author-friendly Zod-typed args.\n * `side` is fixed to `'app'`; `origin` is added by the SDK at registration\n * time (it needs the SDK's `appId`).\n *\n * @example\n * ```ts\n * const switchContract = defineAppCapability({\n * name: 'app.ui.switch_contract',\n * description: 'Switch the visible contract in the app UI.',\n * input: z.object({ contractId: z.string() }),\n * handler: async ({ contractId }, ctx) => {\n * ctx.log.info('switching contract', { contractId });\n * ui.select(contractId);\n * },\n * });\n * ```\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function defineAppCapability<\n T extends z.ZodTypeAny,\n R extends z.ZodTypeAny | undefined = undefined,\n>(args: DefineAppCapabilityArgs<T, R>): DefinedAppCapability {\n const inputSchema = z.toJSONSchema(args.input, {\n target: \"draft-07\",\n io: \"input\",\n }) as Record<string, unknown>;\n const outputSchema = args.output\n ? (z.toJSONSchema(args.output, {\n target: \"draft-07\",\n io: \"output\",\n }) as Record<string, unknown>)\n : undefined;\n\n return {\n name: args.name,\n displayName: args.displayName,\n description: args.description,\n inputSchema,\n outputSchema,\n side: \"app\",\n scope: args.scope,\n requiresApproval: args.requiresApproval,\n kind: args.kind,\n audience: args.audience,\n promptFragment: args.promptFragment,\n callTimeoutMs: args.callTimeoutMs,\n render: args.render,\n inputZod: args.input,\n outputZod: args.output,\n handler: args.handler as (args: unknown, ctx: AppHandlerContext) => Promise<unknown>,\n };\n}\n","/**\n * WebSocket-backed {@link AppTransport}.\n *\n * Browser-safe: uses the standard `WebSocket` global (no `ws` dependency, no\n * `node:*` imports), so it runs unchanged in an embedded app's iframe and in a\n * lightweight Node 22+ service. The injected session identity is presented as a\n * bearer **subprotocol** — the same `skaile-bearer.<base64url>` scheme the\n * platform gateway already decodes for runner connections — so the gateway\n * verifies identity at the WS upgrade, before any `app_hello` is processed.\n *\n * Frames are bare JSON of the app-channel messages (no `MessageV2` envelope),\n * matching the runner peer's on-the-wire convention.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { AppClientMessage, AppServerMessage, AppTransport } from \"./transport.js\";\n\n/** Matches `BEARER_SUBPROTOCOL_PREFIX` in `@skaile/workspaces/transport`'s ws/auth. */\nconst BEARER_SUBPROTOCOL_PREFIX = \"skaile-bearer.\";\n\n/** Encode a bearer token as a WS subprotocol token (base64url, no padding). */\nfunction encodeBearerSubprotocol(token: string): string {\n // Encode via UTF-8 bytes so a non-ASCII token doesn't make `btoa` throw\n // (btoa is Latin-1 only). The gateway decodes the mirror of this.\n const binary = String.fromCharCode(...new TextEncoder().encode(token));\n const b64 = btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n return BEARER_SUBPROTOCOL_PREFIX + b64;\n}\n\n/** Minimal structural view of the parts of the `WebSocket` API the transport uses. */\ntype WebSocketLike = {\n readonly readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: \"open\", handler: () => void): void;\n addEventListener(type: \"close\", handler: () => void): void;\n addEventListener(type: \"error\", handler: () => void): void;\n addEventListener(type: \"message\", handler: (ev: { data: unknown }) => void): void;\n};\n\n/** Constructor shape for a `WebSocket` implementation (global or injected). */\nexport type WebSocketFactory = new (url: string, protocols?: string | string[]) => WebSocketLike;\n\n/**\n * Supplies a session identity token for one connection attempt.\n *\n * The gateway's token is **single-use** — it is consumed at `app_hello` — so a\n * reconnecting transport cannot replay the one it opened with. Pass a provider\n * (typically a call to `resolveAppConnectionParams`) and every `connect()` mints\n * a fresh token.\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport type SessionTokenProvider = () => string | Promise<string>;\n\n/**\n * Options for {@link createWebSocketAppTransport}.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface WebSocketAppTransportOptions {\n /** The gateway app-channel URL (e.g. `wss://platform/app-connect`). */\n url: string;\n /**\n * The injected session identity token, or a {@link SessionTokenProvider} that\n * yields a fresh one per connection attempt. Presented as the WS bearer\n * subprotocol; the gateway verifies it and resolves the session/user.\n *\n * A bare string cannot survive a reconnect (the gateway consumes the token at\n * `app_hello`) — pass a provider whenever `reconnect` is enabled on the peer.\n */\n sessionToken: string | SessionTokenProvider;\n /**\n * WebSocket implementation. Defaults to the `WebSocket` global (browser /\n * Node 22+). Injectable for tests or non-standard runtimes.\n */\n WebSocketImpl?: WebSocketFactory;\n /** Connect timeout in ms. Default 10000. */\n connectTimeoutMs?: number;\n}\n\nconst OPEN = 1;\n\n/**\n * Create a WebSocket-backed {@link AppTransport}. The session token is sent as a\n * bearer subprotocol at connect time; app-channel messages are JSON-framed.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function createWebSocketAppTransport(options: WebSocketAppTransportOptions): AppTransport {\n const { url, sessionToken, connectTimeoutMs = 10_000 } = options;\n const resolved =\n options.WebSocketImpl ?? (globalThis as { WebSocket?: WebSocketFactory }).WebSocket;\n if (!resolved) {\n throw new Error(\n \"No WebSocket implementation available. Pass `WebSocketImpl` in a runtime without a global WebSocket.\",\n );\n }\n const WebSocketImpl: WebSocketFactory = resolved;\n\n let ws: WebSocketLike | null = null;\n const messageHandlers = new Set<(m: AppServerMessage) => void>();\n const disconnectHandlers = new Set<() => void>();\n\n function connect(): Promise<void> {\n // A provider is resolved per attempt — the gateway consumes the token at\n // `app_hello`, so a reconnect must present a new one. A bare string opens the\n // socket synchronously, so `connect()` still constructs it before it yields.\n if (typeof sessionToken === \"function\") {\n return Promise.resolve(sessionToken()).then(openSocket);\n }\n return openSocket(sessionToken);\n }\n\n function openSocket(token: string): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let opened = false;\n const socket = new WebSocketImpl(url, [encodeBearerSubprotocol(token)]);\n ws = socket;\n\n const timer = setTimeout(() => {\n if (opened) return;\n socket.close(4008, \"connect timeout\");\n reject(new Error(`app transport connect timed out after ${connectTimeoutMs}ms`));\n }, connectTimeoutMs);\n\n socket.addEventListener(\"open\", () => {\n opened = true;\n clearTimeout(timer);\n resolve();\n });\n socket.addEventListener(\"error\", () => {\n if (!opened) {\n clearTimeout(timer);\n reject(new Error(\"app transport connection error\"));\n }\n });\n socket.addEventListener(\"close\", () => {\n clearTimeout(timer);\n if (ws === socket) ws = null;\n for (const h of disconnectHandlers) h();\n });\n socket.addEventListener(\"message\", (ev) => {\n let parsed: AppServerMessage;\n try {\n parsed = JSON.parse(String(ev.data)) as AppServerMessage;\n } catch {\n return;\n }\n for (const h of messageHandlers) h(parsed);\n });\n });\n }\n\n return {\n connect,\n async disconnect() {\n const socket = ws;\n ws = null;\n socket?.close(1000, \"client closed\");\n },\n send(message: AppClientMessage) {\n if (!ws || ws.readyState !== OPEN) {\n throw new Error(\"app transport is not connected\");\n }\n ws.send(JSON.stringify(message));\n },\n onMessage(handler) {\n messageHandlers.add(handler);\n return () => messageHandlers.delete(handler);\n },\n onDisconnect(handler) {\n disconnectHandlers.add(handler);\n return () => disconnectHandlers.delete(handler);\n },\n get connected() {\n return ws?.readyState === OPEN;\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../app-sdk/src/logger.ts","../../app-sdk/src/app.ts","../../app-sdk/src/connection-params.ts","../../app-sdk/src/define-app-capability.ts","../../app-sdk/src/ws-transport.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Minimal logger for the app SDK.\n *\n * The SDK is meant to run inside an embedded app (browser or a lightweight\n * Node service), so it deliberately does not pull in the framework's\n * `LogStore` / SQLite logging stack. Authors can pass their own {@link AppLogger}\n * (e.g. a bridge to their app's telemetry) or accept the console-backed default.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\n/** Structured leveled logger the SDK and capability handlers emit through. */\nexport interface AppLogger {\n debug(message: string, data?: unknown): void;\n info(message: string, data?: unknown): void;\n warn(message: string, data?: unknown): void;\n error(message: string, data?: unknown): void;\n}\n\n/** A logger that discards everything. The default when none is supplied. */\nexport const noopLogger: AppLogger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n};\n\n/**\n * Console-backed logger, prefixing every line with `[skaile-app]`. Opt in by\n * passing `logger: consoleLogger` to {@link createSkaileApp} during development.\n */\nexport const consoleLogger: AppLogger = {\n debug(message, data) {\n console.debug(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n info(message, data) {\n console.info(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n warn(message, data) {\n console.warn(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n error(message, data) {\n console.error(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n};\n","/**\n * `createSkaileApp` — the thin app SDK that makes an embedded app\n * agent-controllable.\n *\n * An app becomes a *capability peer*, mirroring how the runner behaves toward\n * the platform:\n *\n * 1. Opens an authenticated back-connection (via an {@link AppTransport}).\n * 2. Performs the app handshake — sends `app_hello`, awaits `app_hello_ack`\n * (binding the connection to `session + appId`) or `app_hello_nack` (refusal).\n * 3. Registers its `app.*` capabilities via `capability_register`, stamping\n * each with `origin:{kind:'app', appId}` and `side:'app'`. Register /\n * deregister mid-session is supported.\n * 4. Answers inbound `capability_invoked` events by running the local handler\n * and replying with `capability_result` (a thrown handler rides back as\n * `{ error }`).\n *\n * The approval round-trip for `requiresApproval` capabilities is handled\n * entirely platform-side: the gateway parks the invocation for user approval\n * and only forwards `capability_invoked` to the app *after* approval, so the\n * app just marks the capability and answers when asked.\n *\n * The peer reconnects and replays its registrations on a dropped socket, and on\n * a *retryable* refusal — the app boots independently of the session's agent\n * container and can win that race. See {@link ReconnectOptions}.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { Capability } from \"@skaile/workspaces/types\";\nimport { PROTOCOL_VERSION_STRING } from \"@skaile/workspaces/types\";\nimport type { DefinedAppCapability } from \"./define-app-capability.js\";\nimport { type AppLogger, noopLogger } from \"./logger.js\";\nimport type { AppServerMessage, AppTransport } from \"./transport.js\";\n\n/** Default timeout waiting for the gateway's `app_hello_ack`. */\nexport const DEFAULT_HELLO_TIMEOUT_MS = 10_000;\n\n/** Defaults mirroring the agent `WebSocketClientTransport`'s backoff. */\nconst DEFAULT_RECONNECT: Required<ReconnectOptions> = {\n maxAttempts: 5,\n baseMs: 1_000,\n maxMs: 30_000,\n};\n\n/**\n * Reconnect / retry policy. Set `reconnect: false` on {@link SkaileAppOptions}\n * to disable entirely (one attempt, no replay).\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport interface ReconnectOptions {\n /** Attempts before giving up. Default 5. */\n maxAttempts?: number;\n /** First backoff delay in ms; doubles each attempt. Default 1000. */\n baseMs?: number;\n /** Backoff ceiling in ms. Default 30000. */\n maxMs?: number;\n}\n\n/**\n * The gateway refused the handshake. `retryable` mirrors the wire field: `true`\n * for a transient refusal (the agent container is not up yet), `false` for a\n * permanent one (bad token, `appId` not permitted).\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport class AppHandshakeRefusedError extends Error {\n readonly retryable: boolean;\n constructor(reason: string, retryable: boolean) {\n super(`app handshake refused: ${reason}`);\n this.name = \"AppHandshakeRefusedError\";\n this.retryable = retryable;\n }\n}\n\n/**\n * Options for {@link createSkaileApp}.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface SkaileAppOptions {\n /** The embedded app's stable id. Every registered capability is bound to it. */\n appId: string;\n /**\n * The app back-connection. Inject {@link createWebSocketAppTransport} in\n * production or a fake in tests. The transport carries the injected session\n * identity as its connection credential.\n *\n * With `reconnect` enabled the transport must be able to produce a **fresh**\n * token per attempt — the gateway consumes it at `app_hello`. Pass a\n * `SessionTokenProvider` rather than a bare string.\n */\n transport: AppTransport;\n /** Protocol version the SDK announces in `app_hello`. Defaults to the SDK's. */\n protocolVersion?: string;\n /** Timeout waiting for `app_hello_ack`. Defaults to {@link DEFAULT_HELLO_TIMEOUT_MS}. */\n helloTimeoutMs?: number;\n /** Logger for the SDK and (by default) capability handlers. Defaults to {@link noopLogger}. */\n logger?: AppLogger;\n /**\n * Reconnect policy, or `false` to disable. Defaults to\n * `{maxAttempts:5, baseMs:1000, maxMs:30000}`.\n *\n * @since 3.5.0\n */\n reconnect?: ReconnectOptions | false;\n}\n\n/** Result of a successful handshake — the bound session context. */\nexport interface AppSession {\n sessionId: string;\n userId?: string;\n}\n\n/**\n * A connected (or connectable) Skaile app. Register capabilities before or\n * after {@link connect}; post-connect changes are pushed to the gateway\n * immediately.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface SkaileApp {\n /**\n * Add (or replace, by name) an app capability. Registering the same name\n * again replaces it — mirrors the registry's replace-by-name semantics. When\n * already connected, a `capability_register` is sent immediately.\n */\n registerCapability(cap: DefinedAppCapability): void;\n /**\n * Remove a capability by name. No-op when unknown. When connected, a\n * `capability_deregister` is sent immediately so the tool disappears from the\n * agent's catalog.\n */\n deregisterCapability(name: string): void;\n /** Wire-format snapshot of the currently registered capabilities (origin stamped). */\n readonly capabilities: readonly Capability[];\n /** The bound session once connected; `null` before the handshake and while reconnecting. */\n readonly session: AppSession | null;\n /**\n * Open the connection, perform the handshake, and register the current\n * capabilities. Resolves with the bound {@link AppSession}. Rejects if the\n * gateway refuses permanently, the handshake times out, or the transport\n * disconnects mid-handshake. A *retryable* refusal is retried per the\n * reconnect policy before rejecting.\n */\n connect(): Promise<AppSession>;\n /**\n * Subscribe to session binding changes. Fires on **every** bind — including\n * the initial `connect()` — with the new {@link AppSession}, and with `null`\n * whenever the connection drops. Returns an unsubscribe function.\n *\n * @since 3.5.0\n */\n onSessionChange(handler: (session: AppSession | null) => void): () => void;\n /** Disconnect and tear down. Idempotent. Cancels any pending reconnect. */\n close(): Promise<void>;\n}\n\nconst delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/**\n * Create a {@link SkaileApp}. See the module doc for the peer lifecycle.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function createSkaileApp(options: SkaileAppOptions): SkaileApp {\n const {\n appId,\n transport,\n protocolVersion = PROTOCOL_VERSION_STRING,\n helloTimeoutMs = DEFAULT_HELLO_TIMEOUT_MS,\n logger = noopLogger,\n } = options;\n\n const reconnectEnabled = options.reconnect !== false;\n const policy: Required<ReconnectOptions> = {\n ...DEFAULT_RECONNECT,\n ...(options.reconnect === false ? {} : options.reconnect),\n };\n\n const registry = new Map<string, DefinedAppCapability>();\n const sessionListeners = new Set<(s: AppSession | null) => void>();\n let session: AppSession | null = null;\n let closed = false;\n let connecting = false;\n let subscribed = false;\n // Bumped by close(). A reconnect loop parked on its backoff timer captures the\n // epoch it started in and abandons if close() (or a close/connect cycle) has\n // since happened — otherwise it would resurrect a torn-down app.\n let epoch = 0;\n let unsubscribeMessage: (() => void) | null = null;\n let unsubscribeDisconnect: (() => void) | null = null;\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n /** Resolver of the backoff sleep, so close() can wake a parked loop. */\n let reconnectWake: (() => void) | null = null;\n\n /** Non-null only while a handshake is in flight. */\n let pending: { settle: (err: Error | null, s?: AppSession) => void } | null = null;\n\n function toWire(cap: DefinedAppCapability): Capability {\n return {\n name: cap.name,\n displayName: cap.displayName,\n description: cap.description,\n inputSchema: cap.inputSchema,\n outputSchema: cap.outputSchema,\n side: \"app\",\n origin: { kind: \"app\", appId },\n scope: cap.scope,\n requiresApproval: cap.requiresApproval,\n kind: cap.kind,\n audience: cap.audience,\n promptFragment: cap.promptFragment,\n callTimeoutMs: cap.callTimeoutMs,\n render: cap.render,\n };\n }\n\n function setSession(next: AppSession | null): void {\n session = next;\n for (const h of sessionListeners) h(next);\n }\n\n function registerCapability(cap: DefinedAppCapability): void {\n registry.set(cap.name, cap);\n if (session) {\n transport.send({ type: \"capability_register\", capabilities: [toWire(cap)] });\n logger.debug(\"registered capability (live)\", { name: cap.name });\n }\n }\n\n function deregisterCapability(name: string): void {\n if (!registry.delete(name)) return;\n if (session) {\n transport.send({ type: \"capability_deregister\", names: [name] });\n logger.debug(\"deregistered capability (live)\", { name });\n }\n }\n\n /** Dispatch an inbound invocation to the local handler; reply with the result. */\n async function handleInvocation(callId: string, name: string, input: unknown): Promise<void> {\n const cap = registry.get(name);\n if (!cap) {\n transport.send({\n type: \"capability_result\",\n callId,\n result: { error: `Unknown app capability: ${name}` },\n });\n return;\n }\n try {\n const validated = cap.inputZod.parse(input);\n const ctx = { sessionId: session?.sessionId ?? \"\", appId, log: logger };\n const result = await cap.handler(validated, ctx);\n transport.send({ type: \"capability_result\", callId, result });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn(\"capability handler failed\", { name, error: message });\n transport.send({ type: \"capability_result\", callId, result: { error: message } });\n }\n }\n\n function onMessage(message: AppServerMessage): void {\n switch (message.type) {\n case \"app_hello_ack\": {\n const bound: AppSession = { sessionId: message.sessionId, userId: message.userId };\n pending?.settle(null, bound);\n return;\n }\n case \"app_hello_nack\":\n pending?.settle(new AppHandshakeRefusedError(message.error, message.retryable === true));\n return;\n case \"capability_invoked\":\n void handleInvocation(message.callId, message.name, message.input);\n return;\n }\n }\n\n function onDisconnect(): void {\n if (pending) {\n pending.settle(new Error(\"transport disconnected during handshake\"));\n return;\n }\n if (closed || !session) return;\n logger.warn(\"app connection lost\", { appId });\n setSession(null);\n if (!reconnectEnabled) return;\n // The loop holds the same guard connect() uses: a manual connect() while it\n // runs would start a second handshake, and both would overwrite `pending`.\n connecting = true;\n const loopEpoch = epoch;\n void reconnectLoop().finally(() => {\n // A close()/connect() cycle since this loop started owns the guard now.\n if (epoch === loopEpoch) connecting = false;\n });\n }\n\n function ensureSubscribed(): void {\n if (subscribed) return;\n unsubscribeMessage = transport.onMessage(onMessage);\n unsubscribeDisconnect = transport.onDisconnect(onDisconnect);\n subscribed = true;\n }\n\n /** One attempt: open the socket, exchange hello/ack, replay the registry. */\n function performHandshake(): Promise<AppSession> {\n ensureSubscribed();\n return transport.connect().then(\n () =>\n new Promise<AppSession>((resolve, reject) => {\n if (closed) {\n reject(new Error(\"app closed during handshake\"));\n return;\n }\n let settled = false;\n const timer = setTimeout(() => {\n pending?.settle(new Error(`app handshake timed out after ${helloTimeoutMs}ms`));\n }, helloTimeoutMs);\n\n pending = {\n settle: (err, bound) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n pending = null;\n if (err || !bound) {\n reject(err ?? new Error(\"app handshake failed\"));\n return;\n }\n setSession(bound);\n logger.info(\"app connection established\", { appId, sessionId: bound.sessionId });\n // Replay the full surface — on a reconnect the gateway dropped our\n // caps when the previous connection closed.\n const wire = Array.from(registry.values()).map(toWire);\n if (wire.length > 0) {\n transport.send({ type: \"capability_register\", capabilities: wire });\n }\n resolve(bound);\n },\n };\n\n transport.send({ type: \"app_hello\", appId, protocolVersion });\n }),\n (err) => Promise.reject(err instanceof Error ? err : new Error(String(err))),\n );\n }\n\n const backoffMs = (attempt: number) =>\n Math.min(policy.maxMs, policy.baseMs * 2 ** Math.max(0, attempt - 1));\n\n /** Rebind after a dropped socket. Stops on a permanent refusal or close(). */\n async function reconnectLoop(): Promise<void> {\n const myEpoch = epoch;\n for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {\n const wait = backoffMs(attempt);\n logger.debug(\"scheduling app reconnect\", { appId, attempt, waitMs: wait });\n // close() cancels the timer, so it also has to hand us the resolver —\n // otherwise this await never settles and the loop frame parks forever,\n // pinning the transport and every other captured binding.\n await new Promise<void>((r) => {\n reconnectWake = r;\n reconnectTimer = setTimeout(r, wait);\n });\n reconnectWake = null;\n if (closed || epoch !== myEpoch) {\n logger.debug(\"app reconnect cancelled\", { appId, attempt });\n return;\n }\n try {\n await performHandshake();\n logger.info(\"app connection re-established\", { appId });\n return;\n } catch (err) {\n if (err instanceof AppHandshakeRefusedError && !err.retryable) {\n logger.error(\"app reconnect refused permanently\", { appId, error: err.message });\n return;\n }\n logger.warn(\"app reconnect attempt failed\", {\n appId,\n attempt,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n logger.error(\"app reconnect gave up\", { appId, attempts: policy.maxAttempts });\n }\n\n async function connect(): Promise<AppSession> {\n if (session) return session;\n if (connecting) throw new Error(\"app connect() is already in progress\");\n connecting = true;\n // A closed app is reusable: connect() reopens it. Any reconnect loop still\n // parked from the previous life is fenced off by the epoch close() bumped.\n closed = false;\n try {\n for (let attempt = 1; ; attempt++) {\n try {\n return await performHandshake();\n } catch (err) {\n // Only a *retryable* refusal is worth another attempt on the initial\n // connect: the app raced the session's agent container on boot. Every\n // other failure (bad token, timeout, transport error) is terminal here.\n const transient = err instanceof AppHandshakeRefusedError && err.retryable;\n if (!transient || !reconnectEnabled || attempt >= policy.maxAttempts || closed) {\n throw err;\n }\n logger.debug(\"app handshake refused (retryable), retrying\", { appId, attempt });\n await delay(backoffMs(attempt));\n }\n }\n } finally {\n connecting = false;\n }\n }\n\n async function close(): Promise<void> {\n closed = true;\n epoch++;\n clearTimeout(reconnectTimer);\n // Wake a loop parked on its backoff so it observes `closed` and returns,\n // rather than awaiting a timer that will now never fire.\n reconnectWake?.();\n reconnectWake = null;\n connecting = false;\n // Abort an in-flight handshake — rejects the pending connect and clears its\n // timer so nothing leaks past close().\n pending?.settle(new Error(\"app closed during handshake\"));\n unsubscribeMessage?.();\n unsubscribeDisconnect?.();\n unsubscribeMessage = null;\n unsubscribeDisconnect = null;\n subscribed = false;\n session = null;\n await transport.disconnect();\n }\n\n return {\n registerCapability,\n deregisterCapability,\n get capabilities() {\n return Array.from(registry.values()).map(toWire);\n },\n get session() {\n return session;\n },\n connect,\n onSessionChange(handler) {\n sessionListeners.add(handler);\n return () => sessionListeners.delete(handler);\n },\n close,\n };\n}\n","/**\n * Connection-param handshake between the embedding host and the app frame.\n *\n * An embedded app needs three things to open its back-connection: the gateway\n * URL, its own `appId`, and a session identity token. The token is a **secret**,\n * so it must not ride in the iframe `src` — the preview surface that serves the\n * app is unauthenticated, and a URL lands in access logs, `Referer` headers, and\n * browser history. Instead the host posts it over `postMessage`, and the app\n * asks for it with a ready ping once its script is running.\n *\n * The host cannot simply post on iframe `load`: it has no way to know when the\n * app's script is listening. So the app speaks first.\n *\n * ```text\n * app → host { type: 'skaile_app_ready', appId } targetOrigin = parentOrigin\n * host → app { type: 'skaile_app_params', appId, url, token } targetOrigin = appOrigin\n * ```\n *\n * Both sides pin the peer: the app checks `event.origin === parentOrigin` and\n * that the message came from its own parent; the host checks the origin and that\n * `event.source` is the very iframe it rendered. The app learns the expected\n * parent origin from a **non-secret** query param on its `src`\n * ({@link PARENT_ORIGIN_QUERY_PARAM}), which is safe to leak precisely because it\n * is not a credential.\n *\n * @category App SDK\n * @since 3.5.0\n */\n\n/**\n * Query param on the app's iframe `src` carrying the embedder's origin. Not a\n * secret — it is the value the app pins its `postMessage` peer to.\n */\nexport const PARENT_ORIGIN_QUERY_PARAM = \"skaileParentOrigin\";\n\n/** Default timeout waiting for the host's params reply. */\nexport const DEFAULT_PARAMS_TIMEOUT_MS = 10_000;\n\n/** First gap between ready pings. Grows by {@link PING_BACKOFF_FACTOR} up to {@link MAX_PING_INTERVAL_MS}. */\nexport const INITIAL_PING_INTERVAL_MS = 100;\n\n/**\n * Ceiling for the ready-ping gap. The host mints a fresh single-use ticket for\n * *every* ping it answers, so pinging is not free — back off rather than hammer.\n */\nexport const MAX_PING_INTERVAL_MS = 1_000;\n\nconst PING_BACKOFF_FACTOR = 2;\n\n/** App → host. Announces the app frame is listening and wants its params. */\nexport type SkaileAppReadyMessage = {\n type: \"skaile_app_ready\";\n /** The app asking. The host answers only for an app it actually embedded. */\n appId: string;\n};\n\n/** Host → app. Carries the gateway URL and the single-use session identity token. */\nexport type SkaileAppParamsMessage = {\n type: \"skaile_app_params\";\n /** Echoed back so a frame embedding several apps can disambiguate. */\n appId: string;\n /** The gateway app-channel URL, e.g. `wss://platform/app-connect`. */\n url: string;\n /** Single-use session identity token. Consumed by the gateway at `app_hello`. */\n token: string;\n};\n\n/** Resolved params, ready to hand to `createWebSocketAppTransport` + `createSkaileApp`. */\nexport type AppConnectionParams = {\n url: string;\n appId: string;\n token: string;\n};\n\n/** The subset of `postMessage` targets this module needs. */\nexport type PostMessageTarget = {\n postMessage(message: unknown, targetOrigin: string): void;\n};\n\n/** Structural view of a `MessageEvent`, so tests need no DOM. */\nexport type MessageEventLike = {\n readonly origin: string;\n readonly source: unknown;\n readonly data: unknown;\n};\n\n/** Structural view of the `window` bits this module needs. Injectable for tests. */\nexport type AppWindowLike = {\n readonly parent: PostMessageTarget | null;\n readonly location: { readonly search: string };\n addEventListener(type: \"message\", handler: (event: MessageEventLike) => void): void;\n removeEventListener(type: \"message\", handler: (event: MessageEventLike) => void): void;\n};\n\n/**\n * Options for {@link resolveAppConnectionParams}.\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport interface ResolveAppConnectionParamsOptions {\n /** The embedded app's stable id. Must match the params the host replies with. */\n appId: string;\n /**\n * Expected origin of the embedding host. Defaults to the\n * {@link PARENT_ORIGIN_QUERY_PARAM} query param on the current URL. Never `\"*\"`\n * or `\"null\"` — an unpinned peer defeats the point of not putting the token in\n * the URL.\n */\n parentOrigin?: string;\n /** How long to wait for the host's reply. Defaults to {@link DEFAULT_PARAMS_TIMEOUT_MS}. */\n timeoutMs?: number;\n /** Window to listen and post on. Defaults to the global. Injectable for tests. */\n windowRef?: AppWindowLike;\n}\n\nfunction readParentOriginFromQuery(search: string): string | null {\n const value = new URLSearchParams(search).get(PARENT_ORIGIN_QUERY_PARAM);\n return value && value.length > 0 ? value : null;\n}\n\nfunction isParamsMessage(data: unknown, appId: string): data is SkaileAppParamsMessage {\n if (!data || typeof data !== \"object\") return false;\n const m = data as Partial<SkaileAppParamsMessage>;\n // Trim before the emptiness check: a whitespace-only token would survive to\n // the gateway and be refused there. Failing here is the clearer error.\n return (\n m.type === \"skaile_app_params\" &&\n m.appId === appId &&\n typeof m.url === \"string\" &&\n m.url.trim().length > 0 &&\n typeof m.token === \"string\" &&\n m.token.trim().length > 0\n );\n}\n\n/**\n * Ask the embedding host for this app's connection params.\n *\n * Posts a {@link SkaileAppReadyMessage} to the parent and resolves with the\n * matching {@link SkaileAppParamsMessage}. Messages from any other origin, from\n * any window other than the parent, or for another `appId` are ignored — not\n * rejected — because a page may legitimately receive unrelated `postMessage`\n * traffic.\n *\n * Call it again to re-request a fresh token: the token is single-use, so a\n * reconnecting transport needs a new one (see `createSkaileApp`'s `reconnect`).\n *\n * @throws when not embedded, when the parent origin cannot be determined or is\n * unpinnable, or when the host does not reply within `timeoutMs`.\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport function resolveAppConnectionParams(\n options: ResolveAppConnectionParamsOptions,\n): Promise<AppConnectionParams> {\n const { appId, timeoutMs = DEFAULT_PARAMS_TIMEOUT_MS } = options;\n const win =\n options.windowRef ?? (globalThis as unknown as { window?: AppWindowLike }).window ?? null;\n if (!win) {\n return Promise.reject(\n new Error(\"resolveAppConnectionParams requires a window (pass `windowRef` off-browser)\"),\n );\n }\n\n const parent = win.parent;\n // A top-level page's `parent` is itself. An app that is not embedded has no\n // host to ask, and posting to itself would hang until the timeout.\n if (!parent || (parent as unknown) === (win as unknown)) {\n return Promise.reject(\n new Error(\"app is not embedded — no parent frame to request params from\"),\n );\n }\n\n const parentOrigin = options.parentOrigin ?? readParentOriginFromQuery(win.location.search);\n if (!parentOrigin) {\n return Promise.reject(\n new Error(\n `cannot determine the host origin: pass \\`parentOrigin\\` or set the \\`${PARENT_ORIGIN_QUERY_PARAM}\\` query param`,\n ),\n );\n }\n // `*` would broadcast the ready ping to whatever is above us, and `null` (an\n // opaque, sandboxed origin) cannot be pinned at all. Both make the peer check\n // meaningless, and the token would then be posted to an unverified window.\n if (parentOrigin === \"*\" || parentOrigin === \"null\") {\n return Promise.reject(new Error(`refusing to use an unpinnable host origin: ${parentOrigin}`));\n }\n\n // Bind the narrowed peer once: the guards above proved both are non-null, but\n // that narrowing does not survive into the hoisted `ping` declaration below.\n const peer: PostMessageTarget = parent;\n const peerOrigin: string = parentOrigin;\n\n return new Promise<AppConnectionParams>((resolve, reject) => {\n let settled = false;\n let pingTimer: ReturnType<typeof setTimeout> | undefined;\n let pingInterval = INITIAL_PING_INTERVAL_MS;\n\n const cleanup = () => {\n clearTimeout(timer);\n clearTimeout(pingTimer);\n win.removeEventListener(\"message\", onMessage);\n };\n\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(\n new Error(`timed out after ${timeoutMs}ms waiting for connection params from the host`),\n );\n }, timeoutMs);\n\n function onMessage(event: MessageEventLike): void {\n if (settled) return;\n // Pin the peer on both axes. Origin alone is not enough: any frame served\n // from the host's origin could otherwise answer.\n if (event.origin !== peerOrigin || event.source !== peer) return;\n if (!isParamsMessage(event.data, appId)) return;\n settled = true;\n cleanup();\n resolve({ url: event.data.url, appId, token: event.data.token });\n }\n\n win.addEventListener(\"message\", onMessage);\n\n const ready: SkaileAppReadyMessage = { type: \"skaile_app_ready\", appId };\n\n /**\n * Keep asking until the host answers. The host only mints in response to a\n * ping and never speaks unprompted, so a single ping is a race the app loses\n * whenever it boots before the host's own state has settled — the ping is\n * dropped and nothing re-asks. Backs off because each answered ping costs the\n * host a single-use ticket.\n */\n function ping(): void {\n if (settled) return;\n try {\n peer.postMessage(ready, peerOrigin);\n } catch (err) {\n settled = true;\n cleanup();\n reject(err instanceof Error ? err : new Error(String(err)));\n return;\n }\n pingTimer = setTimeout(ping, pingInterval);\n pingInterval = Math.min(pingInterval * PING_BACKOFF_FACTOR, MAX_PING_INTERVAL_MS);\n }\n\n ping();\n });\n}\n","/**\n * `defineAppCapability` — author-friendly factory for an embedded app's\n * agent-controllable capabilities.\n *\n * It mirrors the runner's `defineCapability` (`@skaile/workspaces/runner`) but\n * is scoped to the app side: the handler always runs *in the app*, so `side` is\n * fixed to `'app'` and the author never passes it. The originating `appId` is\n * known to the SDK, not the individual capability, so `origin` is stamped by\n * {@link createSkaileApp} at registration time — not here.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { CapabilityAudience, CapabilityOrigin, RenderSpec } from \"@skaile/workspaces/types\";\nimport * as z from \"zod\";\nimport type { AppLogger } from \"./logger.js\";\n\n/**\n * Per-invocation context passed to an app capability handler. Analogous to the\n * runner's `HandlerContext`, but carries the `appId` and the SDK's\n * {@link AppLogger} (there is no server-side `capability:<origin>:<name>` log\n * slice inside the app process).\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type AppHandlerContext = {\n /** The session this app connection is bound to (from the hello ack). */\n sessionId: string;\n /** The embedded app's id. */\n appId: string;\n /** Logger to emit through; defaults to the SDK's configured logger. */\n log: AppLogger;\n};\n\n/**\n * Author-facing arguments to {@link defineAppCapability}. Mirrors the wire\n * {@link import(\"@skaile/workspaces/types\").Capability} fields an app may set,\n * minus `side` / `origin` (fixed / stamped by the SDK) and the runtime-only\n * fields that only make sense platform-side.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type DefineAppCapabilityArgs<\n T extends z.ZodTypeAny,\n R extends z.ZodTypeAny | undefined = undefined,\n> = {\n /**\n * Stable, session-unique name. Convention: `app.<sector>.<verb>` — e.g.\n * `app.ui.switch_contract` (UI sector) or `app.backend.insert_contract`\n * (backend sector). v1 distinguishes sectors by this naming convention.\n */\n name: string;\n /** Human-readable label for user-facing surfaces; falls back to `name`. */\n displayName?: string;\n /** Human/LLM-readable summary; shown to the agent at tool registration. */\n description: string;\n /** Zod schema validating the invocation arguments. Converted to JSON Schema. */\n input: T;\n /** Optional Zod schema for the return value. */\n output?: R;\n /** Lifetime: `session` survives the whole session, `turn` is dropped at end-of-turn. */\n scope?: \"session\" | \"turn\";\n /**\n * When true, the platform shows a confirm UI and waits for user approval\n * before the invocation is forwarded to the app. Mark every mutation\n * (backend writes, destructive UI actions) with this.\n */\n requiresApproval?: boolean;\n /** Documentation hint: side-effect, render, or pure query. */\n kind?: \"effect\" | \"render\" | \"query\";\n /**\n * Audience scoping. Omit for the default (`['llm','user']`). App capabilities\n * default to LLM-only in the command palette (`isUserInvokable`); set\n * `['llm','user']` on the user-facing actions you want to appear in cmdK.\n */\n audience?: CapabilityAudience[];\n /** Appended to the system prompt's `<CAPABILITIES>` section. */\n promptFragment?: string;\n /** Per-capability override for the platform's result round-trip timeout. */\n callTimeoutMs?: number;\n /** Render contract for capabilities that produce a UI surface. */\n render?: RenderSpec;\n /** The handler — runs in the app. Receives validated input + {@link AppHandlerContext}. */\n handler: (\n args: z.infer<T>,\n ctx: AppHandlerContext,\n ) => Promise<R extends z.ZodTypeAny ? z.infer<R> : unknown>;\n};\n\n/**\n * A defined app capability: the wire-format fields the SDK registers (minus the\n * SDK-stamped `origin`), plus the Zod schemas and type-erased handler used for\n * runtime input validation and inbound dispatch.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type DefinedAppCapability = {\n name: string;\n displayName?: string;\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema?: Record<string, unknown>;\n /** Always `'app'` — the handler runs in the embedded app. */\n side: \"app\";\n scope?: \"session\" | \"turn\";\n requiresApproval?: boolean;\n kind?: \"effect\" | \"render\" | \"query\";\n audience?: CapabilityAudience[];\n promptFragment?: string;\n callTimeoutMs?: number;\n render?: RenderSpec;\n /** Original Zod schema for runtime input validation. Not serialized to the wire. */\n readonly inputZod: z.ZodTypeAny;\n /** Original Zod schema for the optional output, if declared. Not serialized. */\n readonly outputZod?: z.ZodTypeAny;\n /** Type-erased handler dispatched on an inbound `capability_invoked`. */\n handler: (args: unknown, ctx: AppHandlerContext) => Promise<unknown>;\n};\n\n/** Origin kind an app capability always carries once stamped by the SDK. */\nexport type AppCapabilityOrigin = Extract<CapabilityOrigin, { kind: \"app\" }>;\n\n/**\n * Build a {@link DefinedAppCapability} from author-friendly Zod-typed args.\n * `side` is fixed to `'app'`; `origin` is added by the SDK at registration\n * time (it needs the SDK's `appId`).\n *\n * @example\n * ```ts\n * const switchContract = defineAppCapability({\n * name: 'app.ui.switch_contract',\n * description: 'Switch the visible contract in the app UI.',\n * input: z.object({ contractId: z.string() }),\n * handler: async ({ contractId }, ctx) => {\n * ctx.log.info('switching contract', { contractId });\n * ui.select(contractId);\n * },\n * });\n * ```\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function defineAppCapability<\n T extends z.ZodTypeAny,\n R extends z.ZodTypeAny | undefined = undefined,\n>(args: DefineAppCapabilityArgs<T, R>): DefinedAppCapability {\n const inputSchema = z.toJSONSchema(args.input, {\n target: \"draft-07\",\n io: \"input\",\n }) as Record<string, unknown>;\n const outputSchema = args.output\n ? (z.toJSONSchema(args.output, {\n target: \"draft-07\",\n io: \"output\",\n }) as Record<string, unknown>)\n : undefined;\n\n return {\n name: args.name,\n displayName: args.displayName,\n description: args.description,\n inputSchema,\n outputSchema,\n side: \"app\",\n scope: args.scope,\n requiresApproval: args.requiresApproval,\n kind: args.kind,\n audience: args.audience,\n promptFragment: args.promptFragment,\n callTimeoutMs: args.callTimeoutMs,\n render: args.render,\n inputZod: args.input,\n outputZod: args.output,\n handler: args.handler as (args: unknown, ctx: AppHandlerContext) => Promise<unknown>,\n };\n}\n","/**\n * WebSocket-backed {@link AppTransport}.\n *\n * Browser-safe: uses the standard `WebSocket` global (no `ws` dependency, no\n * `node:*` imports), so it runs unchanged in an embedded app's iframe and in a\n * lightweight Node 22+ service. The injected session identity is presented as a\n * bearer **subprotocol** — the same `skaile-bearer.<base64url>` scheme the\n * platform gateway already decodes for runner connections — so the gateway\n * verifies identity at the WS upgrade, before any `app_hello` is processed.\n *\n * Frames are bare JSON of the app-channel messages (no `MessageV2` envelope),\n * matching the runner peer's on-the-wire convention.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { AppClientMessage, AppServerMessage, AppTransport } from \"./transport.js\";\n\n/** Matches `BEARER_SUBPROTOCOL_PREFIX` in `@skaile/workspaces/transport`'s ws/auth. */\nconst BEARER_SUBPROTOCOL_PREFIX = \"skaile-bearer.\";\n\n/** Encode a bearer token as a WS subprotocol token (base64url, no padding). */\nfunction encodeBearerSubprotocol(token: string): string {\n // Encode via UTF-8 bytes so a non-ASCII token doesn't make `btoa` throw\n // (btoa is Latin-1 only). The gateway decodes the mirror of this.\n const binary = String.fromCharCode(...new TextEncoder().encode(token));\n const b64 = btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n return BEARER_SUBPROTOCOL_PREFIX + b64;\n}\n\n/** Minimal structural view of the parts of the `WebSocket` API the transport uses. */\ntype WebSocketLike = {\n readonly readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: \"open\", handler: () => void): void;\n addEventListener(type: \"close\", handler: () => void): void;\n addEventListener(type: \"error\", handler: () => void): void;\n addEventListener(type: \"message\", handler: (ev: { data: unknown }) => void): void;\n};\n\n/** Constructor shape for a `WebSocket` implementation (global or injected). */\nexport type WebSocketFactory = new (url: string, protocols?: string | string[]) => WebSocketLike;\n\n/**\n * Supplies a session identity token for one connection attempt.\n *\n * The gateway's token is **single-use** — it is consumed at `app_hello` — so a\n * reconnecting transport cannot replay the one it opened with. Pass a provider\n * (typically a call to `resolveAppConnectionParams`) and every `connect()` mints\n * a fresh token.\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport type SessionTokenProvider = () => string | Promise<string>;\n\n/**\n * Options for {@link createWebSocketAppTransport}.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface WebSocketAppTransportOptions {\n /** The gateway app-channel URL (e.g. `wss://platform/app-connect`). */\n url: string;\n /**\n * The injected session identity token, or a {@link SessionTokenProvider} that\n * yields a fresh one per connection attempt. Presented as the WS bearer\n * subprotocol; the gateway verifies it and resolves the session/user.\n *\n * A bare string cannot survive a reconnect (the gateway consumes the token at\n * `app_hello`) — pass a provider whenever `reconnect` is enabled on the peer.\n */\n sessionToken: string | SessionTokenProvider;\n /**\n * WebSocket implementation. Defaults to the `WebSocket` global (browser /\n * Node 22+). Injectable for tests or non-standard runtimes.\n */\n WebSocketImpl?: WebSocketFactory;\n /** Connect timeout in ms. Default 10000. */\n connectTimeoutMs?: number;\n}\n\nconst OPEN = 1;\n\n/**\n * Create a WebSocket-backed {@link AppTransport}. The session token is sent as a\n * bearer subprotocol at connect time; app-channel messages are JSON-framed.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function createWebSocketAppTransport(options: WebSocketAppTransportOptions): AppTransport {\n const { url, sessionToken, connectTimeoutMs = 10_000 } = options;\n const resolved =\n options.WebSocketImpl ?? (globalThis as { WebSocket?: WebSocketFactory }).WebSocket;\n if (!resolved) {\n throw new Error(\n \"No WebSocket implementation available. Pass `WebSocketImpl` in a runtime without a global WebSocket.\",\n );\n }\n const WebSocketImpl: WebSocketFactory = resolved;\n\n let ws: WebSocketLike | null = null;\n const messageHandlers = new Set<(m: AppServerMessage) => void>();\n const disconnectHandlers = new Set<() => void>();\n\n function connect(): Promise<void> {\n // A provider is resolved per attempt — the gateway consumes the token at\n // `app_hello`, so a reconnect must present a new one. A bare string opens the\n // socket synchronously, so `connect()` still constructs it before it yields.\n if (typeof sessionToken === \"function\") {\n return Promise.resolve(sessionToken()).then(openSocket);\n }\n return openSocket(sessionToken);\n }\n\n function openSocket(token: string): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let opened = false;\n const socket = new WebSocketImpl(url, [encodeBearerSubprotocol(token)]);\n ws = socket;\n\n const timer = setTimeout(() => {\n if (opened) return;\n socket.close(4008, \"connect timeout\");\n reject(new Error(`app transport connect timed out after ${connectTimeoutMs}ms`));\n }, connectTimeoutMs);\n\n socket.addEventListener(\"open\", () => {\n opened = true;\n clearTimeout(timer);\n resolve();\n });\n socket.addEventListener(\"error\", () => {\n if (!opened) {\n clearTimeout(timer);\n reject(new Error(\"app transport connection error\"));\n }\n });\n socket.addEventListener(\"close\", () => {\n clearTimeout(timer);\n if (ws === socket) ws = null;\n for (const h of disconnectHandlers) h();\n });\n socket.addEventListener(\"message\", (ev) => {\n let parsed: AppServerMessage;\n try {\n parsed = JSON.parse(String(ev.data)) as AppServerMessage;\n } catch {\n return;\n }\n for (const h of messageHandlers) h(parsed);\n });\n });\n }\n\n return {\n connect,\n async disconnect() {\n const socket = ws;\n ws = null;\n socket?.close(1000, \"client closed\");\n },\n send(message: AppClientMessage) {\n if (!ws || ws.readyState !== OPEN) {\n throw new Error(\"app transport is not connected\");\n }\n ws.send(JSON.stringify(message));\n },\n onMessage(handler) {\n messageHandlers.add(handler);\n return () => messageHandlers.delete(handler);\n },\n onDisconnect(handler) {\n disconnectHandlers.add(handler);\n return () => disconnectHandlers.delete(handler);\n },\n get connected() {\n return ws?.readyState === OPEN;\n },\n };\n}\n"]}
@@ -33,6 +33,13 @@
33
33
  export declare const PARENT_ORIGIN_QUERY_PARAM = "skaileParentOrigin";
34
34
  /** Default timeout waiting for the host's params reply. */
35
35
  export declare const DEFAULT_PARAMS_TIMEOUT_MS = 10000;
36
+ /** First gap between ready pings. Grows by {@link PING_BACKOFF_FACTOR} up to {@link MAX_PING_INTERVAL_MS}. */
37
+ export declare const INITIAL_PING_INTERVAL_MS = 100;
38
+ /**
39
+ * Ceiling for the ready-ping gap. The host mints a fresh single-use ticket for
40
+ * *every* ping it answers, so pinging is not free — back off rather than hammer.
41
+ */
42
+ export declare const MAX_PING_INTERVAL_MS = 1000;
36
43
  /** App → host. Announces the app frame is listening and wants its params. */
37
44
  export type SkaileAppReadyMessage = {
38
45
  type: "skaile_app_ready";
@@ -1 +1 @@
1
- {"version":3,"file":"connection-params.d.ts","sourceRoot":"","sources":["../../../app-sdk/src/connection-params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;GAGG;AACH,eAAO,MAAM,yBAAyB,uBAAuB,CAAC;AAE9D,2DAA2D;AAC3D,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,6EAA6E;AAC7E,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,kBAAkB,CAAC;IACzB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qFAAqF;AACrF,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,2FAA2F;AAC3F,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,6DAA6D;AAC7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3D,CAAC;AAEF,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI,CAAC;IACpF,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI,CAAC;CACxF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,iCAAiC;IAChD,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAsBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,mBAAmB,CAAC,CAyE9B"}
1
+ {"version":3,"file":"connection-params.d.ts","sourceRoot":"","sources":["../../../app-sdk/src/connection-params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;GAGG;AACH,eAAO,MAAM,yBAAyB,uBAAuB,CAAC;AAE9D,2DAA2D;AAC3D,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,8GAA8G;AAC9G,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,oBAAoB,OAAQ,CAAC;AAI1C,6EAA6E;AAC7E,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,kBAAkB,CAAC;IACzB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qFAAqF;AACrF,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,2FAA2F;AAC3F,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,6DAA6D;AAC7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3D,CAAC;AAEF,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI,CAAC;IACpF,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI,CAAC;CACxF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,iCAAiC;IAChD,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAsBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,mBAAmB,CAAC,CAiG9B"}
@@ -1,9 +1,9 @@
1
1
  import { assignDomain, buildDomainIndex, DOMAIN_MD_FILENAME, parseDomainMd } from './chunk-MZV6WCF3.js';
2
2
  import './chunk-JXT46QFC.js';
3
- import './chunk-HY33RMEA.js';
3
+ import './chunk-5NZ56R63.js';
4
4
  import './chunk-ZAP2BDHG.js';
5
- import { factoryAssetRoots } from './chunk-ZBZQC5R7.js';
6
- import { scanRepo } from './chunk-UQKYT4V6.js';
5
+ import { factoryAssetRoots } from './chunk-7J7LQP6T.js';
6
+ import { scanRepo } from './chunk-ISA7R7PK.js';
7
7
  import './chunk-P4VHQA4N.js';
8
8
  import { walkDir, parseFrontmatter } from './chunk-BQCGFPHW.js';
9
9
  import './chunk-GEEW6SY2.js';
@@ -64,7 +64,7 @@ async function libraryAssets() {
64
64
  const domains = [];
65
65
  try {
66
66
  const [{ openLibraryManager }, { skaileHomeDir }, { getGlobalCacheDir }] = await Promise.all([
67
- import('./open-library-4HMIDK3N.js'),
67
+ import('./open-library-ST2VFQTK.js'),
68
68
  import('./library/index.js'),
69
69
  import('./core/index.js')
70
70
  ]);
@@ -78,9 +78,9 @@ async function libraryAssets() {
78
78
  const entries = [];
79
79
  for (const lib of libs) {
80
80
  if (!existsSync(lib.path)) continue;
81
- const libEntries = [...scanRepo(lib.path, `library:${lib.name}`)];
82
- domains.push(...stampDomains(lib.path, `library:${lib.name}`, libEntries));
83
- entries.push(...libEntries);
81
+ const libEntries = [...scanRepo(lib.path, lib.name)];
82
+ domains.push(...stampDomains(lib.path, lib.name, libEntries, false));
83
+ for (const entry of libEntries) entries.push({ origin: "library", entry, group: lib.name });
84
84
  }
85
85
  return { entries, domains, notes };
86
86
  } finally {
@@ -97,7 +97,7 @@ async function libraryAssets() {
97
97
  async function storeAssets(projectDir) {
98
98
  const notes = [];
99
99
  try {
100
- const { resolveCatalogSource } = await import('./open-library-4HMIDK3N.js');
100
+ const { resolveCatalogSource } = await import('./open-library-ST2VFQTK.js');
101
101
  const { source, close } = await resolveCatalogSource({ projectDir });
102
102
  try {
103
103
  const assets = await source.listAssets();
@@ -179,7 +179,7 @@ async function gatherAssetFeeds(am, projectDir) {
179
179
  return {
180
180
  entries: [
181
181
  ...src.entries,
182
- ...tag("library", libs.entries),
182
+ ...libs.entries,
183
183
  ...tag("factory", factory.entries),
184
184
  ...tag("store", store.entries)
185
185
  ],
@@ -201,5 +201,5 @@ function dedupeBySlug(domains) {
201
201
  }
202
202
 
203
203
  export { gatherAssetFeeds };
204
- //# sourceMappingURL=asset-feeds-T35ELX2O.js.map
205
- //# sourceMappingURL=asset-feeds-T35ELX2O.js.map
204
+ //# sourceMappingURL=asset-feeds-Y66NCOQZ.js.map
205
+ //# sourceMappingURL=asset-feeds-Y66NCOQZ.js.map