@skaile/workspaces 1.4.0 → 1.5.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 (196) hide show
  1. package/CHANGELOG.md +273 -0
  2. package/dist/app-sdk/index.js +241 -55
  3. package/dist/app-sdk/index.js.map +1 -1
  4. package/dist/app-sdk/src/app.d.ts +55 -4
  5. package/dist/app-sdk/src/app.d.ts.map +1 -1
  6. package/dist/app-sdk/src/connection-params.d.ts +117 -0
  7. package/dist/app-sdk/src/connection-params.d.ts.map +1 -0
  8. package/dist/app-sdk/src/index.d.ts +3 -2
  9. package/dist/app-sdk/src/index.d.ts.map +1 -1
  10. package/dist/app-sdk/src/transport.d.ts +9 -0
  11. package/dist/app-sdk/src/transport.d.ts.map +1 -1
  12. package/dist/app-sdk/src/ws-transport.d.ts +18 -2
  13. package/dist/app-sdk/src/ws-transport.d.ts.map +1 -1
  14. package/dist/{asset-feeds-NG5TKB3V.js → asset-feeds-T35ELX2O.js} +9 -9
  15. package/dist/{asset-feeds-NG5TKB3V.js.map → asset-feeds-T35ELX2O.js.map} +1 -1
  16. package/dist/asset-manager/index.js +11 -6
  17. package/dist/asset-manager/installer.js +5 -5
  18. package/dist/asset-manager/src/index.d.ts.map +1 -1
  19. package/dist/{chunk-BE5KTS2L.js → chunk-2DWXGUZA.js} +2 -2
  20. package/dist/{chunk-BE5KTS2L.js.map → chunk-2DWXGUZA.js.map} +1 -1
  21. package/dist/{chunk-XBG2YDQQ.js → chunk-2ZMO4CFG.js} +3 -3
  22. package/dist/{chunk-XBG2YDQQ.js.map → chunk-2ZMO4CFG.js.map} +1 -1
  23. package/dist/{chunk-X57AMTAH.js → chunk-3CHARYD4.js} +16 -7
  24. package/dist/chunk-3CHARYD4.js.map +1 -0
  25. package/dist/{chunk-LCTRCWBK.js → chunk-6NR2BEIY.js} +4 -4
  26. package/dist/{chunk-LCTRCWBK.js.map → chunk-6NR2BEIY.js.map} +1 -1
  27. package/dist/{chunk-AAGD2J4H.js → chunk-BGIC25VK.js} +11 -9
  28. package/dist/chunk-BGIC25VK.js.map +1 -0
  29. package/dist/{chunk-JZPEEMIM.js → chunk-CNSNN7N6.js} +5 -5
  30. package/dist/{chunk-JZPEEMIM.js.map → chunk-CNSNN7N6.js.map} +1 -1
  31. package/dist/{chunk-GWDZW44L.js → chunk-DMG6NJH7.js} +4 -4
  32. package/dist/{chunk-GWDZW44L.js.map → chunk-DMG6NJH7.js.map} +1 -1
  33. package/dist/{chunk-HWTQ6HPO.js → chunk-FDE7XRQA.js} +56 -8
  34. package/dist/chunk-FDE7XRQA.js.map +1 -0
  35. package/dist/{chunk-26YCXWCU.js → chunk-HY33RMEA.js} +3 -3
  36. package/dist/{chunk-26YCXWCU.js.map → chunk-HY33RMEA.js.map} +1 -1
  37. package/dist/chunk-J5TI55E7.js +203 -0
  38. package/dist/chunk-J5TI55E7.js.map +1 -0
  39. package/dist/chunk-JIDKX7GC.js +122 -0
  40. package/dist/chunk-JIDKX7GC.js.map +1 -0
  41. package/dist/{chunk-BDUANIAR.js → chunk-KEQ4GLVM.js} +3 -3
  42. package/dist/{chunk-BDUANIAR.js.map → chunk-KEQ4GLVM.js.map} +1 -1
  43. package/dist/{chunk-DNFIKRO6.js → chunk-KMY7HVQ6.js} +3 -3
  44. package/dist/{chunk-DNFIKRO6.js.map → chunk-KMY7HVQ6.js.map} +1 -1
  45. package/dist/{chunk-B6SSXVF2.js → chunk-MZV6WCF3.js} +81 -49
  46. package/dist/chunk-MZV6WCF3.js.map +1 -0
  47. package/dist/{chunk-DZCK7GRW.js → chunk-P4VHQA4N.js} +22 -6
  48. package/dist/chunk-P4VHQA4N.js.map +1 -0
  49. package/dist/{chunk-OSCJRUKM.js → chunk-RDYOU6N6.js} +2 -2
  50. package/dist/{chunk-OSCJRUKM.js.map → chunk-RDYOU6N6.js.map} +1 -1
  51. package/dist/{chunk-77OXU44H.js → chunk-RSYV7XEN.js} +13 -7
  52. package/dist/chunk-RSYV7XEN.js.map +1 -0
  53. package/dist/{chunk-OUWNTFDG.js → chunk-UQKYT4V6.js} +38 -6
  54. package/dist/chunk-UQKYT4V6.js.map +1 -0
  55. package/dist/chunk-WYGXYGJH.js +688 -0
  56. package/dist/chunk-WYGXYGJH.js.map +1 -0
  57. package/dist/{chunk-TJRWGMD2.js → chunk-ZBZQC5R7.js} +3 -3
  58. package/dist/{chunk-TJRWGMD2.js.map → chunk-ZBZQC5R7.js.map} +1 -1
  59. package/dist/cli/index.js +259 -192
  60. package/dist/cli/index.js.map +1 -1
  61. package/dist/cli/src/commands/catalog.d.ts +18 -0
  62. package/dist/cli/src/commands/catalog.d.ts.map +1 -1
  63. package/dist/cli/src/commands/library-cmd.d.ts +2 -2
  64. package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
  65. package/dist/cli/src/commands/library-publish.d.ts +53 -0
  66. package/dist/cli/src/commands/library-publish.d.ts.map +1 -0
  67. package/dist/cli/src/commands/preset-cmd.d.ts +6 -0
  68. package/dist/cli/src/commands/preset-cmd.d.ts.map +1 -1
  69. package/dist/cli/src/commands/preset-export.d.ts +10 -1
  70. package/dist/cli/src/commands/preset-export.d.ts.map +1 -1
  71. package/dist/cli/src/commands/project.d.ts.map +1 -1
  72. package/dist/cli/src/commands/publish-core.d.ts +75 -0
  73. package/dist/cli/src/commands/publish-core.d.ts.map +1 -0
  74. package/dist/cli/src/commands/run.d.ts.map +1 -1
  75. package/dist/cli/src/commands/source-manifest-publish.d.ts +82 -0
  76. package/dist/cli/src/commands/source-manifest-publish.d.ts.map +1 -0
  77. package/dist/cli/src/commands/source-manifest.d.ts +65 -6
  78. package/dist/cli/src/commands/source-manifest.d.ts.map +1 -1
  79. package/dist/cli/src/commands/source.d.ts +13 -0
  80. package/dist/cli/src/commands/source.d.ts.map +1 -1
  81. package/dist/cli/src/commands/store.d.ts.map +1 -1
  82. package/dist/cli/src/driver-error.d.ts +12 -0
  83. package/dist/cli/src/driver-error.d.ts.map +1 -0
  84. package/dist/cli/src/open-library.d.ts +4 -0
  85. package/dist/cli/src/open-library.d.ts.map +1 -1
  86. package/dist/cli/src/store-auth.d.ts +42 -0
  87. package/dist/cli/src/store-auth.d.ts.map +1 -0
  88. package/dist/cli/src/store-client.d.ts +20 -22
  89. package/dist/cli/src/store-client.d.ts.map +1 -1
  90. package/dist/cli/src/store-publisher.d.ts +91 -0
  91. package/dist/cli/src/store-publisher.d.ts.map +1 -0
  92. package/dist/connectors/config.js +5 -5
  93. package/dist/connectors/index.js +6 -6
  94. package/dist/core/index.js +4 -4
  95. package/dist/core/publish-manifest.js +1 -1
  96. package/dist/core/runtime-assets.js +3 -3
  97. package/dist/core/src/cache-paths.d.ts +17 -0
  98. package/dist/core/src/cache-paths.d.ts.map +1 -1
  99. package/dist/core/src/index.d.ts +2 -2
  100. package/dist/core/src/index.d.ts.map +1 -1
  101. package/dist/core/src/publish-manifest.d.ts.map +1 -1
  102. package/dist/core/src/repo-manager.d.ts +13 -2
  103. package/dist/core/src/repo-manager.d.ts.map +1 -1
  104. package/dist/core/src/walker.d.ts +10 -0
  105. package/dist/core/src/walker.d.ts.map +1 -1
  106. package/dist/core/workspace-config.js +2 -2
  107. package/dist/deploy/index.js +4 -4
  108. package/dist/discovery/index.js +2 -2
  109. package/dist/discovery/src/discover-manifest.d.ts.map +1 -1
  110. package/dist/discovery/src/discover.d.ts +17 -4
  111. package/dist/discovery/src/discover.d.ts.map +1 -1
  112. package/dist/discovery/src/index.d.ts +1 -1
  113. package/dist/discovery/src/index.d.ts.map +1 -1
  114. package/dist/discovery/src/reference-lint.d.ts +7 -0
  115. package/dist/discovery/src/reference-lint.d.ts.map +1 -1
  116. package/dist/discovery/src/source-config.d.ts +28 -0
  117. package/dist/discovery/src/source-config.d.ts.map +1 -1
  118. package/dist/discovery/src/tree-entries.d.ts.map +1 -1
  119. package/dist/{ensure-sources-CHC7X7AJ.js → ensure-sources-QDVFUIUP.js} +14 -9
  120. package/dist/ensure-sources-QDVFUIUP.js.map +1 -0
  121. package/dist/factory-assets/connectors/box.js +6 -6
  122. package/dist/factory-assets/connectors/deploy.js +6 -6
  123. package/dist/factory-assets/connectors/devserver.js +6 -6
  124. package/dist/factory-assets/connectors/flow/adapter.js +6 -6
  125. package/dist/factory-assets/connectors/flow/engine.js +4 -4
  126. package/dist/factory-assets/connectors/flow/run-flow.js +7 -7
  127. package/dist/factory-assets/connectors/flow.js +6 -6
  128. package/dist/factory-assets/connectors/git.js +6 -6
  129. package/dist/factory-assets/connectors/gmail.js +6 -6
  130. package/dist/factory-assets/connectors/googledrive.js +6 -6
  131. package/dist/factory-assets/connectors/local.js +6 -6
  132. package/dist/factory-assets/connectors/mattermost.js +6 -6
  133. package/dist/factory-assets/connectors/memory.js +6 -6
  134. package/dist/factory-assets/connectors/minio.js +6 -6
  135. package/dist/factory-assets/connectors/postgres.js +6 -6
  136. package/dist/factory-assets/connectors/s3.js +6 -6
  137. package/dist/factory-assets/connectors/sharepoint.js +6 -6
  138. package/dist/factory-assets/connectors/sqlite.js +6 -6
  139. package/dist/factory-assets/connectors/static-server.js +6 -6
  140. package/dist/factory-assets/connectors/tunnel.js +6 -6
  141. package/dist/factory-assets/connectors/webdav.js +6 -6
  142. package/dist/factory-assets/connectors/xstate-store.js +6 -6
  143. package/dist/factory-assets/connectors/xstate.js +6 -6
  144. package/dist/{flows-CGOCUNO6.js → flows-MRVXTYWC.js} +3 -3
  145. package/dist/{flows-CGOCUNO6.js.map → flows-MRVXTYWC.js.map} +1 -1
  146. package/dist/{helpers-GWG4CYYC.js → helpers-TF7GCBRQ.js} +14 -9
  147. package/dist/{helpers-GWG4CYYC.js.map → helpers-TF7GCBRQ.js.map} +1 -1
  148. package/dist/library/index.js +6 -6
  149. package/dist/library/install.js +4 -4
  150. package/dist/library/src/index.d.ts +2 -2
  151. package/dist/library/src/index.d.ts.map +1 -1
  152. package/dist/library/src/local/local-catalog-source.d.ts +12 -1
  153. package/dist/library/src/local/local-catalog-source.d.ts.map +1 -1
  154. package/dist/library/src/sync/manifest-writeback.d.ts +36 -0
  155. package/dist/library/src/sync/manifest-writeback.d.ts.map +1 -1
  156. package/dist/library-publish-K5IBE44D.js +249 -0
  157. package/dist/library-publish-K5IBE44D.js.map +1 -0
  158. package/dist/{open-library-37PXN6MK.js → open-library-4HMIDK3N.js} +13 -13
  159. package/dist/{open-library-37PXN6MK.js.map → open-library-4HMIDK3N.js.map} +1 -1
  160. package/dist/{paths-UVOHWUPY.js → paths-P3DWFUPS.js} +7 -7
  161. package/dist/{paths-UVOHWUPY.js.map → paths-P3DWFUPS.js.map} +1 -1
  162. package/dist/{plugin-store-EIMVEH3O.js → plugin-store-S7OMGI7N.js} +6 -6
  163. package/dist/{plugin-store-EIMVEH3O.js.map → plugin-store-S7OMGI7N.js.map} +1 -1
  164. package/dist/runner/index.js +8 -8
  165. package/dist/runner/src/agent.d.ts.map +1 -1
  166. package/dist/sdk/asset-manager.js +11 -6
  167. package/dist/sdk/core.js +4 -4
  168. package/dist/sdk/flow.js +4 -4
  169. package/dist/sdk/index.js +8 -8
  170. package/dist/sdk/runner.js +8 -8
  171. package/dist/{setup-DINVR6ZQ.js → setup-2CCS63L6.js} +6 -6
  172. package/dist/{setup-DINVR6ZQ.js.map → setup-2CCS63L6.js.map} +1 -1
  173. package/dist/source-LOAM7IIQ.js +30 -0
  174. package/dist/{source-RXAHIGMD.js.map → source-LOAM7IIQ.js.map} +1 -1
  175. package/dist/source-manifest-publish-TGVZ6KMC.js +228 -0
  176. package/dist/source-manifest-publish-TGVZ6KMC.js.map +1 -0
  177. package/dist/tui/index.js +8 -8
  178. package/dist/workspace-plugin/adapters/mcp.js +1 -1
  179. package/dist/workspace-plugin/adapters/omp.js +3 -3
  180. package/dist/workspace-plugin/index.js +1 -1
  181. package/package.json +1 -1
  182. package/dist/chunk-77OXU44H.js.map +0 -1
  183. package/dist/chunk-AAGD2J4H.js.map +0 -1
  184. package/dist/chunk-B6SSXVF2.js.map +0 -1
  185. package/dist/chunk-BJHZHNKR.js +0 -93
  186. package/dist/chunk-BJHZHNKR.js.map +0 -1
  187. package/dist/chunk-DZCK7GRW.js.map +0 -1
  188. package/dist/chunk-HWTQ6HPO.js.map +0 -1
  189. package/dist/chunk-OUWNTFDG.js.map +0 -1
  190. package/dist/chunk-QM2TT2PJ.js +0 -385
  191. package/dist/chunk-QM2TT2PJ.js.map +0 -1
  192. package/dist/chunk-X57AMTAH.js.map +0 -1
  193. package/dist/ensure-sources-CHC7X7AJ.js.map +0 -1
  194. package/dist/source-RXAHIGMD.js +0 -30
  195. package/dist/store-client-T3F663YG.js +0 -15
  196. package/dist/store-client-T3F663YG.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,278 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#340](https://github.com/skaile-ai/workspaces/pull/340) [`cde9457`](https://github.com/skaile-ai/workspaces/commit/cde9457c22d7e58111156effec1d4f5a8b9c50ec) Thanks [@henkbla](https://github.com/henkbla)! - `app-sdk`: connection-param handshake, reconnect, and registration replay.
