@skaile/workspaces 1.13.0 → 1.14.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 (127) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/{asset-feeds-ZPZ7C37Z.js → asset-feeds-ZQLJ2DCP.js} +4 -4
  3. package/dist/{asset-feeds-ZPZ7C37Z.js.map → asset-feeds-ZQLJ2DCP.js.map} +1 -1
  4. package/dist/asset-manager/index.js +1 -1
  5. package/dist/bridge/drivers/claude-sdk.js +107 -15
  6. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  7. package/dist/bridge/drivers/codex.js +1 -1
  8. package/dist/bridge/drivers/continue.js +2 -2
  9. package/dist/bridge/drivers/echo.js +1 -1
  10. package/dist/bridge/drivers/gemini.js +3 -3
  11. package/dist/bridge/drivers/goose.js +2 -2
  12. package/dist/bridge/drivers/omp.js +1 -1
  13. package/dist/bridge/drivers/opencode.js +2 -2
  14. package/dist/bridge/drivers/qwen.js +3 -3
  15. package/dist/bridge/index.js +2 -2
  16. package/dist/bridge/src/drivers/claude-sdk.d.ts +34 -7
  17. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  18. package/dist/bridge/src/types.d.ts +7 -4
  19. package/dist/bridge/src/types.d.ts.map +1 -1
  20. package/dist/{chunk-6K6T2RBP.js → chunk-2IJFUAZB.js} +16 -5
  21. package/dist/chunk-2IJFUAZB.js.map +1 -0
  22. package/dist/{chunk-GAA5W4VX.js → chunk-2PFS666T.js} +3 -3
  23. package/dist/{chunk-GAA5W4VX.js.map → chunk-2PFS666T.js.map} +1 -1
  24. package/dist/{chunk-Y6FV73IH.js → chunk-2TSSCHCQ.js} +7 -3
  25. package/dist/chunk-2TSSCHCQ.js.map +1 -0
  26. package/dist/{chunk-25LDSYP4.js → chunk-6GFQ3GXI.js} +4 -4
  27. package/dist/{chunk-25LDSYP4.js.map → chunk-6GFQ3GXI.js.map} +1 -1
  28. package/dist/{chunk-H5XWVDL4.js → chunk-7FXOOC7N.js} +3 -3
  29. package/dist/{chunk-H5XWVDL4.js.map → chunk-7FXOOC7N.js.map} +1 -1
  30. package/dist/{chunk-7J4HI7SO.js → chunk-AF2NTM4B.js} +6 -6
  31. package/dist/{chunk-7J4HI7SO.js.map → chunk-AF2NTM4B.js.map} +1 -1
  32. package/dist/{chunk-SZ3SIVZQ.js → chunk-AL6BU3SP.js} +9 -5
  33. package/dist/chunk-AL6BU3SP.js.map +1 -0
  34. package/dist/{chunk-VU7R3EMF.js → chunk-BIAA3N2A.js} +3 -3
  35. package/dist/{chunk-VU7R3EMF.js.map → chunk-BIAA3N2A.js.map} +1 -1
  36. package/dist/{chunk-HFEJ2L2Y.js → chunk-CS6NFRNQ.js} +2 -2
  37. package/dist/{chunk-HFEJ2L2Y.js.map → chunk-CS6NFRNQ.js.map} +1 -1
  38. package/dist/{chunk-EAPSAJ62.js → chunk-IIGWJE5T.js} +2 -2
  39. package/dist/{chunk-EAPSAJ62.js.map → chunk-IIGWJE5T.js.map} +1 -1
  40. package/dist/{chunk-CPBKSIVF.js → chunk-KECY74AL.js} +3 -3
  41. package/dist/{chunk-CPBKSIVF.js.map → chunk-KECY74AL.js.map} +1 -1
  42. package/dist/{chunk-5D6677HY.js → chunk-LMY6XW6P.js} +30 -11
  43. package/dist/chunk-LMY6XW6P.js.map +1 -0
  44. package/dist/{chunk-AYGPD6WX.js → chunk-MTM6C7IS.js} +3 -3
  45. package/dist/{chunk-AYGPD6WX.js.map → chunk-MTM6C7IS.js.map} +1 -1
  46. package/dist/{chunk-YM5P2P4R.js → chunk-N3OB2YY6.js} +9 -6
  47. package/dist/chunk-N3OB2YY6.js.map +1 -0
  48. package/dist/{chunk-RQJB7KQI.js → chunk-Q44OUGYC.js} +3 -3
  49. package/dist/{chunk-RQJB7KQI.js.map → chunk-Q44OUGYC.js.map} +1 -1
  50. package/dist/{chunk-VCWLS3DM.js → chunk-T2H2ZCU4.js} +3 -3
  51. package/dist/{chunk-VCWLS3DM.js.map → chunk-T2H2ZCU4.js.map} +1 -1
  52. package/dist/{chunk-RJZ5X6AA.js → chunk-UDUFMVJD.js} +6 -6
  53. package/dist/{chunk-RJZ5X6AA.js.map → chunk-UDUFMVJD.js.map} +1 -1
  54. package/dist/cli/index.js +28 -28
  55. package/dist/client/index.js +1 -1
  56. package/dist/client/src/client.d.ts +6 -2
  57. package/dist/client/src/client.d.ts.map +1 -1
  58. package/dist/connectors/config.js +1 -1
  59. package/dist/connectors/index.js +2 -2
  60. package/dist/connectors/src/index.d.ts +1 -1
  61. package/dist/connectors/src/index.d.ts.map +1 -1
  62. package/dist/connectors/src/secrets.d.ts +8 -0
  63. package/dist/connectors/src/secrets.d.ts.map +1 -1
  64. package/dist/{ensure-sources-OFE2X2T6.js → ensure-sources-O7X3LYLG.js} +4 -4
  65. package/dist/{ensure-sources-OFE2X2T6.js.map → ensure-sources-O7X3LYLG.js.map} +1 -1
  66. package/dist/factory-assets/connectors/box.js +2 -2
  67. package/dist/factory-assets/connectors/deploy.js +2 -2
  68. package/dist/factory-assets/connectors/devserver.js +2 -2
  69. package/dist/factory-assets/connectors/flow/adapter.js +2 -2
  70. package/dist/factory-assets/connectors/flow/run-flow.js +3 -3
  71. package/dist/factory-assets/connectors/flow.js +2 -2
  72. package/dist/factory-assets/connectors/git.js +2 -2
  73. package/dist/factory-assets/connectors/gmail.js +2 -2
  74. package/dist/factory-assets/connectors/googledrive.js +2 -2
  75. package/dist/factory-assets/connectors/local.js +2 -2
  76. package/dist/factory-assets/connectors/mattermost.js +2 -2
  77. package/dist/factory-assets/connectors/memory.js +2 -2
  78. package/dist/factory-assets/connectors/minio.js +2 -2
  79. package/dist/factory-assets/connectors/postgres.js +2 -2
  80. package/dist/factory-assets/connectors/s3.js +2 -2
  81. package/dist/factory-assets/connectors/sharepoint.js +2 -2
  82. package/dist/factory-assets/connectors/sqlite.js +2 -2
  83. package/dist/factory-assets/connectors/static-server.js +2 -2
  84. package/dist/factory-assets/connectors/tunnel.js +2 -2
  85. package/dist/factory-assets/connectors/webdav.js +2 -2
  86. package/dist/factory-assets/connectors/xstate-store.js +2 -2
  87. package/dist/factory-assets/connectors/xstate.js +2 -2
  88. package/dist/{helpers-TLYC7OYI.js → helpers-DNTGJSA5.js} +4 -4
  89. package/dist/{helpers-TLYC7OYI.js.map → helpers-DNTGJSA5.js.map} +1 -1
  90. package/dist/{library-publish-H3NNXOGG.js → library-publish-XKQN6RTR.js} +6 -6
  91. package/dist/{library-publish-H3NNXOGG.js.map → library-publish-XKQN6RTR.js.map} +1 -1
  92. package/dist/{open-library-LVN34BTO.js → open-library-2BWIUK4F.js} +5 -5
  93. package/dist/{open-library-LVN34BTO.js.map → open-library-2BWIUK4F.js.map} +1 -1
  94. package/dist/{project-GYPR7O3S.js → project-4NPDM7R3.js} +5 -5
  95. package/dist/{project-GYPR7O3S.js.map → project-4NPDM7R3.js.map} +1 -1
  96. package/dist/runner/index.js +6 -6
  97. package/dist/runner/src/external-mcp.d.ts +14 -2
  98. package/dist/runner/src/external-mcp.d.ts.map +1 -1
  99. package/dist/sdk/asset-manager.js +1 -1
  100. package/dist/sdk/bridge.js +2 -2
  101. package/dist/sdk/client.js +1 -1
  102. package/dist/sdk/index.js +7 -7
  103. package/dist/sdk/runner.js +6 -6
  104. package/dist/sdk/store.js +1 -1
  105. package/dist/{setup-RN2M4VOG.js → setup-HN6POGOA.js} +4 -4
  106. package/dist/{setup-RN2M4VOG.js.map → setup-HN6POGOA.js.map} +1 -1
  107. package/dist/{source-5UU6B4ZR.js → source-HVMQJZU7.js} +6 -6
  108. package/dist/{source-5UU6B4ZR.js.map → source-HVMQJZU7.js.map} +1 -1
  109. package/dist/{source-manifest-publish-KZ5AO5FS.js → source-manifest-publish-JN62UMZD.js} +7 -7
  110. package/dist/{source-manifest-publish-KZ5AO5FS.js.map → source-manifest-publish-JN62UMZD.js.map} +1 -1
  111. package/dist/store/index.js +1 -1
  112. package/dist/store/react.js +6 -2
  113. package/dist/store/react.js.map +1 -1
  114. package/dist/store/src/react.d.ts +6 -2
  115. package/dist/store/src/react.d.ts.map +1 -1
  116. package/dist/store/src/store.d.ts +7 -1
  117. package/dist/store/src/store.d.ts.map +1 -1
  118. package/dist/tui/index.js +6 -6
  119. package/dist/types/src/protocol.d.ts +1 -0
  120. package/dist/types/src/protocol.d.ts.map +1 -1
  121. package/dist/workspace-plugin/index.js +1 -1
  122. package/package.json +1 -1
  123. package/dist/chunk-5D6677HY.js.map +0 -1
  124. package/dist/chunk-6K6T2RBP.js.map +0 -1
  125. package/dist/chunk-SZ3SIVZQ.js.map +0 -1
  126. package/dist/chunk-Y6FV73IH.js.map +0 -1
  127. package/dist/chunk-YM5P2P4R.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#437](https://github.com/skaile-ai/workspaces/pull/437) [`d66e76f`](https://github.com/skaile-ai/workspaces/commit/d66e76ffd7fefea10fed7a83f6d9d3eca7a1e38d) Thanks [@peteralbert](https://github.com/peteralbert)! - Bind `AskUserQuestion` answers to the sub-question they belong to. The `reply` command gains an
