@skaile/workspaces 4.29.1 → 4.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/app-sdk/index.js +1 -1
  3. package/dist/{asset-feeds-46K7CTW2.js → asset-feeds-YB5ROSAE.js} +4 -4
  4. package/dist/{asset-feeds-46K7CTW2.js.map → asset-feeds-YB5ROSAE.js.map} +1 -1
  5. package/dist/asset-manager/index.js +1 -1
  6. package/dist/bridge/drivers/claude-sdk.js +132 -15
  7. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  8. package/dist/bridge/drivers/codex.js +3 -3
  9. package/dist/bridge/drivers/continue.js +4 -4
  10. package/dist/bridge/drivers/echo.js +3 -3
  11. package/dist/bridge/drivers/gemini.js +6 -6
  12. package/dist/bridge/drivers/goose.js +4 -4
  13. package/dist/bridge/drivers/omp.js +5 -5
  14. package/dist/bridge/drivers/opencode.js +4 -4
  15. package/dist/bridge/drivers/qwen.js +6 -6
  16. package/dist/bridge/index.js +6 -6
  17. package/dist/bridge/src/drivers/claude-sdk.d.ts +35 -0
  18. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  19. package/dist/bridge/src/error-classifier.d.ts +13 -0
  20. package/dist/bridge/src/error-classifier.d.ts.map +1 -1
  21. package/dist/bridge/src/normalizer.d.ts.map +1 -1
  22. package/dist/bridge/src/types.d.ts +19 -1
  23. package/dist/bridge/src/types.d.ts.map +1 -1
  24. package/dist/{chunk-C56FCYFU.js → chunk-2ICKARYU.js} +3 -3
  25. package/dist/{chunk-C56FCYFU.js.map → chunk-2ICKARYU.js.map} +1 -1
  26. package/dist/{chunk-6ZELKHAI.js → chunk-3AX5PZRA.js} +4 -4
  27. package/dist/{chunk-6ZELKHAI.js.map → chunk-3AX5PZRA.js.map} +1 -1
  28. package/dist/{chunk-X5CW2HDM.js → chunk-3TK7CZVL.js} +3 -3
  29. package/dist/{chunk-X5CW2HDM.js.map → chunk-3TK7CZVL.js.map} +1 -1
  30. package/dist/{chunk-SZEICOXM.js → chunk-7HJ3YLRF.js} +5 -5
  31. package/dist/chunk-7HJ3YLRF.js.map +1 -0
  32. package/dist/{chunk-3LIUBF5P.js → chunk-7VQGUNXJ.js} +2 -2
  33. package/dist/{chunk-3LIUBF5P.js.map → chunk-7VQGUNXJ.js.map} +1 -1
  34. package/dist/{chunk-7NC3VL6Z.js → chunk-DDCRSE5P.js} +4 -4
  35. package/dist/{chunk-7NC3VL6Z.js.map → chunk-DDCRSE5P.js.map} +1 -1
  36. package/dist/{chunk-T57M3SST.js → chunk-ECTQR7IM.js} +6 -6
  37. package/dist/{chunk-T57M3SST.js.map → chunk-ECTQR7IM.js.map} +1 -1
  38. package/dist/{chunk-H5EGBMZ5.js → chunk-FAQRNZ44.js} +2 -2
  39. package/dist/chunk-FAQRNZ44.js.map +1 -0
  40. package/dist/{chunk-ERDK4ATM.js → chunk-FF5L6IV3.js} +3 -3
  41. package/dist/{chunk-ERDK4ATM.js.map → chunk-FF5L6IV3.js.map} +1 -1
  42. package/dist/{chunk-G3YTK6UI.js → chunk-FRX2BO3V.js} +159 -26
  43. package/dist/chunk-FRX2BO3V.js.map +1 -0
  44. package/dist/{chunk-GYBCW4S7.js → chunk-L2GKM47Q.js} +6 -6
  45. package/dist/{chunk-GYBCW4S7.js.map → chunk-L2GKM47Q.js.map} +1 -1
  46. package/dist/{chunk-NGQVRFQJ.js → chunk-LUC6OEWM.js} +3 -3
  47. package/dist/{chunk-NGQVRFQJ.js.map → chunk-LUC6OEWM.js.map} +1 -1
  48. package/dist/{chunk-HFVK7FKK.js → chunk-M42SMH7V.js} +3 -3
  49. package/dist/{chunk-HFVK7FKK.js.map → chunk-M42SMH7V.js.map} +1 -1
  50. package/dist/{chunk-DJZRPPFJ.js → chunk-MX7LCQUQ.js} +4 -4
  51. package/dist/chunk-MX7LCQUQ.js.map +1 -0
  52. package/dist/{chunk-7SLRVWKR.js → chunk-NP3WH7JF.js} +3 -3
  53. package/dist/{chunk-7SLRVWKR.js.map → chunk-NP3WH7JF.js.map} +1 -1
  54. package/dist/{chunk-LIEOC4JE.js → chunk-NQCQTAMQ.js} +8 -8
  55. package/dist/chunk-NQCQTAMQ.js.map +1 -0
  56. package/dist/{chunk-Q2AOHZFF.js → chunk-PM6DO25W.js} +7 -6
  57. package/dist/chunk-PM6DO25W.js.map +1 -0
  58. package/dist/{chunk-PJY4J37J.js → chunk-RXTKWTZV.js} +3 -3
  59. package/dist/{chunk-PJY4J37J.js.map → chunk-RXTKWTZV.js.map} +1 -1
  60. package/dist/{chunk-XEBSA7YU.js → chunk-SM3UPDXE.js} +6 -6
  61. package/dist/{chunk-XEBSA7YU.js.map → chunk-SM3UPDXE.js.map} +1 -1
  62. package/dist/{chunk-DMXEFYND.js → chunk-UKIL4XH5.js} +3 -3
  63. package/dist/{chunk-DMXEFYND.js.map → chunk-UKIL4XH5.js.map} +1 -1
  64. package/dist/{chunk-5UQNCPIP.js → chunk-VYNIZDAD.js} +3 -3
  65. package/dist/{chunk-5UQNCPIP.js.map → chunk-VYNIZDAD.js.map} +1 -1
  66. package/dist/{chunk-BGY4KTU6.js → chunk-XBSQ4KMC.js} +10 -3
  67. package/dist/chunk-XBSQ4KMC.js.map +1 -0
  68. package/dist/{chunk-ZKX5KY2K.js → chunk-ZVVZ67EH.js} +2 -2
  69. package/dist/{chunk-ZKX5KY2K.js.map → chunk-ZVVZ67EH.js.map} +1 -1
  70. package/dist/cli/index.js +36 -36
  71. package/dist/connectors/index.js +8 -8
  72. package/dist/connectors-shared/index.js +2 -2
  73. package/dist/{ensure-sources-HH4CCGY5.js → ensure-sources-3VOIO6IM.js} +4 -4
  74. package/dist/{ensure-sources-HH4CCGY5.js.map → ensure-sources-3VOIO6IM.js.map} +1 -1
  75. package/dist/factory-assets/connectors/box.js +8 -8
  76. package/dist/factory-assets/connectors/deploy.js +8 -8
  77. package/dist/factory-assets/connectors/devserver.js +8 -8
  78. package/dist/factory-assets/connectors/flow/adapter.js +8 -8
  79. package/dist/factory-assets/connectors/flow/run-flow.d.ts +20 -0
  80. package/dist/factory-assets/connectors/flow/run-flow.d.ts.map +1 -1
  81. package/dist/factory-assets/connectors/flow/run-flow.js +9 -9
  82. package/dist/factory-assets/connectors/flow.js +8 -8
  83. package/dist/factory-assets/connectors/git.js +8 -8
  84. package/dist/factory-assets/connectors/gmail.js +8 -8
  85. package/dist/factory-assets/connectors/googledrive.js +8 -8
  86. package/dist/factory-assets/connectors/local.js +8 -8
  87. package/dist/factory-assets/connectors/mattermost.js +8 -8
  88. package/dist/factory-assets/connectors/memory.js +8 -8
  89. package/dist/factory-assets/connectors/minio.js +8 -8
  90. package/dist/factory-assets/connectors/postgres.js +8 -8
  91. package/dist/factory-assets/connectors/s3.js +8 -8
  92. package/dist/factory-assets/connectors/sharepoint.js +8 -8
  93. package/dist/factory-assets/connectors/sqlite.js +8 -8
  94. package/dist/factory-assets/connectors/static-server.js +8 -8
  95. package/dist/factory-assets/connectors/tunnel.js +8 -8
  96. package/dist/factory-assets/connectors/webdav.js +8 -8
  97. package/dist/factory-assets/connectors/xstate-store.js +8 -8
  98. package/dist/factory-assets/connectors/xstate.js +8 -8
  99. package/dist/{helpers-BKOX66EA.js → helpers-G7AQV3NN.js} +4 -4
  100. package/dist/{helpers-BKOX66EA.js.map → helpers-G7AQV3NN.js.map} +1 -1
  101. package/dist/{library-publish-PCY3ULAA.js → library-publish-YOF75HVC.js} +6 -6
  102. package/dist/{library-publish-PCY3ULAA.js.map → library-publish-YOF75HVC.js.map} +1 -1
  103. package/dist/{open-library-R73XWQNW.js → open-library-DAIEAPNR.js} +5 -5
  104. package/dist/{open-library-R73XWQNW.js.map → open-library-DAIEAPNR.js.map} +1 -1
  105. package/dist/{project-7RHUAYUZ.js → project-IKHGSG4I.js} +5 -5
  106. package/dist/{project-7RHUAYUZ.js.map → project-IKHGSG4I.js.map} +1 -1
  107. package/dist/runner/index.js +12 -12
  108. package/dist/runner/src/agent.d.ts.map +1 -1
  109. package/dist/runner/src/flow-turn-dispatch.d.ts +4 -3
  110. package/dist/runner/src/flow-turn-dispatch.d.ts.map +1 -1
  111. package/dist/runner/src/init-build-hold.d.ts +60 -0
  112. package/dist/runner/src/init-build-hold.d.ts.map +1 -0
  113. package/dist/runner/src/serve.d.ts +14 -0
  114. package/dist/runner/src/serve.d.ts.map +1 -1
  115. package/dist/sdk/asset-manager.js +1 -1
  116. package/dist/sdk/bridge.js +6 -6
  117. package/dist/sdk/index.js +12 -12
  118. package/dist/sdk/runner.js +12 -12
  119. package/dist/sdk/session.js +2 -2
  120. package/dist/sdk/types.js +1 -1
  121. package/dist/session/index.js +2 -2
  122. package/dist/{setup-MYJDFOCG.js → setup-234UBMJX.js} +11 -11
  123. package/dist/{setup-MYJDFOCG.js.map → setup-234UBMJX.js.map} +1 -1
  124. package/dist/{source-SKGXVE7I.js → source-VAKNCLQY.js} +6 -6
  125. package/dist/{source-SKGXVE7I.js.map → source-VAKNCLQY.js.map} +1 -1
  126. package/dist/{source-manifest-publish-QSZRNMSM.js → source-manifest-publish-FEFRWILI.js} +7 -7
  127. package/dist/{source-manifest-publish-QSZRNMSM.js.map → source-manifest-publish-FEFRWILI.js.map} +1 -1
  128. package/dist/tui/index.js +12 -12
  129. package/dist/types/index.js +1 -1
  130. package/dist/types/src/events.d.ts +31 -0
  131. package/dist/types/src/events.d.ts.map +1 -1
  132. package/dist/types/src/index.d.ts +1 -1
  133. package/dist/types/src/index.d.ts.map +1 -1
  134. package/dist/types/src/version.d.ts +16 -1
  135. package/dist/types/src/version.d.ts.map +1 -1
  136. package/dist/workspace-plugin/adapters/mcp.js +2 -2
  137. package/dist/workspace-plugin/index.js +1 -1
  138. package/package.json +1 -1
  139. package/dist/chunk-BGY4KTU6.js.map +0 -1
  140. package/dist/chunk-DJZRPPFJ.js.map +0 -1
  141. package/dist/chunk-G3YTK6UI.js.map +0 -1
  142. package/dist/chunk-H5EGBMZ5.js.map +0 -1
  143. package/dist/chunk-LIEOC4JE.js.map +0 -1
  144. package/dist/chunk-Q2AOHZFF.js.map +0 -1
  145. package/dist/chunk-SZEICOXM.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.30.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#942](https://github.com/skaile-ai/workspaces/pull/942) [`57555b7`](https://github.com/skaile-ai/workspaces/commit/57555b75bdd7385be0b2d9b23bf5febaaa9019b5) Thanks [@Frozen666](https://github.com/Frozen666)! - bridge: the claude-sdk driver now makes a capability registered mid-session callable from the next turn. Since the move to streaming input mode the resident query keeps the `skaile-capabilities` MCP server it was spawned with, so a Skailify app's `app.*` capabilities showed up in `__capabilities.search` but never in the agent's tool set, on any later turn. The driver now recycles the resident query at the next turn boundary (resuming the same session) when the registry's tool set differs from the spawned one; an identical set re-registered in another order does not recycle (platform#7066).