8
+
9
+ The embedded app needs three things to open its back-connection — the gateway
10
+ URL, its `appId`, and a session identity token. The token is a credential, and
11
+ the preview surface that serves the app is unauthenticated, so it must not ride
12
+ in the iframe `src`. `resolveAppConnectionParams()` obtains it over
13
+ `postMessage` instead: the app posts a `skaile_app_ready` ping to its embedder
14
+ and awaits a `skaile_app_params` reply, with both sides pinning the peer on
15
+ `event.origin` **and** `event.source`. The app learns the expected host origin
16
+ from a non-secret `skaileParentOrigin` query param — safe to leak precisely
17
+ because it is not a credential. `"*"` and `"null"` host origins are refused: an
18
+ unpinnable peer defeats the point of keeping the token out of the URL.
19
+
20
+ `createWebSocketAppTransport` now accepts a `SessionTokenProvider` for
21
+ `sessionToken` as well as a bare string. The gateway's token is **single-use**
22
+ (consumed at `app_hello`), so a reconnecting transport cannot replay the one it
23
+ opened with; a provider mints a fresh token per attempt. A bare string still
24
+ opens the socket synchronously.
25
+
26
+ `createSkaileApp` gains a `reconnect` policy (default
27
+ `{maxAttempts:5, baseMs:1000, maxMs:30000}`, or `false` to disable). It rebinds
28
+ after a dropped socket and **replays its capability registry**, since the
29
+ gateway deregisters an app's capabilities when its connection closes. It also
30
+ retries a _retryable_ `app_hello_nack` — the app boots independently of the
31
+ session's agent container and can win that race. A permanent refusal (bad token,
32
+ `appId` not permitted) is never retried, because retrying only burns tokens.
33
+ `AppHelloNackMessage` gains an optional `retryable` field to carry that
34
+ distinction, and the SDK surfaces it as `AppHandshakeRefusedError.retryable`.
35
+ New `onSessionChange(handler)` fires on every bind — including the initial
36
+ `connect()` — and with `null` whenever the connection drops.
37
+
38
+ Behaviour change for `1.4.0` consumers: reconnect is **on by default**, so a
39
+ dropped socket now re-dials instead of leaving the app inert. Pass
40
+ `reconnect: false` for the old single-shot behaviour. Purely additive otherwise
41
+ — new exports, a widened `sessionToken` type, and one new optional wire field.
42
+
43
+ - [#344](https://github.com/skaile-ai/workspaces/pull/344) [`fdd6c6a`](https://github.com/skaile-ai/workspaces/commit/fdd6c6a2bf89b677274665edd31ba9c6f71e5408) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Wire `skaile library publish` / `register` / `yank` — first-party store publish
44
+
45
+ Publish a git-backed library you own (in-repo `skaile.manifest.yaml`) to the
46
+ store: `library publish <lib>` commits + pushes the library repo, registers +
47
+ syncs it as a store source (no sidecar — the in-repo manifest drives discovery),
48
+ and publishes its assets under the manifest's own `publisher` (no namespace
49
+ remap). `--asset <ref>` narrows to one asset; `--create-repo` provisions + links
50
+ a public `github.com/<you>/<lib>` via `gh` when the library has no remote;
51
+ `--dry-run` prints an offline plan. `register` skips the commit/push (already on
52
+ GitHub); `yank` unpublishes. Requires a public git remote + `store login`.
53
+
54
+ Extracts the shared store-interaction core (`publish-core.ts` `publishToStore`)
55
+ reused by both `library publish` and `source manifest publish`, so the two
56
+ front-ends can't drift on the register → sync → candidates → per-asset publish
57
+ sequence.
58
+
59
+ - [#335](https://github.com/skaile-ai/workspaces/pull/335) [`84565e0`](https://github.com/skaile-ai/workspaces/commit/84565e09c4da6daab4f26d9402cefd42884588ce) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Sidecar authoring + consume bug fixes for third-party sources.
60
+
61
+ **Sidecar authoring (`skaile source manifest init` / `edit` + overlay read + add).** You can
62
+ now curate assets from a foreign GitHub repo you don't control by authoring a
63
+ `skaile.manifest.yaml` stored _outside_ that repo, then consume + `add` them under a clean
64
+ curated publisher.
65
+
66
+ - `skaile source manifest init <src> [--publisher <name>] [--dev] [--force]` — clone (if
67
+ needed), run discovery, and write a curated manifest to the local store catalog at
68
+ `~/.skaile/store/manifests/<slug>.yaml`. Publisher defaults to the source's GitHub org.
69
+ Every asset inherits the top-level publisher (no per-asset path-segment stamp).
70
+ Idempotent (won't overwrite without `--force`).
71
+ - `skaile source manifest edit <src>` — open the curated manifest in `$EDITOR` and
72
+ re-validate on save (warnings surfaced, never a hard fail).
73
+ - `skaile source add` / `source sync` apply the curated manifest as a **read overlay**:
74
+ the curated publisher + asset inventory win over raw glob discovery. `LocalCatalogSource`
75
+ gained a `sidecarManifestFile` option; discovery gained `loadMergedSourceConfigFromFile`
76
+ - a `sidecarManifestFile` discovery option; the library gained `writeCuratedManifest`.
77
+ - `skaile add` now resolves the curated publisher too: the provenance walker
78
+ (`buildProvenanceIndex`) reads the same curated store overlay (via a new
79
+ `SourceClone.overlayManifestPath`), so the ref `search --local` displays
80
+ (`skill:@lark/…`) is the ref `add` accepts and deploys.
81
+ - Store manifests are keyed by a collision-free `storeManifestSlug(url)`
82
+ (`<host>__<owner>__<repo>`), so two repos sharing a trailing name (`orgA/cli`,
83
+ `orgB/cli`) no longer collide on `cli.yaml`.
84
+
85
+ **Bug fix — publisher normalization (glob AND manifest paths).** Discovery stored
86
+ publishers _with_ a leading `@`, which the local ref formatter doubled into `@@scripts`
87
+ (glob) / made `@lark` assets un-addable (manifest/curated overlay). `DiscoveredAsset.publisher`
88
+ is now **bare** on every discovery path (glob + manifest, local-FS + virtual-tree), unifying
89
+ them; the single `@` scope sigil is re-added by the ref formatter, and refs carry exactly
90
+ one `@` across both modes. The internal `SourceConfig` layer (`publisher_default` /
91
+ `atPublisher`) is unchanged — the `@`-prefix is correct there.
92
+
93
+ **Bug fix — `search --local` reads the index (and unions the factory source).** It now
94
+ reads the index (`asset_definitions` via `listAssetDefs`) instead of re-scanning the repo
95
+ tree, so its result set and count match exactly what `source add`/`sync` wrote — no scanner
96
+ `kind:name` dedup collapsing same-named or multi-kind assets — and unions the bundled
97
+ first-party factory source (never indexed) so built-in skills still appear in a fresh
98
+ project.
99
+
100
+ - [#339](https://github.com/skaile-ai/workspaces/pull/339) [`56776b2`](https://github.com/skaile-ai/workspaces/commit/56776b2c709d08d2aff0f46a19cd876db52db5b0) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Wire `skaile source manifest publish` and `yank` — the curator publish flow
101
+
102
+ `source manifest publish <src>` takes a locally-curated sidecar manifest (over a
103
+ foreign **public** GitHub repo), pushes it to a public "sidecar" repo you own,
104
+ then registers + syncs the source with the store and publishes each curated asset
105
+ under your namespace (default: your store username; `--as <ns>` to override, e.g.
106
+ a GitHub org you belong to). `--dry-run` prints an offline plan (no push, no store
107
+ calls). `yank` unpublishes the source's published assets; `propose` stays a stub.
108
+
109
+ Requires the store's self-service `sources.add` sidecar support (skaile-store#79)
110
+ and a deployed store backend. Mono-repo sidecars (`sidecar_path`) are a documented
111
+ follow-up.
112
+
113
+ - [#338](https://github.com/skaile-ai/workspaces/pull/338) [`9e959ee`](https://github.com/skaile-ai/workspaces/commit/9e959eec7d3c935aaa3c6c0c001f366b2bdbd80c) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Sidecar authoring phase 3 — wire `skaile source manifest rebuild` / `verify`,
114
+ extend the init test-fixture filter, and fix key-aware legacy clone migration.
115
+
116
+ - **`source manifest rebuild <src>` (new verb).** Refreshes a curated manifest
117
+ after the upstream repo changed. **Merge is the default — curation is sacred:**
118
+ it updates the clone (`git pull --ff-only`, best-effort), re-runs discovery,
119
+ and matches existing↔discovered entries by repo-relative `root` path. Existing
120
+ entries whose root is still present are KEPT verbatim (all hand-edits
121
+ preserved: publisher, per-asset name/version/description); entries whose root
122
+ is gone upstream are DROPPED and reported (`removed (gone upstream): …`).
123
+ Freshly-discovered assets not already curated are NOT auto-added — they are
124
+ reported (`N new upstream asset(s) available (not added): … — add with
125
+ \`skaile source manifest edit\``), so a curated foreign-repo sidecar never
126
+ silently absorbs new upstream skills, and an asset the curator deliberately
127
+ dropped stays dropped. Writes only when something changed (else `already up to
128
+ date`). `--overwrite`re-scans the full inventory with`init --force`
129
+ semantics, warning that curation is replaced.
130
+ - **`source manifest verify <src>` (new verb), read-only.** Checks each manifest
131
+ asset against the clone: `root`/`files` paths still exist upstream (missing →
132
+ error); a declared `sha256` on a **`files:` entry** recomputes to the same
133
+ deterministic composite hash (mismatch → error) — hashed exactly as discovery
134
+ does (repo-root anchored, files verbatim as repo-relative strings), so a
135
+ correctly-pinned hash verifies clean; a declared `sha256` on a `root:` entry is
136
+ **skipped with a warning** (discovery hashes only the per-kind filtered set, so
137
+ a whole-dir recompute would false-mismatch, and the writer never emits sha256
138
+ for `root:` entries); schema decodes cleanly (`decodeSkaileManifest`
139
+ diagnostics → warnings/errors); dependency refs parse (`parseAssetRef`;
140
+ malformed → warning). Prints a summary and exits 0 when there are no errors;
141
+ `--strict` forces a nonzero exit if there are any warnings. Writes nothing.
142
+ - **Extended init/rebuild test-fixture filter.** In addition to
143
+ `tests`/`test`/`__tests__`, the filter now also drops assets whose
144
+ repo-relative root contains a `testdata`, `fixtures`, or `__fixtures__` path
145
+ SEGMENT (exact segment), catching leaks like
146
+ `internal/qualitygate/skillscan/testdata/skills/lark-demo`. `init`/`rebuild`
147
+ path only; the general discovery walk (`source add`) is untouched.
148
+ - **Key-aware legacy clone migration.** `getGlobalCacheDir()` migration of a
149
+ legacy `~/.skaile/sources/<bare-name>` clone now re-derives the clone's true
150
+ cache key from its `origin` remote and lands it at
151
+ `<cacheRoot>/sources/<host>/<owner>/<repo>` (was: preserved the bare name, so a
152
+ URL-keyed lookup missed it and it lingered mis-placed). Non-clone leftovers
153
+ (the sidecar remainder) are untouched. New `mergeLegacyEntriesKeyed` primitive
154
+ in `@skaile/workspaces/core`.
155
+
156
+ `publish` / `propose` / `yank` remain stubs (they need the store publish
157
+ endpoint).
158
+
159
+ - [#343](https://github.com/skaile-ai/workspaces/pull/343) [`a8c2036`](https://github.com/skaile-ai/workspaces/commit/a8c2036e64c069ee46ebab3ff48e37044856b326) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Fix `skaile store login` — Keycloak device flow + correct store URL
160
+
161
+ The CLI's store auth was broken against the deployed store: the default URL
162
+ (`store.skaile.ai`) is dead and the old `/api/auth/*` login endpoints return 404.
163
+ `store login` now runs the Keycloak Device Authorization Grant (RFC 8628) against
164
+ `SKAILE_KEYCLOAK_URL` (default `https://auth.skaile.ai`, realm `skaile-store`,
165
+ public client `skaile-store-cli`) — it prints a URL + code to approve in a
166
+ browser, caches the token at `~/.skaile/auth.json` (shared with `store/cli`), and
167
+ refreshes transparently. The default store API URL is now `https://api.skaile.store`;
168
+ `whoami` reads `/api/v1/me`; every `/api/v1/*` publisher call authorizes with a
169
+ fresh device-flow token. Overridable via `SKAILE_STORE_URL` / `SKAILE_KEYCLOAK_URL`
170
+ / `SKAILE_STORE_REALM` / `SKAILE_STORE_CLI_CLIENT`.
171
+
172
+ Note: live login requires a public `skaile-store-cli` client (device-grant
173
+ enabled) registered on the store's Keycloak realm.
174
+
175
+ ### Patch Changes
176
+
177
+ - [#332](https://github.com/skaile-ai/workspaces/pull/332) [`bd6e8f7`](https://github.com/skaile-ai/workspaces/commit/bd6e8f710e42017dc07afa4ab00f4134a28a038e) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Four CLI UX papercuts from hands-on testing:
178
+
179
+ - `skaile run "<prompt>"` no longer crashes with a raw `spawn omp ENOENT` when the
180
+ configured driver's binary is missing. The single-shot chat path now catches the
181
+ failure and prints the bridge's actionable hint (the binary is not installed /
182
+ not on PATH) plus a suggestion to try `--driver claude-sdk`; unrelated errors
183
+ still surface their own message and a non-zero exit.
184
+ - `skaile source add` warns about a missing `sha256` by pointing at the real
185
+ `skaile source manifest rebuild` command instead of the non-existent
186
+ `skaile source build-manifest --write`.
187
+ - `skaile source remove` (without `--purge`) now tells the user how to reclaim the
188
+ leftover clone — pass `--purge` or run `skaile cache clean`.
189
+ - `skaile install <ref>` (pointer-only global install) adds a clarifying line that
190
+ it installed globally under `~/.skaile/libraries/installed/`, and that
191
+ `skaile add <ref>` is how to deploy the asset into the current project.
192
+
193
+ - [#328](https://github.com/skaile-ai/workspaces/pull/328) [`e613124`](https://github.com/skaile-ai/workspaces/commit/e613124bc6d5f1a46f01504a1fc239a0a905379f) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Preset UX polish from hands-on testing: `preset validate` no longer emits a
194
+ spurious "required" warning for a required placeholder with no default (that is
195
+ exactly what `export --redact` produces — required-ness is enforced at apply, not
196
+ validate); `skaile init` next-steps now suggests `preset apply`; and the apply
197
+ env-var hint points users to `.env`/shell (not skaile.yaml) so a first-time user
198
+ applying a shared preset knows where the secret value goes.
199
+
200
+ - [#330](https://github.com/skaile-ai/workspaces/pull/330) [`be69fb8`](https://github.com/skaile-ai/workspaces/commit/be69fb816125478d7f1d2d1cc3fea8162d3f0875) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Fix `skaile search` so its store leg reaches the live catalog. The store half now
201
+ goes through the shared catalog client (tRPC to `skaile.store`) instead of the dead
202
+ `store.skaile.ai` REST endpoint, surfaces `catalog.url: local` and failures
203
+ non-silently, and prints copyable `kind:@publisher/name#version` refs for both store
204
+ and local results.
205
+
206
+ - [#337](https://github.com/skaile-ai/workspaces/pull/337) [`b0acd87`](https://github.com/skaile-ai/workspaces/commit/b0acd87f3128946fd48a120afabf5cfe262c42ef) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Collapse per-asset missing-sha256 advisories into one summary line.
207
+
208
+ Since `source manifest init` stopped writing per-asset `sha256` for `root:`
209
+ entries, `skaile source add` / `source sync` printed one
210
+ `Missing sha256 — computed <hash> …` advisory line per asset. For a curated
211
+ overlay with dozens of assets that was pure noise.
212
+
213
+ Discovery now tags each absent-sha256 warning with a machine-readable
214
+ `code: "missing_sha256"` (on `ReferenceWarning`) instead of relying on the
215
+ message text, and `LocalCatalogSource.sync()` counts those and emits exactly
216
+ one collapsed advisory:
217
+ `<n> asset(s): sha256 not pinned — auto-computed locally (pin only for store publish)`.
218
+ All other advisories (e.g. unknown-kind ghost-install) still print individually.
219
+ The fatal `ManifestHashMismatch` path for a DECLARED-but-wrong sha256 is
220
+ unchanged.
221
+
222
+ - [#336](https://github.com/skaile-ai/workspaces/pull/336) [`1d2cf89`](https://github.com/skaile-ai/workspaces/commit/1d2cf8905cd67bd5b568b046d6aff49d01cdc16d) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Sidecar polish — five papercut fixes for the `skaile source manifest` sidecar
223
+ authoring flow, found consuming `github.com/larksuite/cli`.
224
+
225
+ - **`init` no longer writes per-asset `sha256`.** The init writer emitted the
226
+ discovery `sha256` (computed over the FILTERED per-kind file set) on each
227
+ `root:` entry; `source add` then re-walked the WHOLE root dir → a different
228
+ file set → a different composite hash → `ManifestHashMismatch`, failing every
229
+ asset. `init` now omits `sha256` for `root:`-mode entries (a missing hash is a
230
+ non-fatal advisory), so `source add` on init output indexes cleanly with zero
231
+ hash mismatches.
232
+ - **`init` skips test-fixture directories.** Assets whose repo-relative `root`
233
+ contains a `tests` / `test` / `__tests__` path segment are dropped from the
234
+ generated manifest (conservative, not repo-specific) so fixture "skills"
235
+ (`scripts/*/tests/*`, `tests/cli_e2e/*`) don't pollute the curated inventory.
236
+ `init`-path only; the general discovery walk (`source add`) is untouched.
237
+ - **`init` next-step hint prints a runnable command; `edit` accepts the store
238
+ slug.** The hint now echoes the exact `<src>` the user passed (the URL) instead
239
+ of a bare short name that mis-expanded to the skaile-ai org. `resolveSourceArg`
240
+ additionally accepts the collision-free store slug (`<host>__<owner>__<repo>`)
241
+ as an identifier, matched against existing `~/.skaile/store/manifests/<arg>.yaml`
242
+ before bare-name expansion. Full-URL resolution still works.
243
+ - **`edit` respects a full `$EDITOR` command.** `EDITOR="code --wait"` now spawns
244
+ `code` with args `["--wait", <file>]` (was treated as one binary and failed).
245
+ New `resolveEditorCommand` splits the editor value on whitespace and appends
246
+ the target last; unset falls back to `vi`.
247
+ - **`preset export` warns on machine-local sidecar overlays.** When a captured
248
+ source resolves through a curated store manifest on this machine, export now
249
+ warns that those assets may not resolve elsewhere until the sidecar is shared.
250
+
251
+ - [#333](https://github.com/skaile-ai/workspaces/pull/333) [`d213d69`](https://github.com/skaile-ai/workspaces/commit/d213d696e308f5253c618df359e8aa0625be7c8a) Thanks [@P1umPudding](https://github.com/P1umPudding)! - Four asset-consume UX papercuts from hands-on testing (foreign repo shipping a
252
+ `skaile.manifest.yaml`, added via `skaile source add`, deployed via `skaile add`):
253
+
254
+ - **P-A — publisher `@` normalization.** A manifest declaring `publisher: '@acme'`
255
+ is now stored as `acme` (the leading `@` is the scope sigil the ref formatter
256
+ adds automatically). Previously it indexed as `@@acme` and the asset was
257
+ un-addable. Both manifest read paths are covered: `decodeSkaileManifest` (index
258
+ side, emits a `warning` diagnostic) **and** `resolveSourcePublisher` / `scanRepo`
259
+ (the `skaile search --local` scan/display side, which previously rendered the
260
+ copyable ref as `@@acme`). Only a single leading `@` is stripped; bare names are
261
+ untouched.
262
+ - **P-B — missing `sha256` is an advisory, not an indexing failure.** A manifest
263
+ asset with no `sha256` (and an unknown asset kind) now flows through the
264
+ discovery `warnings` channel and surfaces as an `advisory` on
265
+ `LocalCatalogSource.sync()` — never on `errors`. `skaile source add` /
266
+ `source sync` no longer report a false "N asset(s) failed to index" or exit 1
267
+ in this case; the asset is indexed and deployable, and the advisory is printed
268
+ on its own info line.
269
+ - **P-C — dead command pointer removed.** The missing-`sha256` advisory no longer
270
+ points at the not-wired `skaile source manifest rebuild` stub; it now reads
271
+ "only needed for store publish; auto-computed locally".
272
+ - **P-E — search kind-miss message.** `skaile search <kind>` with no matches now
273
+ says `No "<kind>" assets found in this project's sources.` instead of the
274
+ misleading never-synced `Run \`skaile source sync\` first` hint.
275
+
3
276
  ## 1.4.0
4
277
 
5
278
  ### Minor Changes
@@ -30,6 +30,20 @@ var consoleLogger = {
30
30
 
31
31
  // app-sdk/src/app.ts
32
32
  var DEFAULT_HELLO_TIMEOUT_MS = 1e4;
33
+ var DEFAULT_RECONNECT = {
34
+ maxAttempts: 5,
35
+ baseMs: 1e3,
36
+ maxMs: 3e4
37
+ };
38
+ var AppHandshakeRefusedError = class extends Error {
39
+ retryable;
40
+ constructor(reason, retryable) {
41
+ super(`app handshake refused: ${reason}`);
42
+ this.name = "AppHandshakeRefusedError";
43
+ this.retryable = retryable;
44
+ }
45
+ };
46
+ var delay = (ms) => new Promise((r) => setTimeout(r, ms));
33
47
  function createSkaileApp(options) {
34
48
  const {
35
49
  appId,
@@ -38,11 +52,23 @@ function createSkaileApp(options) {
38
52
  helloTimeoutMs = DEFAULT_HELLO_TIMEOUT_MS,
39
53
  logger = noopLogger
40
54
  } = options;
55
+ const reconnectEnabled = options.reconnect !== false;
56
+ const policy = {
57
+ ...DEFAULT_RECONNECT,
58
+ ...options.reconnect === false ? {} : options.reconnect
59
+ };
41
60
  const registry = /* @__PURE__ */ new Map();
61
+ const sessionListeners = /* @__PURE__ */ new Set();
42
62
  let session = null;
63
+ let closed = false;
64
+ let connecting = false;
65
+ let subscribed = false;
66
+ let epoch = 0;
43
67
  let unsubscribeMessage = null;
44
68
  let unsubscribeDisconnect = null;
45
- let abortHandshake = null;
69
+ let reconnectTimer;
70
+ let reconnectWake = null;
71
+ let pending = null;
46
72
  function toWire(cap) {
47
73
  return {
48
74
  name: cap.name,
@@ -61,6 +87,10 @@ function createSkaileApp(options) {
61
87
  render: cap.render
62
88
  };
63
89
  }
90
+ function setSession(next) {
91
+ session = next;
92
+ for (const h of sessionListeners) h(next);
93
+ }
64
94
  function registerCapability(cap) {
65
95
  registry.set(cap.name, cap);
66
96
  if (session) {
@@ -96,69 +126,146 @@ function createSkaileApp(options) {
96
126
  transport.send({ type: "capability_result", callId, result: { error: message } });
97
127
  }
98
128
  }
99
- function connect() {
100
- if (session) return Promise.resolve(session);
101
- if (abortHandshake) return Promise.reject(new Error("app connect() is already in progress"));
102
- return new Promise((resolve, reject) => {
103
- let settled = false;
104
- let helloTimer;
105
- const finishHandshake = () => {
106
- if (helloTimer) clearTimeout(helloTimer);
107
- abortHandshake = null;
108
- };
109
- const fail = (err) => {
110
- if (settled) return;
111
- settled = true;
112
- finishHandshake();
113
- reject(err);
114
- };
115
- abortHandshake = fail;
116
- const onMessage = (message) => {
117
- switch (message.type) {
118
- case "app_hello_ack": {
129
+ function onMessage(message) {
130
+ switch (message.type) {
131
+ case "app_hello_ack": {
132
+ const bound = { sessionId: message.sessionId, userId: message.userId };
133
+ pending?.settle(null, bound);
134
+ return;
135
+ }
136
+ case "app_hello_nack":
137
+ pending?.settle(new AppHandshakeRefusedError(message.error, message.retryable === true));
138
+ return;
139
+ case "capability_invoked":
140
+ void handleInvocation(message.callId, message.name, message.input);
141
+ return;
142
+ }
143
+ }
144
+ function onDisconnect() {
145
+ if (pending) {
146
+ pending.settle(new Error("transport disconnected during handshake"));
147
+ return;
148
+ }
149
+ if (closed || !session) return;
150
+ logger.warn("app connection lost", { appId });
151
+ setSession(null);
152
+ if (!reconnectEnabled) return;
153
+ connecting = true;
154
+ const loopEpoch = epoch;
155
+ void reconnectLoop().finally(() => {
156
+ if (epoch === loopEpoch) connecting = false;
157
+ });
158
+ }
159
+ function ensureSubscribed() {
160
+ if (subscribed) return;
161
+ unsubscribeMessage = transport.onMessage(onMessage);
162
+ unsubscribeDisconnect = transport.onDisconnect(onDisconnect);
163
+ subscribed = true;
164
+ }
165
+ function performHandshake() {
166
+ ensureSubscribed();
167
+ return transport.connect().then(
168
+ () => new Promise((resolve, reject) => {
169
+ if (closed) {
170
+ reject(new Error("app closed during handshake"));
171
+ return;
172
+ }
173
+ let settled = false;
174
+ const timer = setTimeout(() => {
175
+ pending?.settle(new Error(`app handshake timed out after ${helloTimeoutMs}ms`));
176
+ }, helloTimeoutMs);
177
+ pending = {
178
+ settle: (err, bound) => {
119
179
  if (settled) return;
120
180
  settled = true;
121
- finishHandshake();
122
- session = { sessionId: message.sessionId, userId: message.userId };
123
- logger.info("app connection established", { appId, sessionId: session.sessionId });
181
+ clearTimeout(timer);
182
+ pending = null;
183
+ if (err || !bound) {
184
+ reject(err ?? new Error("app handshake failed"));
185
+ return;
186
+ }
187
+ setSession(bound);
188
+ logger.info("app connection established", { appId, sessionId: bound.sessionId });
124
189
  const wire = Array.from(registry.values()).map(toWire);
125
190
  if (wire.length > 0) {
126
191
  transport.send({ type: "capability_register", capabilities: wire });
127
192
  }
128
- resolve(session);
129
- return;
193
+ resolve(bound);
130
194
  }
131
- case "app_hello_nack":
132
- fail(new Error(`app handshake refused: ${message.error}`));
133
- return;
134
- case "capability_invoked":
135
- void handleInvocation(message.callId, message.name, message.input);
136
- return;
195
+ };
196
+ transport.send({ type: "app_hello", appId, protocolVersion });
197
+ }),
198
+ (err) => Promise.reject(err instanceof Error ? err : new Error(String(err)))
199
+ );
200
+ }
201
+ const backoffMs = (attempt) => Math.min(policy.maxMs, policy.baseMs * 2 ** Math.max(0, attempt - 1));
202
+ async function reconnectLoop() {
203
+ const myEpoch = epoch;
204
+ for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
205
+ const wait = backoffMs(attempt);
206
+ logger.debug("scheduling app reconnect", { appId, attempt, waitMs: wait });
207
+ await new Promise((r) => {
208
+ reconnectWake = r;
209
+ reconnectTimer = setTimeout(r, wait);
210
+ });
211
+ reconnectWake = null;
212
+ if (closed || epoch !== myEpoch) {
213
+ logger.debug("app reconnect cancelled", { appId, attempt });
214
+ return;
215
+ }
216
+ try {
217
+ await performHandshake();
218
+ logger.info("app connection re-established", { appId });
219
+ return;
220
+ } catch (err) {
221
+ if (err instanceof AppHandshakeRefusedError && !err.retryable) {
222
+ logger.error("app reconnect refused permanently", { appId, error: err.message });
223
+ return;
137
224
  }
138
- };
139
- const onDisconnect = () => {
140
- fail(new Error("transport disconnected during handshake"));
141
- };
142
- transport.connect().then(
143
- () => {
144
- if (settled) return;
145
- unsubscribeMessage = transport.onMessage(onMessage);
146
- unsubscribeDisconnect = transport.onDisconnect(onDisconnect);
147
- helloTimer = setTimeout(() => {
148
- fail(new Error(`app handshake timed out after ${helloTimeoutMs}ms`));
149
- }, helloTimeoutMs);
150
- transport.send({ type: "app_hello", appId, protocolVersion });
151
- },
152
- (err) => fail(err instanceof Error ? err : new Error(String(err)))
153
- );
154
- });
225
+ logger.warn("app reconnect attempt failed", {
226
+ appId,
227
+ attempt,
228
+ error: err instanceof Error ? err.message : String(err)
229
+ });
230
+ }
231
+ }
232
+ logger.error("app reconnect gave up", { appId, attempts: policy.maxAttempts });
233
+ }
234
+ async function connect() {
235
+ if (session) return session;
236
+ if (connecting) throw new Error("app connect() is already in progress");
237
+ connecting = true;
238
+ closed = false;
239
+ try {
240
+ for (let attempt = 1; ; attempt++) {
241
+ try {
242
+ return await performHandshake();
243
+ } catch (err) {
244
+ const transient = err instanceof AppHandshakeRefusedError && err.retryable;
245
+ if (!transient || !reconnectEnabled || attempt >= policy.maxAttempts || closed) {
246
+ throw err;
247
+ }
248
+ logger.debug("app handshake refused (retryable), retrying", { appId, attempt });
249
+ await delay(backoffMs(attempt));
250
+ }
251
+ }
252
+ } finally {
253
+ connecting = false;
254
+ }
155
255
  }
156
256
  async function close() {
157
- abortHandshake?.(new Error("app closed during handshake"));
257
+ closed = true;
258
+ epoch++;
259
+ clearTimeout(reconnectTimer);
260
+ reconnectWake?.();
261
+ reconnectWake = null;
262
+ connecting = false;
263
+ pending?.settle(new Error("app closed during handshake"));
158
264
  unsubscribeMessage?.();
159
265
  unsubscribeDisconnect?.();
160
266
  unsubscribeMessage = null;
161
267
  unsubscribeDisconnect = null;
268
+ subscribed = false;
162
269
  session = null;
163
270
  await transport.disconnect();
164
271
  }
@@ -172,9 +279,84 @@ function createSkaileApp(options) {
172
279
  return session;
173
280
  },
174
281
  connect,
282
+ onSessionChange(handler) {
283
+ sessionListeners.add(handler);
284
+ return () => sessionListeners.delete(handler);
285
+ },
175
286
  close
176
287
  };
177
288
  }
289
+
290
+ // app-sdk/src/connection-params.ts
291
+ var PARENT_ORIGIN_QUERY_PARAM = "skaileParentOrigin";
292
+ var DEFAULT_PARAMS_TIMEOUT_MS = 1e4;
293
+ function readParentOriginFromQuery(search) {
294
+ const value = new URLSearchParams(search).get(PARENT_ORIGIN_QUERY_PARAM);
295
+ return value && value.length > 0 ? value : null;
296
+ }
297
+ function isParamsMessage(data, appId) {
298
+ if (!data || typeof data !== "object") return false;
299
+ const m = data;
300
+ return m.type === "skaile_app_params" && m.appId === appId && typeof m.url === "string" && m.url.trim().length > 0 && typeof m.token === "string" && m.token.trim().length > 0;
301
+ }
302
+ function resolveAppConnectionParams(options) {
303
+ const { appId, timeoutMs = DEFAULT_PARAMS_TIMEOUT_MS } = options;
304
+ const win = options.windowRef ?? globalThis.window ?? null;
305
+ if (!win) {
306
+ return Promise.reject(
307
+ new Error("resolveAppConnectionParams requires a window (pass `windowRef` off-browser)")
308
+ );
309
+ }
310
+ const parent = win.parent;
311
+ if (!parent || parent === win) {
312
+ return Promise.reject(
313
+ new Error("app is not embedded \u2014 no parent frame to request params from")
314
+ );
315
+ }
316
+ const parentOrigin = options.parentOrigin ?? readParentOriginFromQuery(win.location.search);
317
+ if (!parentOrigin) {
318
+ return Promise.reject(
319
+ new Error(
320
+ `cannot determine the host origin: pass \`parentOrigin\` or set the \`${PARENT_ORIGIN_QUERY_PARAM}\` query param`
321
+ )
322
+ );
323
+ }
324
+ if (parentOrigin === "*" || parentOrigin === "null") {
325
+ return Promise.reject(new Error(`refusing to use an unpinnable host origin: ${parentOrigin}`));
326
+ }
327
+ return new Promise((resolve, reject) => {
328
+ let settled = false;
329
+ const cleanup = () => {
330
+ clearTimeout(timer);
331
+ win.removeEventListener("message", onMessage);
332
+ };
333
+ const timer = setTimeout(() => {
334
+ if (settled) return;
335
+ settled = true;
336
+ cleanup();
337
+ reject(
338
+ new Error(`timed out after ${timeoutMs}ms waiting for connection params from the host`)
339
+ );
340
+ }, timeoutMs);
341
+ function onMessage(event) {
342
+ if (settled) return;
343
+ if (event.origin !== parentOrigin || event.source !== parent) return;
344
+ if (!isParamsMessage(event.data, appId)) return;
345
+ settled = true;
346
+ cleanup();
347
+ resolve({ url: event.data.url, appId, token: event.data.token });
348
+ }
349
+ win.addEventListener("message", onMessage);
350
+ 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)));
357
+ }
358
+ });
359
+ }
178
360
  function defineAppCapability(args) {
179
361
  const inputSchema = z.toJSONSchema(args.input, {
180
362
  target: "draft-07",
@@ -207,9 +389,7 @@ function defineAppCapability(args) {
207
389
  // app-sdk/src/ws-transport.ts
208
390
  var BEARER_SUBPROTOCOL_PREFIX = "skaile-bearer.";
209
391
  function encodeBearerSubprotocol(token) {
210
- const bytes = new TextEncoder().encode(token);
211
- let binary = "";
212
- for (const byte of bytes) binary += String.fromCharCode(byte);
392
+ const binary = String.fromCharCode(...new TextEncoder().encode(token));
213
393
  const b64 = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
214
394
  return BEARER_SUBPROTOCOL_PREFIX + b64;
215
395
  }
@@ -227,9 +407,15 @@ function createWebSocketAppTransport(options) {
227
407
  const messageHandlers = /* @__PURE__ */ new Set();
228
408
  const disconnectHandlers = /* @__PURE__ */ new Set();
229
409
  function connect() {
410
+ if (typeof sessionToken === "function") {
411
+ return Promise.resolve(sessionToken()).then(openSocket);
412
+ }
413
+ return openSocket(sessionToken);
414
+ }
415
+ function openSocket(token) {
230
416
  return new Promise((resolve, reject) => {
231
417
  let opened = false;
232
- const socket = new WebSocketImpl(url, [encodeBearerSubprotocol(sessionToken)]);
418
+ const socket = new WebSocketImpl(url, [encodeBearerSubprotocol(token)]);
233
419
  ws = socket;
234
420
  const timer = setTimeout(() => {
235
421
  if (opened) return;
@@ -290,6 +476,6 @@ function createWebSocketAppTransport(options) {
290
476
  };
291
477
  }
292
478
 
293
- export { DEFAULT_HELLO_TIMEOUT_MS, consoleLogger, createSkaileApp, createWebSocketAppTransport, defineAppCapability, noopLogger };
479
+ export { AppHandshakeRefusedError, DEFAULT_HELLO_TIMEOUT_MS, DEFAULT_PARAMS_TIMEOUT_MS, PARENT_ORIGIN_QUERY_PARAM, consoleLogger, createSkaileApp, createWebSocketAppTransport, defineAppCapability, noopLogger, resolveAppConnectionParams };
294
480
  //# sourceMappingURL=index.js.map
295
481
  //# sourceMappingURL=index.js.map