8
+ optional `question` field, and it is threaded through every consumer API that can produce a reply
9
+ — `AgentClient.reply(answer, question?)`, `AgentStore.reply(answer, question?)`, and the
10
+ `useAgentStoreActions().reply` wrappers in both `@skaile/workspaces/store` and
11
+ `@skaile/workspace-bridge-react` — down to `AgentDriver.answerQuestion(answer, question?)`. All of
12
+ them carry the question text verbatim from the `question` event that asked it, which is the same
13
+ key the Claude SDK expects back in `answers`. The claude-sdk driver now files a named answer under that question
14
+ and resolves the paused turn once every sub-question has one.
15
+
16
+ Previously the driver filled the next unanswered slot positionally, so a host that let the user
17
+ answer a multi-question call out of order silently attached each answer to the wrong question.
18
+ Omitting `question` keeps the positional fallback, so existing hosts are unaffected; an unknown
19
+ question name also falls back rather than stranding the turn.
20
+
21
+ ### Patch Changes
22
+
23
+ - [#434](https://github.com/skaile-ai/workspaces/pull/434) [`20aa27d`](https://github.com/skaile-ai/workspaces/commit/20aa27dc95aa3179843670792490f82015d22c86) Thanks [@peteralbert](https://github.com/peteralbert)! - fix(runner): never forward unresolved secret refs to external MCP transports — a
24
+ header/env value that matches a known secret-ref shape (`env:`, `mcp:`, …) but
25
+ fails to resolve now aborts that server's spawn with an error naming the server
26
+ id, key, and ref, instead of sending the literal ref (e.g. `mcp:github`) as the
27
+ Authorization header and surfacing a misleading upstream rejection.
28
+
29
+ ## 1.13.1
30
+
31
+ ### Patch Changes
32
+
33
+ - [#431](https://github.com/skaile-ai/workspaces/pull/431) [`a9951a9`](https://github.com/skaile-ai/workspaces/commit/a9951a9ea6f108388e15a40d51a837685812ae18) Thanks [@peteralbert](https://github.com/peteralbert)! - fix(bridge): recover from a Claude SDK turn that never emits a result — a
34
+ per-turn inactivity watchdog (`SKAILE_TURN_STALL_MS`, default 30 min) tears the
35
+ wedged query down and rejects the turn, and `abort()` now forces the teardown
36
+ when the consumer loop refuses to unwind.
37
+
3
38
  ## 1.13.0
4
39
 
5
40
  ### Minor Changes
@@ -64,7 +64,7 @@ async function libraryAssets() {
64
64
  const domains = [];
65
65
  try {
66
66
  const [{ openLibraryManager }, { skaileHomeDir }, { getGlobalCacheDir }] = await Promise.all([
67
- import('./open-library-LVN34BTO.js'),
67
+ import('./open-library-2BWIUK4F.js'),
68
68
  import('./library/index.js'),
69
69
  import('./core/index.js')
70
70
  ]);
@@ -97,7 +97,7 @@ async function libraryAssets() {
97
97
  async function storeAssets(projectDir) {
98
98
  const notes = [];
99
99
  try {
100
- const { resolveCatalogSource } = await import('./open-library-LVN34BTO.js');
100
+ const { resolveCatalogSource } = await import('./open-library-2BWIUK4F.js');
101
101
  const { source, close } = await resolveCatalogSource({ projectDir });
102
102
  try {
103
103
  const assets = await source.listAssets();
@@ -201,5 +201,5 @@ function dedupeBySlug(domains) {
201
201
  }
202
202
 
203
203
  export { gatherAssetFeeds };
204
- //# sourceMappingURL=asset-feeds-ZPZ7C37Z.js.map
205
- //# sourceMappingURL=asset-feeds-ZPZ7C37Z.js.map
204
+ //# sourceMappingURL=asset-feeds-ZQLJ2DCP.js.map
205
+ //# sourceMappingURL=asset-feeds-ZQLJ2DCP.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../cli/src/asset-feeds.ts"],"names":[],"mappings":"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","file":"asset-feeds-ZPZ7C37Z.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}. */\nfunction loadDomainMds(root: string): DomainMd[] {\n const out: DomainMd[] = [];\n for (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 */\nfunction stampDomains(\n root: string,\n fallbackSlug: string,\n entries: CatalogEntry[],\n synthesizeFallback = true,\n): CatalogDomain[] {\n const domainMds = 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 = [...scanRepo(lib.path, lib.name)];\n domains.push(...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: () => 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 */\nfunction sourceAssets(am: AssetManagerLike): { 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 = 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(...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). Synchronous, 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 */\nfunction factoryAssets(): { entries: CatalogEntry[]; domains: CatalogDomain[]; notes: FeedNote[] } {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const entries: CatalogEntry[] = [];\n for (const root of factoryAssetRoots()) {\n const rootEntries = [...scanRepo(root, \"skaile-factory\")];\n domains.push(...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 + synchronous, 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 = factoryAssets();\n const src = 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":"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","file":"asset-feeds-ZQLJ2DCP.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}. */\nfunction loadDomainMds(root: string): DomainMd[] {\n const out: DomainMd[] = [];\n for (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 */\nfunction stampDomains(\n root: string,\n fallbackSlug: string,\n entries: CatalogEntry[],\n synthesizeFallback = true,\n): CatalogDomain[] {\n const domainMds = 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 = [...scanRepo(lib.path, lib.name)];\n domains.push(...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: () => 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 */\nfunction sourceAssets(am: AssetManagerLike): { 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 = 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(...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). Synchronous, 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 */\nfunction factoryAssets(): { entries: CatalogEntry[]; domains: CatalogDomain[]; notes: FeedNote[] } {\n const notes: FeedNote[] = [];\n const domains: CatalogDomain[] = [];\n try {\n const entries: CatalogEntry[] = [];\n for (const root of factoryAssetRoots()) {\n const rootEntries = [...scanRepo(root, \"skaile-factory\")];\n domains.push(...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 + synchronous, 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 = factoryAssets();\n const src = 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, softDepName } from '../chunk-EAPSAJ62.js';
1
+ export { AssetManager, FACTORY_SOURCE_URL, MissingManifestError, NON_DEPLOYABLE_KINDS, normalizeRepoUrl, parseCanonicalLockKey, parseRefTolerant, softDepName } from '../chunk-IIGWJE5T.js';
2
2
  import '../chunk-MWE7DEGC.js';
3
3
  import '../chunk-MZV6WCF3.js';
4
4
  import '../chunk-JXT46QFC.js';
@@ -2,7 +2,7 @@ import { SUPPORTED_CLOUDS, buildClaudeCloudEnv } from '../../chunk-ISQWMBKD.js';
2
2
  import { dispatchCapability } from '../../chunk-RRVQAE5D.js';
3
3
  import { fetchProviderModels } from '../../chunk-BMQKMOQY.js';
4
4
  import { AuthError, classifyClaudeSdkError, reclassifyAuthAsAccountUnavailable, AccountUnavailableError } from '../../chunk-GAH4YRCI.js';
5
- import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-YM5P2P4R.js';
5
+ import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-N3OB2YY6.js';
6
6
  import '../../chunk-6E6PKKAD.js';
7
7
  import '../../chunk-JB35MVBN.js';
8
8
  import '../../chunk-NSBPE2FW.js';
@@ -311,6 +311,12 @@ if (typeof globalThis.require === "undefined") {
311
311
  function freshRetryBudget() {
312
312
  return { staleResume: false, poison: false, auth: false };
313
313
  }
314
+ var DEFAULT_TURN_STALL_MS = 30 * 6e4;
315
+ function turnStallMs() {
316
+ const raw = Number(process.env.SKAILE_TURN_STALL_MS);
317
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_TURN_STALL_MS;
318
+ }
319
+ var ABORT_GRACE_MS = 3e3;
314
320
  var claudeBinaryCache;
315
321
  var claudeBinaryResolved = false;
316
322
  var ClaudeSdkDriver = class extends AgentDriver {
@@ -343,6 +349,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
343
349
  turnReject = null;
344
350
  /** Guards against duplicate agent_end emissions per turn. */
345
351
  turnCompleted = false;
352
+ /** `Date.now()` of the most recent SDK message — the watchdog's liveness signal. */
353
+ lastSdkMessageAt = 0;
354
+ /** Armed while a turn is in flight; trips when the SDK goes silent for too long. */
355
+ turnStallTimer = null;
346
356
  /**
347
357
  * Mirror of the in-flight {@link prompt} call's auth retry budget: `false`
348
358
  * while the `onAuthError` self-heal is still available, `true` once it has
@@ -361,11 +371,11 @@ var ClaudeSdkDriver = class extends AgentDriver {
361
371
  * Set while the SDK is paused inside `canUseTool` on an `AskUserQuestion`
362
372
  * call, waiting for the user's answer. The SDK delivers every sub-question in
363
373
  * one call (one `toolUseID`) and the turn stays paused until we resolve with
364
- * a {@link PermissionResult}. Replies arrive one per `question` event in
365
- * order, so we accumulate `answers[questionText] = reply` and resolve `allow`
366
- * once `answered === questions.length`. On abort/hibernate the request is
367
- * denied so the SDK writes a clean tool_result (no dangling tool_use to
368
- * poison the resumed transcript). Null when no question is pending.
374
+ * a {@link PermissionResult}. We accumulate `answers[questionText] = reply`
375
+ * the same keying the SDK expects back and resolve `allow` once every entry
376
+ * in `questionTexts` has one. On abort/hibernate the request is denied so the
377
+ * SDK writes a clean tool_result (no dangling tool_use to poison the resumed
378
+ * transcript). Null when no question is pending.
369
379
  */
370
380
  pendingQuestion = null;
371
381
  /** Cached slash commands discovered from the SDK. */
@@ -471,6 +481,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
471
481
  this.running = false;
472
482
  throw err;
473
483
  }
484
+ this.armTurnStallWatchdog();
474
485
  try {
475
486
  await turnPromise;
476
487
  } catch (err) {
@@ -499,12 +510,79 @@ var ClaudeSdkDriver = class extends AgentDriver {
499
510
  throw err;
500
511
  } finally {
501
512
  if (!retrying) {
513
+ this.disarmTurnStallWatchdog();
502
514
  this.turnResolve = null;
503
515
  this.turnReject = null;
504
516
  this.running = false;
505
517
  }
506
518
  }
507
519
  }
520
+ /**
521
+ * Arm the per-turn inactivity watchdog. Self-rescheduling rather than a single
522
+ * absolute timer so any mid-turn SDK message pushes the deadline out; the
523
+ * timer is `unref`'d so an armed watchdog never holds the process open.
524
+ */
525
+ armTurnStallWatchdog() {
526
+ this.disarmTurnStallWatchdog();
527
+ if (this.turnCompleted || this.turnReject == null) return;
528
+ const budget = turnStallMs();
529
+ const tick = () => {
530
+ if (this.turnCompleted || this.turnReject == null) return;
531
+ if (this.hasPendingQuestion()) {
532
+ this.turnStallTimer = setTimeout(tick, budget);
533
+ this.turnStallTimer.unref?.();
534
+ return;
535
+ }
536
+ const idleMs = Date.now() - this.lastSdkMessageAt;
537
+ if (idleMs < budget) {
538
+ this.turnStallTimer = setTimeout(tick, budget - idleMs);
539
+ this.turnStallTimer.unref?.();
540
+ return;
541
+ }
542
+ this.onTurnStalled(idleMs);
543
+ };
544
+ this.lastSdkMessageAt = Date.now();
545
+ this.turnStallTimer = setTimeout(tick, budget);
546
+ this.turnStallTimer.unref?.();
547
+ }
548
+ disarmTurnStallWatchdog() {
549
+ if (this.turnStallTimer) {
550
+ clearTimeout(this.turnStallTimer);
551
+ this.turnStallTimer = null;
552
+ }
553
+ }
554
+ /**
555
+ * The SDK went silent mid-turn. Tear the query down so the *next* prompt
556
+ * starts a fresh one (a live-but-deaf query would otherwise swallow every
557
+ * subsequent `streamInput`), then reject the turn with an actionable message.
558
+ */
559
+ onTurnStalled(idleMs) {
560
+ const idle = idleMs < 6e4 ? `${Math.round(idleMs / 1e3)}s` : `${Math.round(idleMs / 6e4)}m`;
561
+ this.log.error("turn stalled \u2014 no SDK message received; resetting the query", { idleMs });
562
+ this.hardResetQuery();
563
+ this.failTurn(
564
+ new Error(
565
+ `Agent stalled: no response from the model process for ${idle}. The conversation is intact \u2014 send your message again.`
566
+ )
567
+ );
568
+ }
569
+ /**
570
+ * Drop a query that will not unwind on its own, keeping the session intact.
571
+ *
572
+ * Deliberately does NOT clear `sessionId` / `hasSession` — the next
573
+ * `startQuery()` must resume the same SDK session so the conversation
574
+ * survives. That is what separates this from {@link kill}.
575
+ */
576
+ hardResetQuery() {
577
+ if (this.query) {
578
+ try {
579
+ this.query.close();
580
+ } catch {
581
+ }
582
+ this.query = null;
583
+ }
584
+ this.abortController?.abort();
585
+ }
508
586
  /**
509
587
  * Side-effects of the stale-resume recovery path: emit the resume-outcome and
510
588
  * non-fatal error events, then reset session state so the recursive prompt()
@@ -831,8 +909,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
831
909
  resolve,
832
910
  originalInput: input,
833
911
  questionTexts,
834
- answers: {},
835
- answered: 0
912
+ answers: {}
836
913
  };
837
914
  opts.signal.addEventListener(
838
915
  "abort",
@@ -845,16 +922,16 @@ var ClaudeSdkDriver = class extends AgentDriver {
845
922
  hasPendingQuestion() {
846
923
  return this.pendingQuestion !== null;
847
924
  }
848
- answerQuestion(answer) {
925
+ answerQuestion(answer, question) {
849
926
  const pending = this.pendingQuestion;
850
927
  if (!pending) return false;
851
- const key = pending.questionTexts[pending.answered];
928
+ const key = question !== void 0 && pending.questionTexts.includes(question) ? question : pending.questionTexts.find((q) => !Object.hasOwn(pending.answers, q));
852
929
  if (key !== void 0) {
853
930
  pending.answers[key] = answer;
854
- pending.answered += 1;
855
931
  }
856
- if (pending.answered >= pending.questionTexts.length) {
932
+ if (pending.questionTexts.every((q) => Object.hasOwn(pending.answers, q))) {
857
933
  this.pendingQuestion = null;
934
+ this.lastSdkMessageAt = Date.now();
858
935
  pending.resolve({
859
936
  behavior: "allow",
860
937
  updatedInput: { ...pending.originalInput, answers: pending.answers }
@@ -872,6 +949,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
872
949
  const pending = this.pendingQuestion;
873
950
  if (!pending) return;
874
951
  this.pendingQuestion = null;
952
+ this.lastSdkMessageAt = Date.now();
875
953
  pending.resolve({ behavior: "deny", message: reason });
876
954
  }
877
955
  /**
@@ -979,6 +1057,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
979
1057
  try {
980
1058
  for await (const msg of this.query) {
981
1059
  if (this.abortController?.signal.aborted) break;
1060
+ this.lastSdkMessageAt = Date.now();
982
1061
  this.captureSessionId(msg);
983
1062
  this.handleSdkMessage(msg);
984
1063
  if (msg.type === "result" && !msg.is_error) {
@@ -1039,6 +1118,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
1039
1118
  completeTurn() {
1040
1119
  if (this.turnCompleted) return;
1041
1120
  this.turnCompleted = true;
1121
+ this.disarmTurnStallWatchdog();
1042
1122
  if (this.turnResolve) {
1043
1123
  this.turnResolve();
1044
1124
  this.turnResolve = null;
@@ -1085,6 +1165,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
1085
1165
  * exposed this hang).
1086
1166
  */
1087
1167
  failTurn(err, detail) {
1168
+ this.disarmTurnStallWatchdog();
1088
1169
  if (this.turnReject) {
1089
1170
  this.turnReject(err);
1090
1171
  this.turnResolve = null;
@@ -1390,7 +1471,9 @@ var ClaudeSdkDriver = class extends AgentDriver {
1390
1471
  * signals the underlying `AbortController`. The driver remains usable after abort.
1391
1472
  *
1392
1473
  * @remarks Best-effort: if `interrupt()` throws (e.g. query already ended) the
1393
- * error is silently swallowed and the abort signal is still sent.
1474
+ * error is silently swallowed and the abort signal is still sent. A query
1475
+ * whose generator refuses to unwind within {@link ABORT_GRACE_MS} is torn
1476
+ * down outright — see {@link hardResetQuery}.
1394
1477
  */
1395
1478
  async abort() {
1396
1479
  this.denyPendingQuestion("turn aborted before the question was answered");
@@ -1400,8 +1483,17 @@ var ClaudeSdkDriver = class extends AgentDriver {
1400
1483
  } catch {
1401
1484
  }
1402
1485
  }
1403
- if (this.abortController) {
1404
- this.abortController.abort();
1486
+ this.abortController?.abort();
1487
+ const deadline = Date.now() + ABORT_GRACE_MS;
1488
+ while (this.query && Date.now() < deadline) {
1489
+ await new Promise((resolve) => {
1490
+ setTimeout(resolve, 100).unref?.();
1491
+ });
1492
+ }
1493
+ if (this.query) {
1494
+ this.log.warn("query did not unwind after abort; forcing teardown");
1495
+ this.hardResetQuery();
1496
+ this.completeTurn();
1405
1497
  }
1406
1498
  }
1407
1499
  /** `true` while the driver is processing a `prompt()` call. */