8
+
9
+ - [#939](https://github.com/skaile-ai/workspaces/pull/939) [`39e1414`](https://github.com/skaile-ai/workspaces/commit/39e141461271258b5e49adb0b12ba318eabd59b9) Thanks [@Frozen666](https://github.com/Frozen666)! - The Claude SDK driver now recognises the 429 that says an account needs extra usage for long (1M) context. It logs one `long-context entitlement rejection` WARN with the seat's provider config id and model, and tags the reported 429 (`provider_response_seen`) with `cause: "long-context-entitlement"` (protocol 4.11.0). Seat failover and the seat's 429 counters behave exactly as before; this only makes the case visible.
10
+
11
+ - [#941](https://github.com/skaile-ai/workspaces/pull/941) [`791d393`](https://github.com/skaile-ai/workspaces/commit/791d3938364d53b87cd3a77f9616c9d635e51397) Thanks [@peteralbert](https://github.com/peteralbert)! - runner: a `connector_mutate` sent during a cold container's first `session_init` build now answers within its hold. The handler used to await `sessionReady` before entering the hold, and in provisioned mode `sessionReady` resolves only when the first build ends. A build slowed by a git clone (about 17 s in prod) therefore left a run-group `flow.start` with no answer past the platform's 12 s ack timeout. The hold now comes first, so the start answers `connector_mutate before resources ready` by its 10 s deadline and the platform resends it (platform#6987).
12
+
13
+ ## 4.30.0
14
+
15
+ ### Minor Changes
16
+
17
+ - [#933](https://github.com/skaile-ai/workspaces/pull/933) [`2d05b79`](https://github.com/skaile-ai/workspaces/commit/2d05b7994b76ccae60cdb0e7278660b0a9ba959a) Thanks [@Frozen666](https://github.com/Frozen666)! - An in-place prompt retry is now marked on the wire (protocol 4.10.0). The Claude SDK driver's lost-transcript and poisoned-history retries emit their non-fatal `error` with `retrying: true` and a `retryReason` (`stale_resume` or `poison_scrub`), and the normalizer forwards both, so a host can tell the replay of a turn from a turn end. The poisoned-history path no longer sends the raw `400 invalid_request_error` as a separate, unmarked error before the retry; it still surfaces it when the scrub finds nothing to repair. Flow turns, flow subprompts and `skaile agent` no longer fail on a retry's error.
18
+
19
+ ### Patch Changes
20
+
21
+ - [#936](https://github.com/skaile-ai/workspaces/pull/936) [`da0b87f`](https://github.com/skaile-ai/workspaces/commit/da0b87f8cc2f999bc94e7bb9e013ca2a3d64f3d0) Thanks [@Frozen666](https://github.com/Frozen666)! - A `connector_mutate` sent while the runner is still building the session after `session_init` is now held and applied once the build finishes, in the order it arrived, instead of being refused with `connector_mutate before resources ready`. This covers a run group's flow start sent right after a wake and early shared-state merges, which were lost. A request carrying a `requestId` is held for at most 10 seconds (`SKAILE_CONNECTOR_MUTATE_INIT_HOLD_MS`), below the platform's 12 second acknowledgement timeout; past that it still answers `connector_mutate before resources ready`, so the platform's resend keeps working. A held request whose build fails answers `session_unavailable`.
22
+
3
23
  ## 4.29.1
4
24
 
5
25
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import { PROTOCOL_VERSION_STRING } from '../chunk-DJZRPPFJ.js';
1
+ import { PROTOCOL_VERSION_STRING } from '../chunk-MX7LCQUQ.js';
2
2
  import '../chunk-NSBPE2FW.js';
3
3
  import * as z from 'zod';
4
4
 
@@ -69,7 +69,7 @@ async function libraryAssets() {
69
69
  const domains = [];
70
70
  try {
71
71
  const [{ openLibraryManager }, { skaileHomeDir }, { getGlobalCacheDir }] = await Promise.all([
72
- import('./open-library-R73XWQNW.js'),
72
+ import('./open-library-DAIEAPNR.js'),
73
73
  import('./library/index.js'),
74
74
  import('./core/index.js')
75
75
  ]);
@@ -102,7 +102,7 @@ async function libraryAssets() {
102
102
  async function storeAssets(projectDir) {
103
103
  const notes = [];
104
104
  try {
105
- const { resolveCatalogSource } = await import('./open-library-R73XWQNW.js');
105
+ const { resolveCatalogSource } = await import('./open-library-DAIEAPNR.js');
106
106
  const { source, close } = await resolveCatalogSource({ projectDir });
107
107
  try {
108
108
  const assets = await source.listAssets();
@@ -206,5 +206,5 @@ function dedupeBySlug(domains) {
206
206
  }
207
207
 
208
208
  export { gatherAssetFeeds };
209
- //# sourceMappingURL=asset-feeds-46K7CTW2.js.map
210
- //# sourceMappingURL=asset-feeds-46K7CTW2.js.map
209
+ //# sourceMappingURL=asset-feeds-YB5ROSAE.js.map
210
+ //# sourceMappingURL=asset-feeds-YB5ROSAE.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../cli/src/asset-feeds.ts"],"names":[],"mappings":"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","file":"asset-feeds-46K7CTW2.js","sourcesContent":["/**\n * asset-feeds.ts — gather installable assets from every feed the manage TUI\n * surfaces.\n *\n * Four feeds, normalized into `CatalogEntry`:\n * - **Project sources** — `skaile.yaml` `sources:` entries (scanned via\n * `AssetManager.search()`; the new `sources:` field is merged into the\n * legacy repositories map in `AssetManager.loadConfig`).\n * - **Local libraries** — user authoring places from `LocalIndex` (filesystem\n * scan of each library's `path`).\n * - **Factory assets** — the bundled first-party `factory-assets` tree\n * (`factoryAssetRoots()`), scanned from disk. Always present; no network.\n * - **Remote store** — the configured catalog source when `catalog.url` is\n * not `local`. Failures are non-fatal — they surface as notes.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n type AssetKind,\n type CatalogDomain,\n type CatalogEntry,\n factoryAssetRoots,\n parseFrontmatter,\n scanRepo,\n walkDir,\n} from \"@skaile/workspaces/core\";\nimport {\n assignDomain,\n buildDomainIndex,\n DOMAIN_MD_FILENAME,\n type DomainMd,\n parseDomainMd,\n} from \"@skaile/workspaces/discovery\";\n\n/** One non-fatal note returned to the caller for surfacing in the UI. */\nexport interface FeedNote {\n feed: \"sources\" | \"libraries\" | \"store\" | \"factory\";\n message: string;\n}\n\n/**\n * Which feed an asset came from. This is the top-level grouping axis in the\n * manage TUI (\"where is this skill coming from?\") and is intentionally distinct\n * from `publisher` (asset-intrinsic identity): the same publisher can legitimately\n * appear under more than one origin (e.g. a github repo you added as a `source`\n * that is also listed in the remote `store`).\n */\nexport type AssetOrigin = \"source\" | \"library\" | \"factory\" | \"store\";\n\n/** A catalog entry tagged with the feed it was gathered from. */\nexport interface OriginEntry {\n origin: AssetOrigin;\n entry: CatalogEntry;\n /**\n * Second-level grouping label for the manage tree. Defaults (when unset) to the\n * entry's `publisher`; the source feed sets it to the source's short repo name\n * so the Sources band groups by source — \"which repo did this come from\" — not\n * by the asset-intrinsic publisher.\n */\n group?: string;\n}\n\nexport interface AssetFeedResult {\n entries: OriginEntry[];\n /**\n * Display-only domain metadata, keyed (by the consumer) on `slug`. Populated\n * from the store's `catalog.listDomains` when the connected catalog source\n * exposes it; empty otherwise. The manage TUI joins `entry.domainSlug` → a\n * domain for grouping headers, descriptions, and relation badges.\n */\n domains: CatalogDomain[];\n notes: FeedNote[];\n}\n\n// ── Domain stamping (on-disk feeds) ──────────────────────────────────────────\n//\n// `scanRepo` (the source / library / factory scan path) does not read the\n// `DOMAIN.md` sidecars, so its entries arrive with no `domainSlug` and render\n// flat under their publisher band. We re-derive the grouping here from the\n// root's `DOMAIN.md` files (the same sidecars the remote store computes\n// server-side), and fall back to the repo/source name for any entry no domain\n// claims so it still groups instead of landing in the synthetic \"no domain\" pile.\n\n/** Repo-relative POSIX path (DOMAIN.md slugs are matched whole-segment, `/`-joined). */\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n\n/** Parse every `DOMAIN.md` sidecar under `root` into a {@link DomainMd}. */\nasync function loadDomainMds(root: string): Promise<DomainMd[]> {\n const out: DomainMd[] = [];\n for await (const { path: filePath, name } of walkDir(root)) {\n if (name !== DOMAIN_MD_FILENAME) continue;\n try {\n const { data } = parseFrontmatter(readFileSync(filePath, \"utf8\"));\n out.push(parseDomainMd(toPosix(path.relative(root, filePath)), data));\n } catch {}\n }\n return out;\n}\n\n/**\n * Stamp `domainSlug` onto every entry scanned from `root` using the root's\n * `DOMAIN.md` sidecars. Returns the `CatalogDomain` registry for the real\n * domains, plus a synthetic fallback domain when any entry used the fallback.\n * Mutates `entries` in place (sets `entry.domainSlug`).\n *\n * `synthesizeFallback` (default true): entries no domain claims fall back to\n * `fallbackSlug` (the repo/source name). Pass `false` when the caller already\n * groups these entries one level up (e.g. the source feed groups by source\n * node) — unmatched entries then get no domain (`\"\"`) and hang directly under\n * that parent, instead of nesting under a same-named fallback domain.\n */\nasync function stampDomains(\n root: string,\n fallbackSlug: string,\n entries: CatalogEntry[],\n synthesizeFallback = true,\n): Promise<CatalogDomain[]> {\n const domainMds = await loadDomainMds(root);\n const assignables = entries.map((entry) => ({\n entry,\n ref: `${entry.kind}:${entry.name}`,\n relativePath: toPosix(path.relative(root, entry.source)),\n }));\n\n let usedFallback = false;\n for (const a of assignables) {\n const slug = assignDomain(a.relativePath, domainMds);\n if (slug) a.entry.domainSlug = slug;\n else if (synthesizeFallback) {\n a.entry.domainSlug = fallbackSlug;\n usedFallback = true;\n } else a.entry.domainSlug = \"\";\n }\n\n const { index } = buildDomainIndex(assignables, domainMds);\n const catalog: CatalogDomain[] = Object.values(index).map((d) => ({\n slug: d.slug,\n title: d.title,\n ...(d.description ? { description: d.description } : {}),\n ...(d.relation ? { relation: d.relation } : {}),\n ...(d.order !== undefined ? { order: d.order } : {}),\n ...(d.recommended[0] ? { recommendedRef: d.recommended[0] } : {}),\n }));\n if (usedFallback) catalog.push({ slug: fallbackSlug, title: fallbackSlug });\n return catalog;\n}\n\n/**\n * Pull assets from local libraries (the authoring places under\n * `~/.skaile/libraries/<name>/`, not the `~/.skaile/cache/sources/` cache). Each\n * library's `path` is scanned with `scanRepo` and grouped under the bare library\n * name (`group: <name>`) so the manage TUI shows a clean `<name>` band — no\n * `library:` prefix and no redundant same-named fallback domain (mirrors the\n * source feed).\n */\nasync function libraryAssets(): Promise<{\n entries: OriginEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const [{ openLibraryManager }, { skaileHomeDir }, { getGlobalCacheDir }] = await Promise.all([\n import(\"./open-library.ts\"),\n import(\"@skaile/workspaces/library\"),\n import(\"@skaile/workspaces/core\"),\n ]);\n const { manager, close } = await openLibraryManager();\n try {\n // Authoring libraries are the rows that are NOT source clones. index.db\n // rows predating the cache migration still carry the legacy\n // ~/.skaile/sources path, so exclude both roots.\n const newRoot = getGlobalCacheDir();\n const legacyRoot = path.join(skaileHomeDir(), \"sources\");\n const libs = (await manager.listLibraries()).filter(\n (l) => !l.path.startsWith(newRoot) && !l.path.startsWith(legacyRoot),\n );\n const entries: OriginEntry[] = [];\n for (const lib of libs) {\n if (!existsSync(lib.path)) continue;\n // Scan + group under the bare library name. `synthesizeFallback: false`\n // (as the source feed does) keeps domainless assets directly under the\n // library band instead of a redundant same-named fallback domain.\n const libEntries = [...(await scanRepo(lib.path, lib.name))];\n domains.push(...(await stampDomains(lib.path, lib.name, libEntries, false)));\n for (const entry of libEntries) entries.push({ origin: \"library\", entry, group: lib.name });\n }\n return { entries, domains, notes };\n } finally {\n close();\n }\n } catch (err) {\n notes.push({\n feed: \"libraries\",\n message: err instanceof Error ? err.message : String(err),\n });\n return { entries: [], domains, notes };\n }\n}\n\n/**\n * Pull a (best-effort) snapshot of the connected remote store catalog.\n *\n * - Returns no entries when the resolved catalog is local-only or the host\n * is unreachable (the failure becomes a note, not an exception).\n * - Maps `CatalogAsset` → `CatalogEntry`, carrying `publisher`, `repo`, and\n * `category` straight through and using `source = \"store://<id>\"`; per-file\n * `source` paths are not available for remote assets so the canonical ref is\n * a stable placeholder.\n */\nasync function storeAssets(projectDir: string): Promise<{\n entries: CatalogEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n try {\n const { resolveCatalogSource } = await import(\"./open-library.ts\");\n const { source, close } = await resolveCatalogSource({ projectDir });\n try {\n const assets = await source.listAssets();\n const entries: CatalogEntry[] = assets.map((a) => ({\n name: a.name,\n kind: (a.kind as AssetKind) ?? \"skill\",\n description: a.description ?? \"\",\n source: `store://${a.id}`,\n publisher: a.publisher,\n repo: a.repo,\n domainSlug: a.domainSlug,\n category: a.category,\n version: a.version,\n requires: [],\n dependencies: [],\n }));\n // Domains are optional on the catalog source — only the store backend\n // (post Part 4 `catalog.listDomains`) exposes them. Duck-type the call so\n // older sources and the local catalog degrade to no domains, not an error.\n const domains = await loadDomains(source);\n return { entries, domains, notes };\n } finally {\n close();\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // Local-only catalog throws a recognizable message — silence it; absence\n // is the user's choice, not an error.\n if (!/local/i.test(msg)) {\n notes.push({ feed: \"store\", message: msg });\n }\n return { entries: [], domains: [], notes };\n }\n}\n\n/** Optional `listDomains` capability — returns `[]` when the source lacks it. */\nasync function loadDomains(source: unknown): Promise<CatalogDomain[]> {\n const fn = (source as { listDomains?: () => Promise<CatalogDomain[]> }).listDomains;\n if (typeof fn !== \"function\") return [];\n const domains = await fn.call(source);\n return Array.isArray(domains) ? domains : [];\n}\n\n/** The slice of `AssetManager` the source feed needs (kept narrow for tests). */\ninterface AssetManagerLike {\n search: () => Promise<CatalogEntry[]>;\n searchRepoRoots: () => { repoName: string; root: string }[];\n}\n\n/**\n * Project-source feed: leans on `AssetManager.search()`, which now merges\n * `skaile.yaml`'s `sources:` field into the repositories map, then stamps\n * `DOMAIN.md`-derived domain grouping onto the scanned entries via\n * `searchRepoRoots()`.\n */\nasync function sourceAssets(\n am: AssetManagerLike,\n): Promise<{ entries: OriginEntry[]; domains: CatalogDomain[] }> {\n // The bundled factory tree is injected into the repositories map as an implicit\n // source, so `am.search()` returns it too. It has its own `factory` origin band,\n // so drop it here — otherwise the same first-party skills list twice under Sources.\n const factoryPrefixes = factoryAssetRoots().map((r) => (r.endsWith(path.sep) ? r : r + path.sep));\n const isFactoryPath = (p: string) =>\n factoryPrefixes.some((fp) => p === fp.slice(0, -1) || p.startsWith(fp));\n\n const all = (await am.search()).filter((e) => !isFactoryPath(e.source));\n const entries: OriginEntry[] = [];\n const domains: CatalogDomain[] = [];\n // Group each source's entries under the source node (its short repo name), not\n // the publisher: under Sources the navigational question is \"which repo did\n // this come from\". Each entry's `source` (absolute manifest path) lives under\n // exactly one declared root, so group + stamp by prefix. The cache slug is\n // URL-keyed `github.com/<org>/<repo>` — take the trailing segment.\n for (const { repoName, root } of am.searchRepoRoots()) {\n if (isFactoryPath(root)) continue;\n const prefix = root.endsWith(path.sep) ? root : root + path.sep;\n const owned = all.filter((e) => e.source === root || e.source.startsWith(prefix));\n if (owned.length === 0) continue;\n const sourceName = repoName.split(\"/\").pop() || repoName;\n // No synthetic fallback domain — the source node already groups these, so a\n // same-named fallback would just nest the source under itself.\n domains.push(...(await stampDomains(root, sourceName, owned, false)));\n for (const entry of owned) entries.push({ origin: \"source\", entry, group: sourceName });\n }\n return { entries, domains };\n}\n\n/**\n * Factory-asset feed: scan the bundled first-party `factory-assets` tree(s)\n * resolved by `factoryAssetRoots()` (the `SKAILE_FACTORY_ASSETS_DIR` override\n * layer ahead of the bundled tree). Local disk only, no network — the entries are\n * the built-in skills / connectors / bundles that ship with `@skaile/workspaces`.\n * `factoryAssetRoots()` already dedupes, so the env override pointing at the\n * bundled dir collapses to one root.\n */\nasync function factoryAssets(): Promise<{\n entries: CatalogEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const entries: CatalogEntry[] = [];\n for (const root of factoryAssetRoots()) {\n const rootEntries = [...(await scanRepo(root, \"skaile-factory\"))];\n domains.push(...(await stampDomains(root, \"skaile-factory\", rootEntries)));\n entries.push(...rootEntries);\n }\n return { entries, domains, notes };\n } catch (err) {\n notes.push({\n feed: \"factory\",\n message: err instanceof Error ? err.message : String(err),\n });\n return { entries: [], domains, notes };\n }\n}\n\n/** Tag a flat entry list with a single origin. */\nfunction tag(origin: AssetOrigin, entries: CatalogEntry[]): OriginEntry[] {\n return entries.map((entry) => ({ origin, entry }));\n}\n\n/**\n * Run all feeds and return a single normalized result.\n *\n * Feeds run in parallel where possible — the store fetch is the slow one\n * (one HTTP call) and is fired alongside the local library walk. The factory\n * scan is local disk only, so it runs inline.\n */\nexport async function gatherAssetFeeds(\n am: AssetManagerLike,\n projectDir: string,\n): Promise<AssetFeedResult> {\n const [libs, store] = await Promise.all([libraryAssets(), storeAssets(projectDir)]);\n const factory = await factoryAssets();\n const src = await sourceAssets(am);\n return {\n entries: [\n ...src.entries,\n ...libs.entries,\n ...tag(\"factory\", factory.entries),\n ...tag(\"store\", store.entries),\n ],\n // Store domains first (server-curated), then the on-disk feeds; dedupe by\n // slug, first-wins, so a store-served domain shadows a same-slug local one.\n domains: dedupeBySlug([...store.domains, ...src.domains, ...libs.domains, ...factory.domains]),\n notes: [...libs.notes, ...store.notes, ...factory.notes],\n };\n}\n\n/** First-wins dedupe of a domain list by `slug`. */\nfunction dedupeBySlug(domains: CatalogDomain[]): CatalogDomain[] {\n const seen = new Set<string>();\n const out: CatalogDomain[] = [];\n for (const d of domains) {\n if (seen.has(d.slug)) continue;\n seen.add(d.slug);\n out.push(d);\n }\n return out;\n}\n"]}
1
+ {"version":3,"sources":["../cli/src/asset-feeds.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqFA,SAAS,QAAQ,CAAA,EAAmB;AAClC,EAAA,OAAO,CAAA,CAAE,KAAA,CAAW,IAAA,CAAA,GAAG,CAAA,CAAE,KAAK,GAAG,CAAA;AACnC;AAGA,eAAe,cAAc,IAAA,EAAmC;AAC9D,EAAA,MAAM,MAAkB,EAAC;AACzB,EAAA,WAAA,MAAiB,EAAE,IAAA,EAAM,QAAA,EAAU,MAAK,IAAK,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC1D,IAAA,IAAI,SAAS,kBAAA,EAAoB;AACjC,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,IAAA,EAAK,GAAI,iBAAiB,YAAA,CAAa,QAAA,EAAU,MAAM,CAAC,CAAA;AAChE,MAAA,GAAA,CAAI,IAAA,CAAK,cAAc,OAAA,CAAa,IAAA,CAAA,QAAA,CAAS,MAAM,QAAQ,CAAC,CAAA,EAAG,IAAI,CAAC,CAAA;AAAA,IACtE,CAAA,CAAA,MAAQ;AAAA,IAAC;AAAA,EACX;AACA,EAAA,OAAO,GAAA;AACT;AAcA,eAAe,YAAA,CACb,IAAA,EACA,YAAA,EACA,OAAA,EACA,qBAAqB,IAAA,EACK;AAC1B,EAAA,MAAM,SAAA,GAAY,MAAM,aAAA,CAAc,IAAI,CAAA;AAC1C,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,IAC1C,KAAA;AAAA,IACA,KAAK,CAAA,EAAG,KAAA,CAAM,IAAI,CAAA,CAAA,EAAI,MAAM,IAAI,CAAA,CAAA;AAAA,IAChC,cAAc,OAAA,CAAa,IAAA,CAAA,QAAA,CAAS,IAAA,EAAM,KAAA,CAAM,MAAM,CAAC;AAAA,GACzD,CAAE,CAAA;AAEF,EAAA,IAAI,YAAA,GAAe,KAAA;AACnB,EAAA,KAAA,MAAW,KAAK,WAAA,EAAa;AAC3B,IAAA,MAAM,IAAA,GAAO,YAAA,CAAa,CAAA,CAAE,YAAA,EAAc,SAAS,CAAA;AACnD,IAAA,IAAI,IAAA,EAAM,CAAA,CAAE,KAAA,CAAM,UAAA,GAAa,IAAA;AAAA,SAAA,IACtB,kBAAA,EAAoB;AAC3B,MAAA,CAAA,CAAE,MAAM,UAAA,GAAa,YAAA;AACrB,MAAA,YAAA,GAAe,IAAA;AAAA,IACjB,CAAA,MAAO,CAAA,CAAE,KAAA,CAAM,UAAA,GAAa,EAAA;AAAA,EAC9B;AAEA,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,gBAAA,CAAiB,aAAa,SAAS,CAAA;AACzD,EAAA,MAAM,UAA2B,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,IAChE,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,GAAI,EAAE,WAAA,GAAc,EAAE,aAAa,CAAA,CAAE,WAAA,KAAgB,EAAC;AAAA,IACtD,GAAI,EAAE,QAAA,GAAW,EAAE,UAAU,CAAA,CAAE,QAAA,KAAa,EAAC;AAAA,IAC7C,GAAI,EAAE,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA,EAAM,GAAI,EAAC;AAAA,IAClD,GAAI,CAAA,CAAE,WAAA,CAAY,CAAC,CAAA,GAAI,EAAE,cAAA,EAAgB,CAAA,CAAE,WAAA,CAAY,CAAC,CAAA,EAAE,GAAI;AAAC,GACjE,CAAE,CAAA;AACF,EAAA,IAAI,YAAA,UAAsB,IAAA,CAAK,EAAE,MAAM,YAAA,EAAc,KAAA,EAAO,cAAc,CAAA;AAC1E,EAAA,OAAO,OAAA;AACT;AAUA,eAAe,aAAA,GAIZ;AACD,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,UAA2B,EAAC;AAClC,EAAA,IAAI;AACF,IAAA,MAAM,CAAC,EAAE,kBAAA,EAAmB,EAAG,EAAE,aAAA,EAAc,EAAG,EAAE,iBAAA,EAAmB,CAAA,GAAI,MAAM,QAAQ,GAAA,CAAI;AAAA,MAC3F,OAAO,4BAAmB,CAAA;AAAA,MAC1B,OAAO,oBAA4B,CAAA;AAAA,MACnC,OAAO,iBAAyB;AAAA,KACjC,CAAA;AACD,IAAA,MAAM,EAAE,OAAA,EAAS,KAAA,EAAM,GAAI,MAAM,kBAAA,EAAmB;AACpD,IAAA,IAAI;AAIF,MAAA,MAAM,UAAU,iBAAA,EAAkB;AAClC,MAAA,MAAM,UAAA,GAAkB,IAAA,CAAA,IAAA,CAAK,aAAA,EAAc,EAAG,SAAS,CAAA;AACvD,MAAA,MAAM,IAAA,GAAA,CAAQ,MAAM,OAAA,CAAQ,aAAA,EAAc,EAAG,MAAA;AAAA,QAC3C,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA,IAAK,CAAC,CAAA,CAAE,IAAA,CAAK,UAAA,CAAW,UAAU;AAAA,OACrE;AACA,MAAA,MAAM,UAAyB,EAAC;AAChC,MAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,QAAA,IAAI,CAAC,UAAA,CAAW,GAAA,CAAI,IAAI,CAAA,EAAG;AAI3B,QAAA,MAAM,UAAA,GAAa,CAAC,GAAI,MAAM,SAAS,GAAA,CAAI,IAAA,EAAM,GAAA,CAAI,IAAI,CAAE,CAAA;AAC3D,QAAA,OAAA,CAAQ,IAAA,CAAK,GAAI,MAAM,YAAA,CAAa,GAAA,CAAI,MAAM,GAAA,CAAI,IAAA,EAAM,UAAA,EAAY,KAAK,CAAE,CAAA;AAC3E,QAAA,KAAA,MAAW,KAAA,IAAS,UAAA,EAAY,OAAA,CAAQ,IAAA,CAAK,EAAE,MAAA,EAAQ,SAAA,EAAW,KAAA,EAAO,KAAA,EAAO,GAAA,CAAI,IAAA,EAAM,CAAA;AAAA,MAC5F;AACA,MAAA,OAAO,EAAE,OAAA,EAAS,OAAA,EAAS,KAAA,EAAM;AAAA,IACnC,CAAA,SAAE;AACA,MAAA,KAAA,EAAM;AAAA,IACR;AAAA,EACF,SAAS,GAAA,EAAK;AACZ,IAAA,KAAA,CAAM,IAAA,CAAK;AAAA,MACT,IAAA,EAAM,WAAA;AAAA,MACN,SAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG;AAAA,KACzD,CAAA;AACD,IAAA,OAAO,EAAE,OAAA,EAAS,EAAC,EAAG,SAAS,KAAA,EAAM;AAAA,EACvC;AACF;AAYA,eAAe,YAAY,UAAA,EAIxB;AACD,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,oBAAA,EAAqB,GAAI,MAAM,OAAO,4BAAmB,CAAA;AACjE,IAAA,MAAM,EAAE,QAAQ,KAAA,EAAM,GAAI,MAAM,oBAAA,CAAqB,EAAE,YAAY,CAAA;AACnE,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,UAAA,EAAW;AACvC,MAAA,MAAM,OAAA,GAA0B,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,QACjD,MAAM,CAAA,CAAE,IAAA;AAAA,QACR,IAAA,EAAO,EAAE,IAAA,IAAsB,OAAA;AAAA,QAC/B,WAAA,EAAa,EAAE,WAAA,IAAe,EAAA;AAAA,QAC9B,MAAA,EAAQ,CAAA,QAAA,EAAW,CAAA,CAAE,EAAE,CAAA,CAAA;AAAA,QACvB,WAAW,CAAA,CAAE,SAAA;AAAA,QACb,MAAM,CAAA,CAAE,IAAA;AAAA,QACR,YAAY,CAAA,CAAE,UAAA;AAAA,QACd,UAAU,CAAA,CAAE,QAAA;AAAA,QACZ,SAAS,CAAA,CAAE,OAAA;AAAA,QACX,UAAU,EAAC;AAAA,QACX,cAAc;AAAC,OACjB,CAAE,CAAA;AAIF,MAAA,MAAM,OAAA,GAAU,MAAM,WAAA,CAAY,MAAM,CAAA;AACxC,MAAA,OAAO,EAAE,OAAA,EAAS,OAAA,EAAS,KAAA,EAAM;AAAA,IACnC,CAAA,SAAE;AACA,MAAA,KAAA,EAAM;AAAA,IACR;AAAA,EACF,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,MAAM,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAG3D,IAAA,IAAI,CAAC,QAAA,CAAS,IAAA,CAAK,GAAG,CAAA,EAAG;AACvB,MAAA,KAAA,CAAM,KAAK,EAAE,IAAA,EAAM,OAAA,EAAS,OAAA,EAAS,KAAK,CAAA;AAAA,IAC5C;AACA,IAAA,OAAO,EAAE,OAAA,EAAS,IAAI,OAAA,EAAS,IAAI,KAAA,EAAM;AAAA,EAC3C;AACF;AAGA,eAAe,YAAY,MAAA,EAA2C;AACpE,EAAA,MAAM,KAAM,MAAA,CAA4D,WAAA;AACxE,EAAA,IAAI,OAAO,EAAA,KAAO,UAAA,EAAY,OAAO,EAAC;AACtC,EAAA,MAAM,OAAA,GAAU,MAAM,EAAA,CAAG,IAAA,CAAK,MAAM,CAAA;AACpC,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,GAAI,UAAU,EAAC;AAC7C;AAcA,eAAe,aACb,EAAA,EAC+D;AAI/D,EAAA,MAAM,eAAA,GAAkB,iBAAA,EAAkB,CAAE,GAAA,CAAI,CAAC,CAAA,KAAO,CAAA,CAAE,QAAA,CAAc,IAAA,CAAA,GAAG,CAAA,GAAI,CAAA,GAAI,CAAA,GAAS,IAAA,CAAA,GAAI,CAAA;AAChG,EAAA,MAAM,gBAAgB,CAAC,CAAA,KACrB,eAAA,CAAgB,IAAA,CAAK,CAAC,EAAA,KAAO,CAAA,KAAM,EAAA,CAAG,KAAA,CAAM,GAAG,EAAE,CAAA,IAAK,CAAA,CAAE,UAAA,CAAW,EAAE,CAAC,CAAA;AAExE,EAAA,MAAM,GAAA,GAAA,CAAO,MAAM,EAAA,CAAG,MAAA,EAAO,EAAG,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,aAAA,CAAc,CAAA,CAAE,MAAM,CAAC,CAAA;AACtE,EAAA,MAAM,UAAyB,EAAC;AAChC,EAAA,MAAM,UAA2B,EAAC;AAMlC,EAAA,KAAA,MAAW,EAAE,QAAA,EAAU,IAAA,EAAK,IAAK,EAAA,CAAG,iBAAgB,EAAG;AACrD,IAAA,IAAI,aAAA,CAAc,IAAI,CAAA,EAAG;AACzB,IAAA,MAAM,SAAS,IAAA,CAAK,QAAA,CAAc,IAAA,CAAA,GAAG,CAAA,GAAI,OAAO,IAAA,GAAY,IAAA,CAAA,GAAA;AAC5D,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,IAAA,IAAQ,CAAA,CAAE,MAAA,CAAO,UAAA,CAAW,MAAM,CAAC,CAAA;AAChF,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACxB,IAAA,MAAM,aAAa,QAAA,CAAS,KAAA,CAAM,GAAG,CAAA,CAAE,KAAI,IAAK,QAAA;AAGhD,IAAA,OAAA,CAAQ,IAAA,CAAK,GAAI,MAAM,YAAA,CAAa,MAAM,UAAA,EAAY,KAAA,EAAO,KAAK,CAAE,CAAA;AACpE,IAAA,KAAA,MAAW,KAAA,IAAS,KAAA,EAAO,OAAA,CAAQ,IAAA,CAAK,EAAE,QAAQ,QAAA,EAAU,KAAA,EAAO,KAAA,EAAO,UAAA,EAAY,CAAA;AAAA,EACxF;AACA,EAAA,OAAO,EAAE,SAAS,OAAA,EAAQ;AAC5B;AAUA,eAAe,aAAA,GAIZ;AACD,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,UAA2B,EAAC;AAClC,EAAA,IAAI;AACF,IAAA,MAAM,UAA0B,EAAC;AACjC,IAAA,KAAA,MAAW,IAAA,IAAQ,mBAAkB,EAAG;AACtC,MAAA,MAAM,cAAc,CAAC,GAAI,MAAM,QAAA,CAAS,IAAA,EAAM,gBAAgB,CAAE,CAAA;AAChE,MAAA,OAAA,CAAQ,KAAK,GAAI,MAAM,aAAa,IAAA,EAAM,gBAAA,EAAkB,WAAW,CAAE,CAAA;AACzE,MAAA,OAAA,CAAQ,IAAA,CAAK,GAAG,WAAW,CAAA;AAAA,IAC7B;AACA,IAAA,OAAO,EAAE,OAAA,EAAS,OAAA,EAAS,KAAA,EAAM;AAAA,EACnC,SAAS,GAAA,EAAK;AACZ,IAAA,KAAA,CAAM,IAAA,CAAK;AAAA,MACT,IAAA,EAAM,SAAA;AAAA,MACN,SAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG;AAAA,KACzD,CAAA;AACD,IAAA,OAAO,EAAE,OAAA,EAAS,EAAC,EAAG,SAAS,KAAA,EAAM;AAAA,EACvC;AACF;AAGA,SAAS,GAAA,CAAI,QAAqB,OAAA,EAAwC;AACxE,EAAA,OAAO,QAAQ,GAAA,CAAI,CAAC,WAAW,EAAE,MAAA,EAAQ,OAAM,CAAE,CAAA;AACnD;AASA,eAAsB,gBAAA,CACpB,IACA,UAAA,EAC0B;AAC1B,EAAA,MAAM,CAAC,IAAA,EAAM,KAAK,CAAA,GAAI,MAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,aAAA,EAAc,EAAG,WAAA,CAAY,UAAU,CAAC,CAAC,CAAA;AAClF,EAAA,MAAM,OAAA,GAAU,MAAM,aAAA,EAAc;AACpC,EAAA,MAAM,GAAA,GAAM,MAAM,YAAA,CAAa,EAAE,CAAA;AACjC,EAAA,OAAO;AAAA,IACL,OAAA,EAAS;AAAA,MACP,GAAG,GAAA,CAAI,OAAA;AAAA,MACP,GAAG,IAAA,CAAK,OAAA;AAAA,MACR,GAAG,GAAA,CAAI,SAAA,EAAW,OAAA,CAAQ,OAAO,CAAA;AAAA,MACjC,GAAG,GAAA,CAAI,OAAA,EAAS,KAAA,CAAM,OAAO;AAAA,KAC/B;AAAA;AAAA;AAAA,IAGA,OAAA,EAAS,YAAA,CAAa,CAAC,GAAG,MAAM,OAAA,EAAS,GAAG,GAAA,CAAI,OAAA,EAAS,GAAG,IAAA,CAAK,OAAA,EAAS,GAAG,OAAA,CAAQ,OAAO,CAAC,CAAA;AAAA,IAC7F,KAAA,EAAO,CAAC,GAAG,IAAA,CAAK,KAAA,EAAO,GAAG,KAAA,CAAM,KAAA,EAAO,GAAG,OAAA,CAAQ,KAAK;AAAA,GACzD;AACF;AAGA,SAAS,aAAa,OAAA,EAA2C;AAC/D,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,MAAM,MAAuB,EAAC;AAC9B,EAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,IAAI,CAAA,EAAG;AACtB,IAAA,IAAA,CAAK,GAAA,CAAI,EAAE,IAAI,CAAA;AACf,IAAA,GAAA,CAAI,KAAK,CAAC,CAAA;AAAA,EACZ;AACA,EAAA,OAAO,GAAA;AACT","file":"asset-feeds-YB5ROSAE.js","sourcesContent":["/**\n * asset-feeds.ts — gather installable assets from every feed the manage TUI\n * surfaces.\n *\n * Four feeds, normalized into `CatalogEntry`:\n * - **Project sources** — `skaile.yaml` `sources:` entries (scanned via\n * `AssetManager.search()`; the new `sources:` field is merged into the\n * legacy repositories map in `AssetManager.loadConfig`).\n * - **Local libraries** — user authoring places from `LocalIndex` (filesystem\n * scan of each library's `path`).\n * - **Factory assets** — the bundled first-party `factory-assets` tree\n * (`factoryAssetRoots()`), scanned from disk. Always present; no network.\n * - **Remote store** — the configured catalog source when `catalog.url` is\n * not `local`. Failures are non-fatal — they surface as notes.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n type AssetKind,\n type CatalogDomain,\n type CatalogEntry,\n factoryAssetRoots,\n parseFrontmatter,\n scanRepo,\n walkDir,\n} from \"@skaile/workspaces/core\";\nimport {\n assignDomain,\n buildDomainIndex,\n DOMAIN_MD_FILENAME,\n type DomainMd,\n parseDomainMd,\n} from \"@skaile/workspaces/discovery\";\n\n/** One non-fatal note returned to the caller for surfacing in the UI. */\nexport interface FeedNote {\n feed: \"sources\" | \"libraries\" | \"store\" | \"factory\";\n message: string;\n}\n\n/**\n * Which feed an asset came from. This is the top-level grouping axis in the\n * manage TUI (\"where is this skill coming from?\") and is intentionally distinct\n * from `publisher` (asset-intrinsic identity): the same publisher can legitimately\n * appear under more than one origin (e.g. a github repo you added as a `source`\n * that is also listed in the remote `store`).\n */\nexport type AssetOrigin = \"source\" | \"library\" | \"factory\" | \"store\";\n\n/** A catalog entry tagged with the feed it was gathered from. */\nexport interface OriginEntry {\n origin: AssetOrigin;\n entry: CatalogEntry;\n /**\n * Second-level grouping label for the manage tree. Defaults (when unset) to the\n * entry's `publisher`; the source feed sets it to the source's short repo name\n * so the Sources band groups by source — \"which repo did this come from\" — not\n * by the asset-intrinsic publisher.\n */\n group?: string;\n}\n\nexport interface AssetFeedResult {\n entries: OriginEntry[];\n /**\n * Display-only domain metadata, keyed (by the consumer) on `slug`. Populated\n * from the store's `catalog.listDomains` when the connected catalog source\n * exposes it; empty otherwise. The manage TUI joins `entry.domainSlug` → a\n * domain for grouping headers, descriptions, and relation badges.\n */\n domains: CatalogDomain[];\n notes: FeedNote[];\n}\n\n// ── Domain stamping (on-disk feeds) ──────────────────────────────────────────\n//\n// `scanRepo` (the source / library / factory scan path) does not read the\n// `DOMAIN.md` sidecars, so its entries arrive with no `domainSlug` and render\n// flat under their publisher band. We re-derive the grouping here from the\n// root's `DOMAIN.md` files (the same sidecars the remote store computes\n// server-side), and fall back to the repo/source name for any entry no domain\n// claims so it still groups instead of landing in the synthetic \"no domain\" pile.\n\n/** Repo-relative POSIX path (DOMAIN.md slugs are matched whole-segment, `/`-joined). */\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n\n/** Parse every `DOMAIN.md` sidecar under `root` into a {@link DomainMd}. */\nasync function loadDomainMds(root: string): Promise<DomainMd[]> {\n const out: DomainMd[] = [];\n for await (const { path: filePath, name } of walkDir(root)) {\n if (name !== DOMAIN_MD_FILENAME) continue;\n try {\n const { data } = parseFrontmatter(readFileSync(filePath, \"utf8\"));\n out.push(parseDomainMd(toPosix(path.relative(root, filePath)), data));\n } catch {}\n }\n return out;\n}\n\n/**\n * Stamp `domainSlug` onto every entry scanned from `root` using the root's\n * `DOMAIN.md` sidecars. Returns the `CatalogDomain` registry for the real\n * domains, plus a synthetic fallback domain when any entry used the fallback.\n * Mutates `entries` in place (sets `entry.domainSlug`).\n *\n * `synthesizeFallback` (default true): entries no domain claims fall back to\n * `fallbackSlug` (the repo/source name). Pass `false` when the caller already\n * groups these entries one level up (e.g. the source feed groups by source\n * node) — unmatched entries then get no domain (`\"\"`) and hang directly under\n * that parent, instead of nesting under a same-named fallback domain.\n */\nasync function stampDomains(\n root: string,\n fallbackSlug: string,\n entries: CatalogEntry[],\n synthesizeFallback = true,\n): Promise<CatalogDomain[]> {\n const domainMds = await loadDomainMds(root);\n const assignables = entries.map((entry) => ({\n entry,\n ref: `${entry.kind}:${entry.name}`,\n relativePath: toPosix(path.relative(root, entry.source)),\n }));\n\n let usedFallback = false;\n for (const a of assignables) {\n const slug = assignDomain(a.relativePath, domainMds);\n if (slug) a.entry.domainSlug = slug;\n else if (synthesizeFallback) {\n a.entry.domainSlug = fallbackSlug;\n usedFallback = true;\n } else a.entry.domainSlug = \"\";\n }\n\n const { index } = buildDomainIndex(assignables, domainMds);\n const catalog: CatalogDomain[] = Object.values(index).map((d) => ({\n slug: d.slug,\n title: d.title,\n ...(d.description ? { description: d.description } : {}),\n ...(d.relation ? { relation: d.relation } : {}),\n ...(d.order !== undefined ? { order: d.order } : {}),\n ...(d.recommended[0] ? { recommendedRef: d.recommended[0] } : {}),\n }));\n if (usedFallback) catalog.push({ slug: fallbackSlug, title: fallbackSlug });\n return catalog;\n}\n\n/**\n * Pull assets from local libraries (the authoring places under\n * `~/.skaile/libraries/<name>/`, not the `~/.skaile/cache/sources/` cache). Each\n * library's `path` is scanned with `scanRepo` and grouped under the bare library\n * name (`group: <name>`) so the manage TUI shows a clean `<name>` band — no\n * `library:` prefix and no redundant same-named fallback domain (mirrors the\n * source feed).\n */\nasync function libraryAssets(): Promise<{\n entries: OriginEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const [{ openLibraryManager }, { skaileHomeDir }, { getGlobalCacheDir }] = await Promise.all([\n import(\"./open-library.ts\"),\n import(\"@skaile/workspaces/library\"),\n import(\"@skaile/workspaces/core\"),\n ]);\n const { manager, close } = await openLibraryManager();\n try {\n // Authoring libraries are the rows that are NOT source clones. index.db\n // rows predating the cache migration still carry the legacy\n // ~/.skaile/sources path, so exclude both roots.\n const newRoot = getGlobalCacheDir();\n const legacyRoot = path.join(skaileHomeDir(), \"sources\");\n const libs = (await manager.listLibraries()).filter(\n (l) => !l.path.startsWith(newRoot) && !l.path.startsWith(legacyRoot),\n );\n const entries: OriginEntry[] = [];\n for (const lib of libs) {\n if (!existsSync(lib.path)) continue;\n // Scan + group under the bare library name. `synthesizeFallback: false`\n // (as the source feed does) keeps domainless assets directly under the\n // library band instead of a redundant same-named fallback domain.\n const libEntries = [...(await scanRepo(lib.path, lib.name))];\n domains.push(...(await stampDomains(lib.path, lib.name, libEntries, false)));\n for (const entry of libEntries) entries.push({ origin: \"library\", entry, group: lib.name });\n }\n return { entries, domains, notes };\n } finally {\n close();\n }\n } catch (err) {\n notes.push({\n feed: \"libraries\",\n message: err instanceof Error ? err.message : String(err),\n });\n return { entries: [], domains, notes };\n }\n}\n\n/**\n * Pull a (best-effort) snapshot of the connected remote store catalog.\n *\n * - Returns no entries when the resolved catalog is local-only or the host\n * is unreachable (the failure becomes a note, not an exception).\n * - Maps `CatalogAsset` → `CatalogEntry`, carrying `publisher`, `repo`, and\n * `category` straight through and using `source = \"store://<id>\"`; per-file\n * `source` paths are not available for remote assets so the canonical ref is\n * a stable placeholder.\n */\nasync function storeAssets(projectDir: string): Promise<{\n entries: CatalogEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n try {\n const { resolveCatalogSource } = await import(\"./open-library.ts\");\n const { source, close } = await resolveCatalogSource({ projectDir });\n try {\n const assets = await source.listAssets();\n const entries: CatalogEntry[] = assets.map((a) => ({\n name: a.name,\n kind: (a.kind as AssetKind) ?? \"skill\",\n description: a.description ?? \"\",\n source: `store://${a.id}`,\n publisher: a.publisher,\n repo: a.repo,\n domainSlug: a.domainSlug,\n category: a.category,\n version: a.version,\n requires: [],\n dependencies: [],\n }));\n // Domains are optional on the catalog source — only the store backend\n // (post Part 4 `catalog.listDomains`) exposes them. Duck-type the call so\n // older sources and the local catalog degrade to no domains, not an error.\n const domains = await loadDomains(source);\n return { entries, domains, notes };\n } finally {\n close();\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // Local-only catalog throws a recognizable message — silence it; absence\n // is the user's choice, not an error.\n if (!/local/i.test(msg)) {\n notes.push({ feed: \"store\", message: msg });\n }\n return { entries: [], domains: [], notes };\n }\n}\n\n/** Optional `listDomains` capability — returns `[]` when the source lacks it. */\nasync function loadDomains(source: unknown): Promise<CatalogDomain[]> {\n const fn = (source as { listDomains?: () => Promise<CatalogDomain[]> }).listDomains;\n if (typeof fn !== \"function\") return [];\n const domains = await fn.call(source);\n return Array.isArray(domains) ? domains : [];\n}\n\n/** The slice of `AssetManager` the source feed needs (kept narrow for tests). */\ninterface AssetManagerLike {\n search: () => Promise<CatalogEntry[]>;\n searchRepoRoots: () => { repoName: string; root: string }[];\n}\n\n/**\n * Project-source feed: leans on `AssetManager.search()`, which now merges\n * `skaile.yaml`'s `sources:` field into the repositories map, then stamps\n * `DOMAIN.md`-derived domain grouping onto the scanned entries via\n * `searchRepoRoots()`.\n */\nasync function sourceAssets(\n am: AssetManagerLike,\n): Promise<{ entries: OriginEntry[]; domains: CatalogDomain[] }> {\n // The bundled factory tree is injected into the repositories map as an implicit\n // source, so `am.search()` returns it too. It has its own `factory` origin band,\n // so drop it here — otherwise the same first-party skills list twice under Sources.\n const factoryPrefixes = factoryAssetRoots().map((r) => (r.endsWith(path.sep) ? r : r + path.sep));\n const isFactoryPath = (p: string) =>\n factoryPrefixes.some((fp) => p === fp.slice(0, -1) || p.startsWith(fp));\n\n const all = (await am.search()).filter((e) => !isFactoryPath(e.source));\n const entries: OriginEntry[] = [];\n const domains: CatalogDomain[] = [];\n // Group each source's entries under the source node (its short repo name), not\n // the publisher: under Sources the navigational question is \"which repo did\n // this come from\". Each entry's `source` (absolute manifest path) lives under\n // exactly one declared root, so group + stamp by prefix. The cache slug is\n // URL-keyed `github.com/<org>/<repo>` — take the trailing segment.\n for (const { repoName, root } of am.searchRepoRoots()) {\n if (isFactoryPath(root)) continue;\n const prefix = root.endsWith(path.sep) ? root : root + path.sep;\n const owned = all.filter((e) => e.source === root || e.source.startsWith(prefix));\n if (owned.length === 0) continue;\n const sourceName = repoName.split(\"/\").pop() || repoName;\n // No synthetic fallback domain — the source node already groups these, so a\n // same-named fallback would just nest the source under itself.\n domains.push(...(await stampDomains(root, sourceName, owned, false)));\n for (const entry of owned) entries.push({ origin: \"source\", entry, group: sourceName });\n }\n return { entries, domains };\n}\n\n/**\n * Factory-asset feed: scan the bundled first-party `factory-assets` tree(s)\n * resolved by `factoryAssetRoots()` (the `SKAILE_FACTORY_ASSETS_DIR` override\n * layer ahead of the bundled tree). Local disk only, no network — the entries are\n * the built-in skills / connectors / bundles that ship with `@skaile/workspaces`.\n * `factoryAssetRoots()` already dedupes, so the env override pointing at the\n * bundled dir collapses to one root.\n */\nasync function factoryAssets(): Promise<{\n entries: CatalogEntry[];\n domains: CatalogDomain[];\n notes: FeedNote[];\n}> {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const entries: CatalogEntry[] = [];\n for (const root of factoryAssetRoots()) {\n const rootEntries = [...(await scanRepo(root, \"skaile-factory\"))];\n domains.push(...(await stampDomains(root, \"skaile-factory\", rootEntries)));\n entries.push(...rootEntries);\n }\n return { entries, domains, notes };\n } catch (err) {\n notes.push({\n feed: \"factory\",\n message: err instanceof Error ? err.message : String(err),\n });\n return { entries: [], domains, notes };\n }\n}\n\n/** Tag a flat entry list with a single origin. */\nfunction tag(origin: AssetOrigin, entries: CatalogEntry[]): OriginEntry[] {\n return entries.map((entry) => ({ origin, entry }));\n}\n\n/**\n * Run all feeds and return a single normalized result.\n *\n * Feeds run in parallel where possible — the store fetch is the slow one\n * (one HTTP call) and is fired alongside the local library walk. The factory\n * scan is local disk only, so it runs inline.\n */\nexport async function gatherAssetFeeds(\n am: AssetManagerLike,\n projectDir: string,\n): Promise<AssetFeedResult> {\n const [libs, store] = await Promise.all([libraryAssets(), storeAssets(projectDir)]);\n const factory = await factoryAssets();\n const src = await sourceAssets(am);\n return {\n entries: [\n ...src.entries,\n ...libs.entries,\n ...tag(\"factory\", factory.entries),\n ...tag(\"store\", store.entries),\n ],\n // Store domains first (server-curated), then the on-disk feeds; dedupe by\n // slug, first-wins, so a store-served domain shadows a same-slug local one.\n domains: dedupeBySlug([...store.domains, ...src.domains, ...libs.domains, ...factory.domains]),\n notes: [...libs.notes, ...store.notes, ...factory.notes],\n };\n}\n\n/** First-wins dedupe of a domain list by `slug`. */\nfunction dedupeBySlug(domains: CatalogDomain[]): CatalogDomain[] {\n const seen = new Set<string>();\n const out: CatalogDomain[] = [];\n for (const d of domains) {\n if (seen.has(d.slug)) continue;\n seen.add(d.slug);\n out.push(d);\n }\n return out;\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { AssetManager, FACTORY_SOURCE_URL, MissingManifestError, NON_DEPLOYABLE_KINDS, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, renderMcpServerConfig, softDepName } from '../chunk-3LIUBF5P.js';
1
+ export { AssetManager, FACTORY_SOURCE_URL, MissingManifestError, NON_DEPLOYABLE_KINDS, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, renderMcpServerConfig, softDepName } from '../chunk-7VQGUNXJ.js';
2
2
  import '../chunk-UFWADBG6.js';
3
3
  import '../chunk-HGPSZXZP.js';
4
4
  import '../chunk-VOEBLPXO.js';
@@ -1,13 +1,13 @@
1
1
  import { formatDroppedToolsNotice, filterInProcessMcpServerTools, checkToolPropertyKeys } from '../../chunk-X5O56UO4.js';
2
2
  import { scrubPoisonedTranscript, transcriptStoreForRuntime, resolveClaudeConfigDir, claudeEndpointMode, buildClaudeEndpointEnv } from '../../chunk-6S5KL76M.js';
3
3
  import { scopeReconfigureModel } from '../../chunk-TJRNJVGU.js';
4
- import { dispatchCapability } from '../../chunk-ERDK4ATM.js';
5
- import { fetchProviderModels } from '../../chunk-X5CW2HDM.js';
6
- import { AuthError, stripRunnerPrivateEnv, classifyClaudeSdkError, reclassifyAuthAsAccountUnavailable, AccountUnavailableError } from '../../chunk-BGY4KTU6.js';
4
+ import { dispatchCapability } from '../../chunk-FF5L6IV3.js';
5
+ import { fetchProviderModels } from '../../chunk-3TK7CZVL.js';
6
+ import { AuthError, stripRunnerPrivateEnv, classifyClaudeSdkError, reclassifyAuthAsAccountUnavailable, AccountUnavailableError, isLongContextEntitlementRejection, LONG_CONTEXT_CREDITS_API_ERROR } from '../../chunk-XBSQ4KMC.js';
7
7
  import { agentExecRequested, wrapAgentCommand, resolveAgentExec } from '../../chunk-YROR332M.js';
8
- import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger, entryCredentialValue, registryDriverConfig } from '../../chunk-H5EGBMZ5.js';
8
+ import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger, entryCredentialValue, registryDriverConfig } from '../../chunk-FAQRNZ44.js';
9
9
  import '../../chunk-6E6PKKAD.js';
10
- import { withActivityLabelShape } from '../../chunk-DJZRPPFJ.js';
10
+ import { withActivityLabelShape } from '../../chunk-MX7LCQUQ.js';
11
11
  import '../../chunk-UWU572Q6.js';
12
12
  import '../../chunk-4GY63JU6.js';
13
13
  import '../../chunk-5RHPNNWP.js';
@@ -255,6 +255,11 @@ function streamingSafeSanitize(text) {
255
255
 
256
256
  // bridge/src/drivers/claude-sdk.ts
257
257
  var zStatic = zNS.z ?? zNS.default ?? zNS;
258
+ function capabilityToolsKey(tools) {
259
+ return JSON.stringify(
260
+ [...tools].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map((t) => [t.name, t.description, t.parameters])
261
+ );
262
+ }
258
263
  function jsonSchemaToZodLoose(schema, z2) {
259
264
  const props = schema?.properties ?? null;
260
265
  if (!props || typeof props !== "object") return null;
@@ -351,6 +356,9 @@ function apiErrorExcerpt(text) {
351
356
  return masked.length > API_ERROR_EXCERPT_MAX ? `${masked.slice(0, API_ERROR_EXCERPT_MAX)}\u2026` : masked;
352
357
  }
353
358
  var STALE_RESUME_PATTERN = /No conversation found with session ID/i;
359
+ function isPoisonedHistoryError(message) {
360
+ return /invalid_request_error/i.test(message) && /media[_ ]?type|could not process image|image exceeds|cache_control/i.test(message);
361
+ }
354
362
  function snapshotProcessEnv() {
355
363
  const env = {};
356
364
  for (const [k, v] of Object.entries(process.env)) {
@@ -471,8 +479,11 @@ var ClaudeSdkDriver = class extends AgentDriver {
471
479
  * The SDK's own render of an upstream API error this turn (`error` on an
472
480
  * assistant message). An `is_error` result with an empty `errors[]` carries
473
481
  * nothing else, so without it a 404 logs exactly like routine noise.
482
+ * `longContext` marks the long-context entitlement 429 render.
474
483
  */
475
484
  lastApiErrorRender = null;
485
+ /** The long-context entitlement WARN was logged this attempt (once per attempt). */
486
+ longContextRejectionLogged = false;
476
487
  /** Active query — stays alive between turns (streaming input mode). */
477
488
  query = null;
478
489
  /** The block appended to the preset on the live query; finds it in a context report. */
@@ -511,6 +522,14 @@ var ClaudeSdkDriver = class extends AgentDriver {
511
522
  liveQueryConfig = null;
512
523
  /** A config change the resident query cannot take live: respawn at the next turn. */
513
524
  recycleForConfig = false;
525
+ /**
526
+ * The capability tool set the resident query's `skaile-capabilities` server
527
+ * was built from ({@link capabilityToolsKey}); `null` without capability hooks.
528
+ * The SDK fixes an MCP server's tool list at spawn, so a capability that
529
+ * registers later (a Skailify app's `app.*` set) is only callable once a
530
+ * respawn rebuilds the server — see {@link capabilityToolsChanged}.
531
+ */
532
+ liveCapabilityToolsKey = null;
514
533
  /** In-flight {@link syncLiveQueryConfig} chain, so switches apply in order. */
515
534
  liveConfigSync = null;
516
535
  /** {@link LIVE_SET_MODEL_TIMEOUT_MS}, as a field so tests can shorten it. */
@@ -564,6 +583,11 @@ var ClaudeSdkDriver = class extends AgentDriver {
564
583
  * {@link authSelfHealUsed}, for the "No conversation found" fresh-session retry.
565
584
  */
566
585
  staleResumeRetryUsed = false;
586
+ /**
587
+ * Mirror of the in-flight turn's poisoned-transcript scrub budget — same
588
+ * contract as {@link authSelfHealUsed}.
589
+ */
590
+ poisonScrubUsed = false;
567
591
  /**
568
592
  * Set when THIS attempt saw an upstream usage-limit rejection: either the
569
593
  * SDK's `rate_limit_event` telemetry with `status: "rejected"`, or an error
@@ -859,9 +883,11 @@ var ClaudeSdkDriver = class extends AgentDriver {
859
883
  this.authSelfHealUsed = _budget.auth;
860
884
  this.limitFailoverUsed = _budget.limit;
861
885
  this.staleResumeRetryUsed = _budget.staleResume;
886
+ this.poisonScrubUsed = _budget.poison;
862
887
  this.limitRejectionSeen = false;
863
888
  this.limitTurnRejectedForFailover = false;
864
889
  this.providerResponseReported = false;
890
+ this.longContextRejectionLogged = false;
865
891
  this.lastLimitInfo = null;
866
892
  this.deferredTurnError = null;
867
893
  let retrying = false;
@@ -880,7 +906,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
880
906
  this.turnCompleted = false;
881
907
  this.disarmErrorResultGrace();
882
908
  if (this.liveConfigSync || this.liveConfigDrifted()) await this.syncLiveQueryConfig();
883
- if (this.query && (this.inputStream == null || this.inputStream.ended || this.recycleForConfig || this.shouldRecycleQuery())) {
909
+ if (this.query && (this.inputStream == null || this.inputStream.ended || this.recycleForConfig || this.shouldRecycleQuery() || this.capabilityToolsChanged())) {
884
910
  this.recycleQueryForNextTurn();
885
911
  }
886
912
  const turnPromise = new Promise((resolve, reject) => {
@@ -917,7 +943,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
917
943
  retrying = true;
918
944
  return this.runPrompt(message, { ..._budget, staleResume: true });
919
945
  }
920
- const isPoisonedHistory = !_budget.poison && /invalid_request_error/i.test(errMsg) && /media[_ ]?type|could not process image|image exceeds|cache_control/i.test(errMsg);
946
+ const isPoisonedHistory = !_budget.poison && isPoisonedHistoryError(errMsg);
921
947
  const poisonSessionId = this.config.resumeSessionId || this.sessionId;
922
948
  if (isPoisonedHistory && poisonSessionId) {
923
949
  if (await this.attemptPoisonScrubRetry(poisonSessionId)) {
@@ -948,6 +974,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
948
974
  return;
949
975
  }
950
976
  }
977
+ if (this.deferredTurnError) this.emitDeferredTurnError(this.deferredTurnError);
951
978
  throw err;
952
979
  } finally {
953
980
  if (!retrying) {
@@ -1098,6 +1125,30 @@ var ClaudeSdkDriver = class extends AgentDriver {
1098
1125
  this.inputStream = null;
1099
1126
  }
1100
1127
  }
1128
+ /**
1129
+ * Whether the registry's capability tool set differs from the one the
1130
+ * resident query was spawned with. A mid-session `capability_register` only
1131
+ * reaches the agent through a respawn, which resumes the same SDK session.
1132
+ */
1133
+ capabilityToolsChanged() {
1134
+ const hooks = this.config.capabilities;
1135
+ if (!this.query || !hooks) return false;
1136
+ let current;
1137
+ try {
1138
+ current = capabilityToolsKey(hooks.composeTools());
1139
+ } catch {
1140
+ return false;
1141
+ }
1142
+ if (current === this.liveCapabilityToolsKey) return false;
1143
+ const names = (key) => key ? JSON.parse(key).map(([name]) => name) : [];
1144
+ const before = new Set(names(this.liveCapabilityToolsKey));
1145
+ const after = new Set(names(current));
1146
+ this.log.info("capability tool set changed; recycling the resident query", {
1147
+ added: [...after].filter((n) => !before.has(n)),
1148
+ removed: [...before].filter((n) => !after.has(n))
1149
+ });
1150
+ return true;
1151
+ }
1101
1152
  /**
1102
1153
  * `maxTurns` counts agentic turns, but whether the CLI resets that count per
1103
1154
  * user turn or accumulates it across a resident streaming session is
@@ -1222,7 +1273,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
1222
1273
  this.emit("agent-event", {
1223
1274
  type: "error",
1224
1275
  error: "Claude Code session state was lost (likely a prior crash). Restarting with a fresh session \u2014 previous conversation context is not available.",
1225
- fatal: false
1276
+ fatal: false,
1277
+ // The prompt is replayed with no `agent_end` in between: not a turn end.
1278
+ retrying: true,
1279
+ retryReason: "stale_resume"
1226
1280
  });
1227
1281
  this.sessionId = "";
1228
1282
  this.hasSession = false;
@@ -1294,7 +1348,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
1294
1348
  this.emit("agent-event", {
1295
1349
  type: "error",
1296
1350
  error: `Recovered corrupt data in the conversation history (${repairs.join(", ")}). Retrying \u2014 earlier context is preserved.`,
1297
- fatal: false
1351
+ fatal: false,
1352
+ // The prompt is replayed with no `agent_end` in between: not a turn end.
1353
+ retrying: true,
1354
+ retryReason: "poison_scrub"
1298
1355
  });
1299
1356
  this.query = null;
1300
1357
  this.endInputStream();
@@ -1810,7 +1867,9 @@ var ClaudeSdkDriver = class extends AgentDriver {
1810
1867
  const dropped = [];
1811
1868
  const hooks = this.config.capabilities;
1812
1869
  if (!hooks) return { server: null, dropped };
1813
- const tools = hooks.composeTools().filter((tool) => {
1870
+ const composed = hooks.composeTools();
1871
+ this.liveCapabilityToolsKey = capabilityToolsKey(composed);
1872
+ const tools = composed.filter((tool) => {
1814
1873
  const reason = checkToolPropertyKeys(tool.parameters);
1815
1874
  if (!reason) return true;
1816
1875
  dropped.push({ server: "skaile-capabilities", tool: tool.name, reason });
@@ -1980,11 +2039,17 @@ var ClaudeSdkDriver = class extends AgentDriver {
1980
2039
  retryable: detail.retryable,
1981
2040
  ...this.upstreamErrorLogFields()
1982
2041
  });
2042
+ const seatCapacity = detail.category !== "auth" && isSeatCapacityFailure(detail);
2043
+ const longContext = this.noteLongContextRejection([error.message], detail, seatCapacity);
1983
2044
  if (detail.category === "auth") {
1984
2045
  this.reportProviderResponse({ status: 401 });
1985
- } else if (isSeatCapacityFailure(detail)) {
2046
+ } else if (seatCapacity) {
1986
2047
  this.limitRejectionSeen = true;
1987
- this.reportProviderResponse({ status: 429, ...this.lastLimitInfo ?? {} });
2048
+ this.reportProviderResponse({
2049
+ status: 429,
2050
+ ...this.lastLimitInfo ?? {},
2051
+ ...longContext ? { cause: "long-context-entitlement" } : {}
2052
+ });
1988
2053
  }
1989
2054
  const surfaced = detail.category === "auth" ? new AuthError(detail) : detail.category === "account" ? new AccountUnavailableError(detail) : error;
1990
2055
  this.failTurn(surfaced, detail);
@@ -2104,6 +2169,11 @@ var ClaudeSdkDriver = class extends AgentDriver {
2104
2169
  this.deferredTurnError = event;
2105
2170
  return;
2106
2171
  }
2172
+ if (hadPendingTurn && this.willAttemptPoisonScrub(err.message)) {
2173
+ this.log.info("deferring poisoned-history error event; a transcript scrub will be attempted");
2174
+ this.deferredTurnError = event;
2175
+ return;
2176
+ }
2107
2177
  this.emitTurnErrorEvent(event);
2108
2178
  }
2109
2179
  /**
@@ -2124,6 +2194,14 @@ var ClaudeSdkDriver = class extends AgentDriver {
2124
2194
  willRetryStaleResume(message) {
2125
2195
  return !this.staleResumeRetryUsed && STALE_RESUME_PATTERN.test(message);
2126
2196
  }
2197
+ /**
2198
+ * Whether `runPrompt`'s catch will attempt a transcript scrub for `message`.
2199
+ * The scrub may still find nothing, so unlike {@link willRetryStaleResume}
2200
+ * the deferred error is not always dropped: `runPrompt` flushes it then.
2201
+ */
2202
+ willAttemptPoisonScrub(message) {
2203
+ return !this.poisonScrubUsed && isPoisonedHistoryError(message) && Boolean(this.config.resumeSessionId || this.sessionId);
2204
+ }
2127
2205
  /** Emit one `agent-event: error`, swallowing a throwing listener. */
2128
2206
  emitTurnErrorEvent(event) {
2129
2207
  try {
@@ -2204,6 +2282,26 @@ var ClaudeSdkDriver = class extends AgentDriver {
2204
2282
  }
2205
2283
  return false;
2206
2284
  }
2285
+ /**
2286
+ * Recognise the long-context entitlement 429 in a failed turn and log it
2287
+ * once per attempt under a stable message. Detection only: the caller's
2288
+ * failover and 429 reporting are unchanged. `seatCapacity` records whether
2289
+ * that path still treats it as a full seat. Logs no prompt or error text.
2290
+ */
2291
+ noteLongContextRejection(errorTexts, detail, seatCapacity) {
2292
+ const matched = this.lastApiErrorRender?.longContext === true || isLongContextEntitlementRejection(errorTexts);
2293
+ if (!matched || this.longContextRejectionLogged) return matched;
2294
+ this.longContextRejectionLogged = true;
2295
+ const model = this.effectiveModelForLog();
2296
+ this.log.warn("long-context entitlement rejection", {
2297
+ cause: "long-context-entitlement",
2298
+ category: detail.category,
2299
+ seatCapacity,
2300
+ ...this.config.aiProviderConfigId ? { providerConfigId: this.config.aiProviderConfigId } : {},
2301
+ ...model ? { model } : {}
2302
+ });
2303
+ return matched;
2304
+ }
2207
2305
  /**
2208
2306
  * Fields that let an alert tell a systematic upstream failure (one provider
2209
2307
  * entry, one class of error) from routine per-turn noise.
@@ -2269,7 +2367,20 @@ var ClaudeSdkDriver = class extends AgentDriver {
2269
2367
  return;
2270
2368
  }
2271
2369
  const apiErrorKind = msg.error;
2272
- this.lastApiErrorRender = typeof apiErrorKind === "string" ? { kind: apiErrorKind, excerpt: apiErrorExcerpt(this.assistantTextOf(agentMsg)) } : null;
2370
+ if (typeof apiErrorKind === "string") {
2371
+ const renderText = this.assistantTextOf(agentMsg);
2372
+ const { apiError, errorDetails } = msg;
2373
+ this.lastApiErrorRender = {
2374
+ kind: apiErrorKind,
2375
+ excerpt: apiErrorExcerpt(renderText),
2376
+ longContext: apiError === LONG_CONTEXT_CREDITS_API_ERROR || isLongContextEntitlementRejection([
2377
+ renderText,
2378
+ typeof errorDetails === "string" ? errorDetails : void 0
2379
+ ])
2380
+ };
2381
+ } else {
2382
+ this.lastApiErrorRender = null;
2383
+ }
2273
2384
  if (this.isSuppressibleAuthRender(agentMsg)) {
2274
2385
  this.log.info("suppressing SDK auth-error assistant text; self-heal will retry");
2275
2386
  this.stashSuppressedAuthRender(this.assistantTextOf(agentMsg));
@@ -2554,11 +2665,17 @@ var ClaudeSdkDriver = class extends AgentDriver {
2554
2665
  retryable: detail.retryable,
2555
2666
  ...this.upstreamErrorLogFields()
2556
2667
  });
2668
+ const seatCapacity = detail.category !== "auth" && isSeatCapacityFailure(detail, msg.subtype);
2669
+ const longContext = this.noteLongContextRejection(effectiveErrors, detail, seatCapacity);
2557
2670
  if (detail.category === "auth") {
2558
2671
  this.reportProviderResponse({ status: 401 });
2559
- } else if (isSeatCapacityFailure(detail, msg.subtype)) {
2672
+ } else if (seatCapacity) {
2560
2673
  this.limitRejectionSeen = true;
2561
- this.reportProviderResponse({ status: 429, ...this.lastLimitInfo ?? {} });
2674
+ this.reportProviderResponse({
2675
+ status: 429,
2676
+ ...this.lastLimitInfo ?? {},
2677
+ ...longContext ? { cause: "long-context-entitlement" } : {}
2678
+ });
2562
2679
  }
2563
2680
  const errorText = effectiveErrors.join("; ") || `Agent stopped: ${msg.subtype}`;
2564
2681
  if (this.turnReject != null && this.willRetryStaleResume(errorText)) {