@aiwg/cli 2026.10.3 → 2026.10.5

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 (279) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/model-capabilities.v1.json +96 -4
  3. package/bin/aiwg.mjs +50 -27
  4. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  5. package/dist/src/artifacts/browser-export.js +6 -0
  6. package/dist/src/artifacts/checksum-manifest.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +173 -92
  8. package/dist/src/artifacts/citation-table-schema.js +59 -0
  9. package/dist/src/artifacts/cli.js +69 -18
  10. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  11. package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
  12. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  13. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  14. package/dist/src/artifacts/discovery-eval.js +6 -4
  15. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  16. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  17. package/dist/src/artifacts/framework-capabilities.js +171 -0
  18. package/dist/src/artifacts/framework-index-paths.js +73 -0
  19. package/dist/src/artifacts/framework-source-digest.js +75 -0
  20. package/dist/src/artifacts/index-builder.js +286 -131
  21. package/dist/src/artifacts/index-files.js +132 -18
  22. package/dist/src/artifacts/index-preparation.js +130 -0
  23. package/dist/src/artifacts/index-refresh.js +591 -0
  24. package/dist/src/artifacts/index-worker.mjs +11 -0
  25. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  26. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  27. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  28. package/dist/src/artifacts/query-engine.js +410 -88
  29. package/dist/src/artifacts/relations-builder.js +168 -0
  30. package/dist/src/artifacts/relations.js +148 -0
  31. package/dist/src/artifacts/stats.js +5 -2
  32. package/dist/src/artifacts/types.js +30 -10
  33. package/dist/src/artifacts/watcher.js +1 -1
  34. package/dist/src/auth/credential-store.js +2 -1
  35. package/dist/src/catalog/cli.d.mts +5 -0
  36. package/dist/src/channel/manager.d.mts +20 -0
  37. package/dist/src/channel/manager.mjs +19 -2
  38. package/dist/src/cli/command-log.js +2 -3
  39. package/dist/src/cli/handlers/artifacts.js +3 -0
  40. package/dist/src/cli/handlers/auth.js +3 -0
  41. package/dist/src/cli/handlers/decision.js +19 -12
  42. package/dist/src/cli/handlers/diagnose.js +4 -3
  43. package/dist/src/cli/handlers/effect.js +4 -2
  44. package/dist/src/cli/handlers/evidence.js +3 -0
  45. package/dist/src/cli/handlers/help.js +1 -0
  46. package/dist/src/cli/handlers/index.js +3 -1
  47. package/dist/src/cli/handlers/init.js +4 -1
  48. package/dist/src/cli/handlers/lint.js +5 -0
  49. package/dist/src/cli/handlers/models.js +104 -8
  50. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  51. package/dist/src/cli/handlers/refresh.js +12 -0
  52. package/dist/src/cli/handlers/resource-versions.js +3 -0
  53. package/dist/src/cli/handlers/rules.js +195 -0
  54. package/dist/src/cli/handlers/run.js +4 -0
  55. package/dist/src/cli/handlers/runtime-info.js +1 -2
  56. package/dist/src/cli/handlers/script-runner.js +3 -2
  57. package/dist/src/cli/handlers/session.js +19 -9
  58. package/dist/src/cli/handlers/sessions.js +137 -43
  59. package/dist/src/cli/handlers/setup-manifest.js +39 -44
  60. package/dist/src/cli/handlers/setup.js +41 -0
  61. package/dist/src/cli/handlers/steward.js +11 -0
  62. package/dist/src/cli/handlers/subcommands.js +57 -2
  63. package/dist/src/cli/handlers/uhp.js +3 -0
  64. package/dist/src/cli/handlers/use.js +294 -55
  65. package/dist/src/cli/handlers/utilities.js +26 -10
  66. package/dist/src/cli/handlers/version.js +8 -0
  67. package/dist/src/cli/router.js +15 -1
  68. package/dist/src/cli/services/deployment-verification.js +19 -9
  69. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  70. package/dist/src/cli/skill-usage.js +2 -3
  71. package/dist/src/config/aiwg-config.js +87 -37
  72. package/dist/src/config/cli.js +111 -5
  73. package/dist/src/config/decision-config.js +106 -0
  74. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  75. package/dist/src/config/gitignore.js +50 -0
  76. package/dist/src/config/index-graph-overrides.js +36 -0
  77. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  78. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  79. package/dist/src/config/user-config-dir.d.mts +9 -0
  80. package/dist/src/config/user-config.js +2 -1
  81. package/dist/src/config/user-registry.js +23 -15
  82. package/dist/src/config/user-state.js +7 -0
  83. package/dist/src/decision/ask.js +203 -43
  84. package/dist/src/decision/context-live-qualification.js +2 -2
  85. package/dist/src/decision/driver.js +13 -8
  86. package/dist/src/decision/ensemble/aggregate.js +23 -10
  87. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  88. package/dist/src/decision/entry.js +2 -15
  89. package/dist/src/decision/graph-live-qualification.js +1 -1
  90. package/dist/src/decision/graph-run.js +9 -0
  91. package/dist/src/decision/heldout/collector.js +5 -13
  92. package/dist/src/decision/heldout/contract.js +30 -7
  93. package/dist/src/decision/jev-credentials.js +15 -1
  94. package/dist/src/decision/ledger.js +131 -28
  95. package/dist/src/decision/offload-activation.js +137 -0
  96. package/dist/src/decision/offload.js +550 -0
  97. package/dist/src/decision/qualification/load.js +4 -6
  98. package/dist/src/decision/savings.js +28 -0
  99. package/dist/src/effects/types.js +5 -1
  100. package/dist/src/effects/verifiers/git.js +2 -1
  101. package/dist/src/effects/verifiers/tracker.js +26 -14
  102. package/dist/src/extensions/commands/definitions.js +19 -0
  103. package/dist/src/extensions/project-local-remove.js +36 -1
  104. package/dist/src/gates/providers/loader.js +61 -17
  105. package/dist/src/installation/manager-command.d.mts +18 -0
  106. package/dist/src/installation/manager-command.mjs +14 -2
  107. package/dist/src/installation/manager.d.mts +49 -0
  108. package/dist/src/installation/manager.mjs +78 -3
  109. package/dist/src/issues/cli.js +4 -4
  110. package/dist/src/issues/local.js +24 -5
  111. package/dist/src/jobs/runner.js +1 -1
  112. package/dist/src/lint/cli.js +27 -1
  113. package/dist/src/lint/loader.js +2 -1
  114. package/dist/src/lint/reporters.js +25 -1
  115. package/dist/src/lint/rule-activation.js +43 -0
  116. package/dist/src/lint/runner.js +137 -62
  117. package/dist/src/mcp/adapters/codex-runtime.js +7 -1
  118. package/dist/src/mcp/claude-install.d.mts +4 -0
  119. package/dist/src/mcp/claude-install.mjs +41 -0
  120. package/dist/src/mcp/cli.d.mts +1 -0
  121. package/dist/src/mcp/cli.mjs +36 -11
  122. package/dist/src/mcp/config-file.d.mts +28 -0
  123. package/dist/src/mcp/config-layers.d.mts +36 -0
  124. package/dist/src/mcp/credentials.d.mts +51 -0
  125. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  126. package/dist/src/mcp/grok-build-config.mjs +1 -1
  127. package/dist/src/mcp/helpers.mjs +3 -2
  128. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  129. package/dist/src/mcp/muse-native-config.mjs +1 -0
  130. package/dist/src/mcp/omp-config.d.mts +4 -0
  131. package/dist/src/mcp/registry.js +3 -93
  132. package/dist/src/mcp/registry.mjs +3 -87
  133. package/dist/src/mcp/server-config.d.mts +3 -0
  134. package/dist/src/mcp/server-config.mjs +89 -0
  135. package/dist/src/mcp/server.mjs +37 -375
  136. package/dist/src/mcp/test-simple.mjs +2 -1
  137. package/dist/src/mcp/toml-editor.d.mts +4 -0
  138. package/dist/src/mcp/toml-string.d.mts +1 -0
  139. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  140. package/dist/src/mcp/tool-filters.d.mts +46 -0
  141. package/dist/src/mcp/tools/decision.mjs +12 -11
  142. package/dist/src/mcp/tools/discovery.mjs +62 -55
  143. package/dist/src/mcp/tools/orchestration.mjs +12 -2
  144. package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
  145. package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
  146. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
  147. package/dist/src/mission-protocol/codecs.js +8 -0
  148. package/dist/src/models/config-loader.js +7 -2
  149. package/dist/src/models/model-capabilities.v1.json +96 -4
  150. package/dist/src/models/model-discovery.js +76 -11
  151. package/dist/src/models/provider-policy.js +101 -7
  152. package/dist/src/models/qualification-digest.d.mts +2 -0
  153. package/dist/src/models/resolver.js +12 -10
  154. package/dist/src/models/worker-direct-readiness.js +65 -44
  155. package/dist/src/models/worker-dispatch-offer.js +3 -3
  156. package/dist/src/models/worker-health-readiness.js +16 -3
  157. package/dist/src/models/worker-installation.d.mts +39 -0
  158. package/dist/src/models/worker-installation.mjs +135 -28
  159. package/dist/src/process/bounded-child-process.d.mts +84 -0
  160. package/dist/src/process/bounded-child-process.mjs +43 -0
  161. package/dist/src/process/cli-command.d.mts +16 -0
  162. package/dist/src/process/signal-child.d.mts +6 -0
  163. package/dist/src/process/signal-child.mjs +7 -0
  164. package/dist/src/providers/deployed-skill-roots.js +20 -0
  165. package/dist/src/providers/grok-build-acp.js +3 -2
  166. package/dist/src/providers/grok-build-headless.js +12 -4
  167. package/dist/src/providers/grok-build-inspect.js +1 -0
  168. package/dist/src/providers/omp-agent.d.mts +8 -0
  169. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  170. package/dist/src/providers/omp-paths.d.mts +13 -0
  171. package/dist/src/providers/skill-ownership.d.mts +9 -0
  172. package/dist/src/providers/skill-ownership.mjs +88 -0
  173. package/dist/src/rules/activation.d.mts +21 -0
  174. package/dist/src/rules/activation.mjs +62 -0
  175. package/dist/src/rules/inline-budget.d.mts +26 -0
  176. package/dist/src/rules/inline-budget.mjs +66 -0
  177. package/dist/src/rules/inventory.js +129 -0
  178. package/dist/src/rules/pin-state.js +73 -0
  179. package/dist/src/rules/pins.d.mts +14 -0
  180. package/dist/src/rules/pins.mjs +112 -0
  181. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  182. package/dist/src/serve/mission-conductor.js +38 -12
  183. package/dist/src/serve/worker-invocation.js +30 -10
  184. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  185. package/dist/src/sessions/adapter-registry.js +23 -19
  186. package/dist/src/sessions/adapters/claude.js +39 -54
  187. package/dist/src/sessions/adapters/codex.js +95 -67
  188. package/dist/src/sessions/adapters/copilot.js +4 -4
  189. package/dist/src/sessions/adapters/cursor.js +40 -57
  190. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  191. package/dist/src/sessions/adapters/factory.js +31 -27
  192. package/dist/src/sessions/adapters/generic.js +10 -10
  193. package/dist/src/sessions/adapters/grok-build.js +11 -11
  194. package/dist/src/sessions/adapters/grokbot.js +24 -13
  195. package/dist/src/sessions/adapters/hermes.js +27 -29
  196. package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
  197. package/dist/src/sessions/adapters/muse.js +150 -93
  198. package/dist/src/sessions/adapters/omp.js +28 -14
  199. package/dist/src/sessions/adapters/openclaw.js +89 -79
  200. package/dist/src/sessions/adapters/opencode.js +27 -24
  201. package/dist/src/sessions/adapters/openhuman.js +18 -60
  202. package/dist/src/sessions/adapters/pi.js +29 -14
  203. package/dist/src/sessions/adapters/warp.js +3 -3
  204. package/dist/src/sessions/adapters/windsurf.js +19 -48
  205. package/dist/src/sessions/batch-contracts.js +16 -8
  206. package/dist/src/sessions/batch-import.js +252 -12
  207. package/dist/src/sessions/contracts.js +50 -3
  208. package/dist/src/sessions/coverage-preflight.js +27 -2
  209. package/dist/src/sessions/export-plan.js +4 -3
  210. package/dist/src/sessions/import-exclusions.js +72 -0
  211. package/dist/src/sessions/import-lease.js +34 -18
  212. package/dist/src/sessions/import-limits.js +37 -0
  213. package/dist/src/sessions/importer.js +60 -38
  214. package/dist/src/sessions/lifecycle.js +45 -0
  215. package/dist/src/sessions/policy.js +145 -26
  216. package/dist/src/sessions/readers.js +106 -53
  217. package/dist/src/sessions/refresh.js +105 -0
  218. package/dist/src/sessions/repository.js +151 -54
  219. package/dist/src/sessions/workspace-discovery.js +292 -60
  220. package/dist/src/sessions/workspace-matching.js +107 -0
  221. package/dist/src/skills/importer.js +1 -0
  222. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  223. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  224. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  225. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  226. package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
  227. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  228. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  229. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
  230. package/dist/src/storage/backends/fs.js +18 -76
  231. package/dist/src/storage/backends/obsidian.js +1 -0
  232. package/dist/src/tracker/forge-http.js +2 -1
  233. package/dist/src/update/checker.d.mts +2 -0
  234. package/dist/src/update/service.d.mts +22 -0
  235. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  236. package/dist/src/utils/contained-filesystem.mjs +683 -0
  237. package/dist/src/utils/temporary-directory.js +36 -0
  238. package/dist/src/writing/example-generator.js +1 -1
  239. package/package.json +15 -2
  240. package/schemas/models/model-catalog.v1.schema.json +1 -1
  241. package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
  242. package/tools/_resolve-impl.mjs +13 -15
  243. package/tools/agents/deploy-agents.mjs +49 -21
  244. package/tools/agents/providers/base.mjs +104 -134
  245. package/tools/agents/providers/claude.mjs +8 -24
  246. package/tools/agents/providers/codex.mjs +18 -11
  247. package/tools/agents/providers/cursor.mjs +19 -18
  248. package/tools/agents/providers/grok-build.mjs +3 -1
  249. package/tools/agents/providers/hermes.mjs +57 -47
  250. package/tools/agents/providers/openclaw.mjs +9 -4
  251. package/tools/agents/providers/pi.mjs +5 -0
  252. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  253. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  254. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  255. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  256. package/tools/agents/providers/warp.mjs +20 -26
  257. package/tools/cli/decision-offload-report.mjs +40 -0
  258. package/tools/cli/workspace-status.mjs +862 -0
  259. package/tools/plugin/package-plugins.mjs +186 -8
  260. package/tools/plugin/plugin-links.mjs +114 -0
  261. package/tools/providers/antigravity-transport.mjs +3 -1
  262. package/tools/providers/deepseek-harness-transport.mjs +6 -4
  263. package/tools/providers/omp-transport.mjs +4 -2
  264. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  265. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  266. package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
  267. package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
  268. package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
  269. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  270. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  271. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  272. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  273. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  274. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  275. package/tools/ralph-external/process-monitor.mjs +1017 -0
  276. package/tools/ralph-external/session-launcher.mjs +5057 -0
  277. package/tools/scripts/resource-preflight.mjs +541 -0
  278. package/tools/skills/deploy-skills-codex.mjs +2 -1
  279. package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
@@ -27,7 +27,9 @@ export async function authorizeSourceFile(input) {
27
27
  throw new SessionContractError('SOURCE_NOT_REGULAR_FILE', 'session source must be a regular file');
28
28
  }
29
29
  if (input.maxBytes !== undefined && stat.size > input.maxBytes) {
30
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'session source exceeds the authorized byte limit');
30
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'session source exceeds the authorized byte limit', {
31
+ limit: 'maxBytes', configured: input.maxBytes, observed: stat.size,
32
+ });
31
33
  }
32
34
  return {
33
35
  canonicalPath,
@@ -54,21 +56,62 @@ async function safeRealpath(path, root = false) {
54
56
  throw new SessionContractError('SOURCE_NOT_AUTHORIZED', root ? 'an authorized source root is inaccessible' : 'selected session source is inaccessible');
55
57
  }
56
58
  }
59
+ // Newline gaps are bounded and each repetition consumes a line break, keeping
60
+ // indentation scans disjoint even on large malformed records (#3273, #3404).
61
+ const VALUE_GAP = /[ \t]*(?:\r?\n[ \t]*){0,2}/.source;
62
+ const NEXT_LINE_GAP = /[ \t]*(?:\r?\n[ \t]*){1,2}/.source;
63
+ const QUOTED_VALUE = /"(?:\\[^\r\n]|[^"\\\r\n])+"|'(?:\\[^\r\n]|[^'\\\r\n])+'|`(?!`)(?:\\[^\r\n]|[^`\\\r\n])+`(?!`)/.source;
64
+ const GENERATED_VALUE = /(?=[A-Za-z0-9._~+/=-]*[0-9])(?=[A-Za-z0-9._~+/=-]*[A-Za-z])[A-Za-z0-9._~+/=-]{20,}/.source;
65
+ function assignmentPattern(key, separator, sameLineValue) {
66
+ // Only the next-line branch needs stronger evidence: quoted text or a long
67
+ // generated value. In particular, YAML lists and Markdown fences are prose.
68
+ return new RegExp(`\\b${key}[ \\t]*${separator}(?:${NEXT_LINE_GAP}(?:${QUOTED_VALUE}|${GENERATED_VALUE})|[ \\t]*(?:${QUOTED_VALUE}|${sameLineValue}))`, 'gi');
69
+ }
70
+ // Ambiguous token components are bounded; whitespace and key blocks use disjoint scans.
57
71
  const SECRET_PATTERNS = [
58
- ['private-key', /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g],
59
- ['bearer-token', /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/gi],
60
- ['authorization', /\bAuthorization\s*:\s*[^\r\n]+/gi],
61
- ['cookie', /\b(?:Cookie|Set-Cookie)\s*:\s*[^\r\n]+/gi],
62
- ['credential', /\b(?:api[_-]?key|token|password|secret)\s*(?:[:=]|\s)\s*["']?[^\s"',;]{6,}/gi],
63
- ['provider-token', /\b(?:sk-(?:proj-)?|gh[pousr]_|github_pat_|xox[baprs]-|AKIA)[A-Za-z0-9._~+/=-]{8,}\b/g],
72
+ ['bearer-token', new RegExp(`\\bBearer(?:[ \\t]+|${NEXT_LINE_GAP})(?:${QUOTED_VALUE}|\\\\"[A-Za-z0-9._~+/=-]{12,}\\\\"|[A-Za-z0-9._~+/=-]{12,})`, 'gi')],
73
+ ['authorization', new RegExp(`\\bAuthorization[ \\t]*:${VALUE_GAP}[^\\s][^\\r\\n]*`, 'gi')],
74
+ ['cookie', new RegExp(`\\b(?:Cookie|Set-Cookie)[ \\t]*:${VALUE_GAP}[^\\s][^\\r\\n]*`, 'gi')],
75
+ ['environment-assignment', assignmentPattern('(?:AWS_SECRET_ACCESS_KEY|[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY))', '=', /[^\s]+/.source)],
76
+ ['credential', assignmentPattern('(?:api[_-]?key|token|password|secret)', '[:=]', /["'`]?[^\s"'`,;]{6,}/.source)],
77
+ ['credential', /^[ \t]*password[ \t]+[^\s]+[ \t]*$/gim],
78
+ // Space-separated values only when they look generated (20+ chars, letters and digits), so prose stays.
79
+ ['credential', new RegExp(`\\b(?:api[_-]?key|token|secret)[ \\t]+${GENERATED_VALUE}`, 'gi')],
80
+ ['provider-token', /\b(?:sk-(?:ant-|proj-)?|[sr]k_(?:live|test)_|gh[pousr]_|github_pat_|glpat-|xai-|npm_|hf_|xox[baprs]-|AKIA|AIza)[A-Za-z0-9._~+/=-]{8,}/g],
81
+ ['provider-token', /https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+/g],
64
82
  ['connection-string', /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis):\/\/[^\s]+/gi],
65
- ['credential-url', /\bhttps?:\/\/[^/\s:@]+:[^/\s@]+@[^\s]+/gi],
66
- ['environment-assignment', /\b[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY)\s*=\s*(?:"[^"]+"|'[^']+'|[^\s]+)/g],
83
+ ['credential-url', new RegExp(`\\bhttps?://${VALUE_GAP}[^/\\s:@]+:${VALUE_GAP}(?:${QUOTED_VALUE}|[^/\\s@]+)@${VALUE_GAP}[^\\s"'\x60<>\\\\]+`, 'gi')],
67
84
  ['fixture-credential', /\bredaction-canary-[A-Za-z0-9_-]+\b/gi],
68
- ['email', /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi],
85
+ ['email', /\b[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\.[A-Z]{2,63}\b/gi],
69
86
  ];
87
+ function redactJwts(input, mark) {
88
+ // Consume even incomplete candidates. Retrying at each inner `eyJ` in an
89
+ // unterminated segment would repeatedly scan the remainder of a large record.
90
+ return input.replace(/\beyJ[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+){0,2}/g, (candidate) => {
91
+ const parts = candidate.split('.');
92
+ return parts.length === 3 && parts[0].length >= 8 && parts[1].length >= 2 && parts[2].length >= 8
93
+ ? mark('provider-token') : candidate;
94
+ });
95
+ }
96
+ const PRIVATE_KEY_BEGIN = /-----BEGIN ((?:(?:RSA|EC|DSA|OPENSSH|ENCRYPTED) )?PRIVATE KEY|PGP PRIVATE KEY BLOCK)-----/g;
97
+ function redactPrivateKeys(input, mark) {
98
+ const parts = [];
99
+ let cursor = 0;
100
+ PRIVATE_KEY_BEGIN.lastIndex = 0;
101
+ let match;
102
+ while ((match = PRIVATE_KEY_BEGIN.exec(input))) {
103
+ parts.push(input.slice(cursor, match.index), mark('private-key'));
104
+ const endMarker = `-----END ${match[1]}-----`;
105
+ const end = input.indexOf(endMarker, PRIVATE_KEY_BEGIN.lastIndex);
106
+ // An unfinished key block is sensitive through the end of the record.
107
+ cursor = end < 0 ? input.length : end + endMarker.length;
108
+ PRIVATE_KEY_BEGIN.lastIndex = cursor;
109
+ }
110
+ parts.push(input.slice(cursor));
111
+ return parts.join('');
112
+ }
70
113
  const SENSITIVE_KEY = /(?:authorization|auth[_-]?header|cookie|credential|password|passwd|secret|token|api[_-]?key|private[_-]?key|connection[_-]?string)/i;
71
- const CONTENT_KEY = /(?:^|[_-])(?:text|content|prompt|command|arguments?|args|result|output|body|request|response|source|code|query)(?:$|[_-])/i;
114
+ const CONTENT_KEY = /(?:^|[_-])(?:data|text|content|prompt|command|arguments?|args|result|output|body|request|response|source|code|query)(?:$|[_-])/i;
72
115
  const PATH_KEY = /(?:^|[_-])(?:path|cwd|directory|filename|file)(?:$|[_-])/i;
73
116
  const SAFE_STRING_KEY = /(?:^|[_-])(?:id|kind|type|role|status|state|lifecycle|reason|name|product|provider|version|schema|model|format|class|mode|event|operation|tool|method|language|scope|consistency|disposition|phase|visibility|protocol|category|classification|decision|capability|permission|transition|hash|digest)(?:$|[_-])/i;
74
117
  const CONTROL_CHARACTERS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g;
@@ -103,23 +146,28 @@ export function sanitizeNativeExtensions(input, overrides = {}) {
103
146
  return marker('depth-limit');
104
147
  if (value === null || typeof value === 'boolean' || typeof value === 'number')
105
148
  return value;
149
+ if (typeof value === 'string' && /^\[REDACTED:[a-z-]+\]$/.test(value))
150
+ return value;
151
+ if (SENSITIVE_KEY.test(classifiedKey))
152
+ return marker('sensitive-field');
153
+ if (CONTENT_KEY.test(classifiedKey))
154
+ return marker('content');
155
+ if (PATH_KEY.test(classifiedKey))
156
+ return marker('path');
106
157
  if (typeof value === 'bigint')
107
158
  return String(value);
108
159
  if (typeof value !== 'string' && typeof value !== 'object') {
109
160
  return marker('unsupported-value');
110
161
  }
111
162
  if (typeof value === 'string') {
112
- if (SENSITIVE_KEY.test(classifiedKey))
113
- return marker('sensitive-field');
114
- if (CONTENT_KEY.test(classifiedKey))
115
- return marker('content');
116
- if (PATH_KEY.test(classifiedKey))
117
- return marker('path');
118
163
  if (Buffer.byteLength(value) > limits.maxStringBytes)
119
164
  return marker('string-limit');
120
165
  const redacted = redactSessionText(value);
121
166
  for (const classification of redacted.classes)
122
167
  classes.add(classification);
168
+ for (const [classification, count] of Object.entries(redacted.decisions)) {
169
+ decisions[classification] = (decisions[classification] ?? 0) + count;
170
+ }
123
171
  if (redacted.classes.length) {
124
172
  decisions['classified-value'] = (decisions['classified-value'] ?? 0) + 1;
125
173
  return redacted.text.replace(CONTROL_CHARACTERS, ' ');
@@ -142,8 +190,15 @@ export function sanitizeNativeExtensions(input, overrides = {}) {
142
190
  const output = {};
143
191
  const entries = Object.entries(value);
144
192
  for (const [childKey, childValue] of entries.slice(0, limits.maxObjectKeys)) {
145
- output[childKey.replace(/\r?\n/g, ' ').replace(CONTROL_CHARACTERS, '')] =
146
- visit(childValue, childKey, depth + 1);
193
+ const safeKey = redactSessionText(childKey);
194
+ for (const classification of safeKey.classes)
195
+ classes.add(classification);
196
+ for (const [classification, count] of Object.entries(safeKey.decisions)) {
197
+ decisions[classification] = (decisions[classification] ?? 0) + count;
198
+ }
199
+ Object.defineProperty(output, safeKey.text.replace(/\r?\n/g, ' ').replace(CONTROL_CHARACTERS, ''), {
200
+ value: visit(childValue, childKey, depth + 1), enumerable: true, configurable: true, writable: true,
201
+ });
147
202
  }
148
203
  if (entries.length > limits.maxObjectKeys)
149
204
  output.__truncated__ = marker('object-key-limit');
@@ -165,15 +220,79 @@ export function sanitizeNativeExtensions(input, overrides = {}) {
165
220
  };
166
221
  }
167
222
  export function redactSessionText(input) {
168
- const classes = new Set();
169
- let text = input;
223
+ const decisions = {};
224
+ const mark = (classification) => {
225
+ decisions[classification] = (decisions[classification] ?? 0) + 1;
226
+ return `[REDACTED:${classification}]`;
227
+ };
228
+ let text = redactJwts(redactPrivateKeys(input, mark), mark);
170
229
  for (const [classification, pattern] of SECRET_PATTERNS) {
171
- text = text.replace(pattern, () => {
172
- classes.add(classification);
173
- return `[REDACTED:${classification}]`;
174
- });
230
+ // Every email match requires an at-sign. Avoid costly bounded local-part
231
+ // backtracking on large records containing only email-shaped near matches.
232
+ if (classification === 'email' && !text.includes('@'))
233
+ continue;
234
+ text = text.replace(pattern, () => mark(classification));
235
+ }
236
+ const classes = Object.keys(decisions).sort();
237
+ return { text, sensitivity: classes.length ? 'sensitive' : 'none', classes, decisions };
238
+ }
239
+ /** Redact every string, including object keys, without changing canonical field shapes. */
240
+ export function redactSessionValue(input) {
241
+ const decisions = {};
242
+ const active = new WeakSet();
243
+ const text = (value) => {
244
+ const result = redactSessionText(value);
245
+ for (const [key, count] of Object.entries(result.decisions)) {
246
+ decisions[key] = (decisions[key] ?? 0) + count;
247
+ }
248
+ return result.text;
249
+ };
250
+ const visit = (value, depth) => {
251
+ if (typeof value === 'string')
252
+ return text(value);
253
+ if (!value || typeof value !== 'object')
254
+ return value;
255
+ if (depth > 64 || active.has(value))
256
+ return '[REDACTED:structure-limit]';
257
+ active.add(value);
258
+ try {
259
+ return Array.isArray(value)
260
+ ? value.map((item) => visit(item, depth + 1))
261
+ : Object.fromEntries(Object.entries(value).map(([key, item]) => [text(key), visit(item, depth + 1)]));
262
+ }
263
+ finally {
264
+ active.delete(value);
265
+ }
266
+ };
267
+ const value = visit(input, 0);
268
+ return { value, classes: Object.keys(decisions).sort(), decisions };
269
+ }
270
+ /** Shared import boundary for canonical text, metadata and bounded native attributes. */
271
+ export function sanitizeSessionEvent(input) {
272
+ const native = sanitizeNativeExtensions(input.extensions);
273
+ const result = redactSessionValue({ ...input, extensions: native.value });
274
+ const event = result.value;
275
+ const classes = [...new Set([...event.sensitivity.classes, ...native.classes, ...result.classes])].sort();
276
+ event.sensitivity = {
277
+ classification: classes.length ? 'sensitive' : 'none', classes,
278
+ };
279
+ // Counts contain classifications only, never matched input or excerpts.
280
+ const prior = event.extensions['native.redaction'];
281
+ const counts = {};
282
+ for (const item of Array.isArray(prior?.decisions) ? prior.decisions : []) {
283
+ if (item && typeof item.classification === 'string' && Number.isSafeInteger(item.count) && item.count >= 0) {
284
+ counts[item.classification] = item.count;
285
+ }
286
+ }
287
+ for (const decisions of [native.decisions, result.decisions]) {
288
+ for (const [key, count] of Object.entries(decisions)) {
289
+ counts[key] = (counts[key] ?? 0) + count;
290
+ }
175
291
  }
176
- return { text, sensitivity: classes.size ? 'sensitive' : 'none', classes: [...classes].sort() };
292
+ event.extensions['native.redaction'] = {
293
+ decisions: Object.entries(counts).map(([classification, count]) => ({ classification, count })),
294
+ };
295
+ return event;
177
296
  }
178
297
  export function contentFreeAuditEvent(event) {
179
298
  return Object.freeze({ ...event, counts: { ...event.counts } });
@@ -1,15 +1,82 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { createReadStream } from 'node:fs';
3
2
  import { open, readFile } from 'node:fs/promises';
4
- import { createInterface } from 'node:readline';
5
- import { SessionContractError } from './contracts.js';
3
+ import { assertSessionResourceLimit, schemaIssueDetail, SessionContractError } from './contracts.js';
4
+ import { assertImportByteLimit, DEFAULT_IMPORT_BYTE_LIMITS } from './import-limits.js';
6
5
  import { authorizeSourceFile } from './policy.js';
7
6
  export const DEFAULT_READER_LIMITS = Object.freeze({
8
7
  maxRecords: 1_000_000,
9
- maxRecordBytes: 8 * 1024 * 1024,
10
- maxTotalBytes: 1024 * 1024 * 1024,
8
+ ...DEFAULT_IMPORT_BYTE_LIMITS,
11
9
  maxNestingDepth: 64,
12
10
  });
11
+ const JSONL_READ_CHUNK_BYTES = 1024 * 1024;
12
+ export async function* streamJsonLines(path, options = {}) {
13
+ const startOffset = options.start ?? 0;
14
+ if (!Number.isSafeInteger(startOffset) || startOffset < 0)
15
+ throw new RangeError('start must be a nonnegative safe integer');
16
+ if (options.end !== undefined && options.end !== Infinity
17
+ && (!Number.isSafeInteger(options.end) || options.end < 0))
18
+ throw new RangeError('end must be a nonnegative safe integer or Infinity');
19
+ if (options.end !== undefined && options.end < startOffset)
20
+ throw new RangeError('end must not precede start');
21
+ // Reuse raw storage across the full source instead of allocating a new MiB
22
+ // buffer per read. Only incomplete line fragments outlive the current read.
23
+ const storage = Buffer.allocUnsafe(JSONL_READ_CHUNK_BYTES);
24
+ const input = await open(path, 'r');
25
+ let position = startOffset;
26
+ let parts = [];
27
+ let length = 0;
28
+ let total = 0;
29
+ try {
30
+ while (options.end === undefined || position <= options.end) {
31
+ const wanted = options.end === undefined ? storage.length
32
+ : Math.min(storage.length, options.end - position + 1);
33
+ const { bytesRead } = await input.read(storage, 0, wanted, position);
34
+ if (bytesRead === 0)
35
+ break;
36
+ position += bytesRead;
37
+ const buffer = storage.subarray(0, bytesRead);
38
+ let start = 0;
39
+ while (start < buffer.length) {
40
+ const newline = buffer.indexOf(10, start);
41
+ const end = newline === -1 ? buffer.length : newline + 1;
42
+ const segment = buffer.subarray(start, end);
43
+ if (options.limits) {
44
+ const recordBytes = length + segment.length;
45
+ const totalBytes = (options.start ?? 0) + total + segment.length;
46
+ // For an incomplete segment, report the first byte known to exceed
47
+ // the limit instead of making diagnostics depend on read-buffer size.
48
+ assertImportByteLimit('maxRecordBytes', options.limits, newline === -1 && recordBytes > options.limits.maxRecordBytes
49
+ ? options.limits.maxRecordBytes + 1 : recordBytes);
50
+ assertImportByteLimit('maxTotalBytes', options.limits, newline === -1 && options.limits.maxTotalBytes !== 0 && totalBytes > options.limits.maxTotalBytes
51
+ ? options.limits.maxTotalBytes + 1 : totalBytes);
52
+ }
53
+ // Bound the raw bytes before copying or decoding a record. Only LF
54
+ // separates records; CRLF and Unicode separators remain JSON input.
55
+ parts.push(segment);
56
+ length += segment.length;
57
+ total += segment.length;
58
+ start = end;
59
+ if (newline !== -1) {
60
+ const line = (parts.length === 1 ? parts[0] : Buffer.concat(parts, length)).toString('utf8');
61
+ const byteLength = length;
62
+ parts = [];
63
+ length = 0;
64
+ yield { line, byteLength };
65
+ }
66
+ }
67
+ // Earlier fragments already own their bytes. The last fragment is the
68
+ // only view into storage, which the next read will overwrite.
69
+ if (parts.length > 0)
70
+ parts[parts.length - 1] = Buffer.from(parts[parts.length - 1]);
71
+ }
72
+ if (length > 0) {
73
+ yield { line: (parts.length === 1 ? parts[0] : Buffer.concat(parts, length)).toString('utf8'), byteLength: length };
74
+ }
75
+ }
76
+ finally {
77
+ await input.close();
78
+ }
79
+ }
13
80
  export async function readBoundedJsonLines(authorization, options) {
14
81
  const stream = await streamBoundedJsonLines(authorization, options);
15
82
  const records = [];
@@ -28,50 +95,36 @@ export async function streamBoundedJsonLines(authorization, options) {
28
95
  const limits = { ...DEFAULT_READER_LIMITS, ...options.limits };
29
96
  const start = parseCursor(options.cursor);
30
97
  if (start > allowed.size)
31
- throw new SessionContractError('SCHEMA_DRIFT', 'reader cursor is beyond source size');
98
+ throw new SessionContractError('SCHEMA_DRIFT', 'reader cursor is beyond source size', schemaIssueDetail('cursor'));
32
99
  let offset = start;
33
100
  let total = 0;
34
101
  let count = 0;
35
102
  let incompleteTail = false;
36
103
  const iterate = async function* () {
37
- const input = createReadStream(allowed.canonicalPath, { start, encoding: 'utf8' });
38
- const lines = createInterface({ input, crlfDelay: Infinity });
39
- try {
40
- for await (const line of lines) {
41
- const bytes = Buffer.byteLength(line) + 1;
42
- if (bytes > limits.maxRecordBytes || total + bytes > limits.maxTotalBytes
43
- || count + 1 > limits.maxRecords) {
44
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'bounded session reader limit exceeded');
45
- }
46
- if (line.trim() === '') {
47
- offset += bytes;
48
- total += bytes;
49
- continue;
50
- }
51
- let value;
52
- try {
53
- value = JSON.parse(line);
54
- }
55
- catch {
56
- if (options.consistency === 'provisional') {
57
- incompleteTail = true;
58
- break;
59
- }
60
- throw new SessionContractError('SCHEMA_DRIFT', 'malformed JSONL record in consistent source');
61
- }
62
- if (jsonDepth(value) > limits.maxNestingDepth) {
63
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'JSON nesting depth exceeds reader limit');
64
- }
65
- const record = { value, sequence: count, byteOffset: offset, byteLength: bytes };
66
- count += 1;
104
+ for await (const { line, byteLength: bytes } of streamJsonLines(allowed.canonicalPath, { start, limits })) {
105
+ assertSessionResourceLimit('maxRecords', limits.maxRecords, count + 1);
106
+ if (line.trim() === '') {
67
107
  offset += bytes;
68
108
  total += bytes;
69
- yield record;
109
+ continue;
70
110
  }
71
- }
72
- finally {
73
- lines.close();
74
- input.destroy();
111
+ let value;
112
+ try {
113
+ value = JSON.parse(line);
114
+ }
115
+ catch {
116
+ if (options.consistency === 'provisional') {
117
+ incompleteTail = true;
118
+ break;
119
+ }
120
+ throw new SessionContractError('SCHEMA_DRIFT', 'malformed JSONL record in consistent source', schemaIssueDetail('<root>', 'invalid_json'));
121
+ }
122
+ assertSessionResourceLimit('maxNestingDepth', limits.maxNestingDepth, jsonDepth(value));
123
+ const record = { value, sequence: count, byteOffset: offset, byteLength: bytes };
124
+ count += 1;
125
+ offset += bytes;
126
+ total += bytes;
127
+ yield record;
75
128
  }
76
129
  };
77
130
  return {
@@ -83,30 +136,30 @@ export async function streamBoundedJsonLines(authorization, options) {
83
136
  [Symbol.asyncIterator]: iterate,
84
137
  };
85
138
  }
139
+ // Single-document JSON and text exports cannot be incrementally normalized.
140
+ export const MAX_BUFFERED_SOURCE_BYTES = 64 * 1024 * 1024;
86
141
  export async function readBoundedJson(authorization, limitsInput) {
87
142
  const allowed = await authorizeSourceFile(authorization);
88
143
  const limits = { ...DEFAULT_READER_LIMITS, ...limitsInput };
89
- if (allowed.size > limits.maxTotalBytes || allowed.size > limits.maxRecordBytes) {
90
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'bounded JSON source limit exceeded');
91
- }
144
+ assertImportByteLimit('maxTotalBytes', limits, allowed.size);
145
+ assertImportByteLimit('maxRecordBytes', limits, allowed.size);
146
+ assertSessionResourceLimit('maxBufferedSourceBytes', MAX_BUFFERED_SOURCE_BYTES, allowed.size);
92
147
  let value;
93
148
  try {
94
149
  value = JSON.parse(await readFile(allowed.canonicalPath, 'utf8'));
95
150
  }
96
151
  catch {
97
- throw new SessionContractError('SCHEMA_DRIFT', 'malformed JSON source');
98
- }
99
- if (jsonDepth(value) > limits.maxNestingDepth) {
100
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'JSON nesting depth exceeds reader limit');
152
+ throw new SessionContractError('SCHEMA_DRIFT', 'malformed JSON source', schemaIssueDetail('<root>', 'invalid_json'));
101
153
  }
154
+ assertSessionResourceLimit('maxNestingDepth', limits.maxNestingDepth, jsonDepth(value));
102
155
  return { value, bytesRead: allowed.size };
103
156
  }
104
157
  export async function readBoundedText(authorization, limitsInput) {
105
158
  const allowed = await authorizeSourceFile(authorization);
106
159
  const limits = { ...DEFAULT_READER_LIMITS, ...limitsInput };
107
- if (allowed.size > limits.maxTotalBytes || allowed.size > limits.maxRecordBytes) {
108
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'bounded text source limit exceeded');
109
- }
160
+ assertImportByteLimit('maxTotalBytes', limits, allowed.size);
161
+ assertImportByteLimit('maxRecordBytes', limits, allowed.size);
162
+ assertSessionResourceLimit('maxBufferedSourceBytes', MAX_BUFFERED_SOURCE_BYTES, allowed.size);
110
163
  return {
111
164
  value: await readFile(allowed.canonicalPath, 'utf8'),
112
165
  bytesRead: allowed.size,
@@ -136,7 +189,7 @@ export async function fingerprintSourcePrefix(authorization, length, skipPrefixB
136
189
  const allowed = await authorizeSourceFile(authorization);
137
190
  if (!Number.isSafeInteger(length) || length < 0 || length > allowed.size
138
191
  || !Number.isSafeInteger(skipPrefixBytes) || skipPrefixBytes < 0 || skipPrefixBytes > allowed.size) {
139
- throw new SessionContractError('SCHEMA_DRIFT', 'checkpoint source position is beyond the current source size');
192
+ throw new SessionContractError('SCHEMA_DRIFT', 'checkpoint source position is beyond the current source size', schemaIssueDetail('cursor'));
140
193
  }
141
194
  const handle = await open(allowed.canonicalPath, 'r');
142
195
  const hash = createHash('sha256');
@@ -165,7 +218,7 @@ function parseCursor(cursor) {
165
218
  if (cursor === undefined || cursor === '')
166
219
  return 0;
167
220
  if (!/^\d+$/.test(cursor))
168
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid reader cursor');
221
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid reader cursor', schemaIssueDetail('cursor'));
169
222
  return Number(cursor);
170
223
  }
171
224
  function jsonDepth(value, depth = 0) {
@@ -0,0 +1,105 @@
1
+ import { existsSync, realpathSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { requireFeaturePackage } from '../features/runtime.js';
4
+ import { readAiwgConfig } from '../config/aiwg-config.js';
5
+ import { createSessionAdapterRegistry } from './adapter-registry.js';
6
+ import { resolveImportExclusions } from './import-exclusions.js';
7
+ import { resolveImportLimits } from './import-limits.js';
8
+ import { acquireImportLease } from './import-lease.js';
9
+ import { SessionRepository } from './repository.js';
10
+ import { importDiscoveryManifest } from './batch-import.js';
11
+ import { defaultDiscoveryManifestPath, discoverWorkspaceHistories, readDiscoveryManifest, writeDiscoveryManifest, } from './workspace-discovery.js';
12
+ export function sessionRefreshAvailable() {
13
+ try {
14
+ const Database = requireFeaturePackage('better-sqlite3');
15
+ const probe = new Database(':memory:');
16
+ probe.close();
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ /** Refresh current configured scope within saved provider root grants. */
24
+ export async function refreshSessionCatalog(workspace, full, waitMs, signal, workspaceAliases = [], flags = {}) {
25
+ const canonicalWorkspace = realpathSync(workspace);
26
+ const config = await readAiwgConfig(canonicalWorkspace);
27
+ const limits = resolveImportLimits(config?.sessions?.import, flags);
28
+ const exclude = resolveImportExclusions(config?.sessions?.import, flags.exclude);
29
+ const database = path.join(workspace, '.aiwg/sessions/catalog.sqlite');
30
+ const manifests = [];
31
+ if (!full) {
32
+ const paths = new Set([
33
+ defaultDiscoveryManifestPath(canonicalWorkspace),
34
+ ...(config?.sessions?.discovery_manifests ?? []).map(file => path.resolve(canonicalWorkspace, file)),
35
+ ]);
36
+ let found = false;
37
+ for (const manifestPath of paths) {
38
+ if (!existsSync(manifestPath))
39
+ continue;
40
+ const previous = await readDiscoveryManifest(manifestPath);
41
+ if (previous.workspaceId !== canonicalWorkspace)
42
+ throw new Error('discovery manifest workspace mismatch');
43
+ found = true;
44
+ const authorizedRoots = previous.authorizedRoots ?? {};
45
+ if (!previous.authorizedRoots) {
46
+ for (const source of previous.sources) {
47
+ const roots = authorizedRoots[source.provider] ??= [];
48
+ if (!roots.includes(source.authorizedRoot))
49
+ roots.push(source.authorizedRoot);
50
+ }
51
+ }
52
+ const providers = Object.keys(authorizedRoots)
53
+ .filter(provider => authorizedRoots[provider]?.length);
54
+ if (!providers.length)
55
+ continue;
56
+ const manifest = await discoverWorkspaceHistories({
57
+ workspace: canonicalWorkspace,
58
+ workspaceAliases: [workspace, ...workspaceAliases],
59
+ refreshFrom: { ...previous, authorizedRoots },
60
+ registry: createSessionAdapterRegistry(limits),
61
+ exclude,
62
+ knownSources: previous.sources,
63
+ });
64
+ manifests.push({ path: manifestPath, manifest });
65
+ }
66
+ if (!found)
67
+ return 'no authorized discovery manifest';
68
+ if (!manifests.length)
69
+ return 'no authorized provider roots';
70
+ }
71
+ const lease = await acquireImportLease(database, `index-refresh-${process.pid}`, { waitMs });
72
+ try {
73
+ const repository = new SessionRepository(database);
74
+ try {
75
+ if (full)
76
+ repository.reindex();
77
+ else {
78
+ const receipts = [];
79
+ for (const entry of manifests) {
80
+ const prior = repository.getBatchImportRunForManifest(entry.manifest.manifestId, canonicalWorkspace);
81
+ if (prior?.status === 'complete')
82
+ continue;
83
+ // Persist the exact discovered batch before its confirmed import.
84
+ await writeDiscoveryManifest(entry.path, entry.manifest);
85
+ const receipt = await importDiscoveryManifest({ manifest: entry.manifest, repository, signal, limits, exclude });
86
+ if (receipt.totals.pending) {
87
+ throw new Error(`session import incomplete: ${receipt.totals.rejected} rejected, ${receipt.totals.pending} pending`);
88
+ }
89
+ receipts.push(receipt);
90
+ }
91
+ if (!receipts.length)
92
+ return 'unchanged';
93
+ return receipts.length === 1 ? receipts[0] : receipts;
94
+ }
95
+ }
96
+ finally {
97
+ repository.close();
98
+ }
99
+ }
100
+ finally {
101
+ await lease.release();
102
+ }
103
+ return undefined;
104
+ }
105
+ //# sourceMappingURL=refresh.js.map