@aiwg/cli 2026.10.3 → 2026.10.4

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 +57 -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 +285 -131
  21. package/dist/src/artifacts/index-files.js +132 -18
  22. package/dist/src/artifacts/index-preparation.js +65 -0
  23. package/dist/src/artifacts/index-refresh.js +576 -0
  24. package/dist/src/artifacts/index-worker.mjs +9 -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 +98 -37
  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 +82 -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 +51 -11
  207. package/dist/src/sessions/contracts.js +40 -2
  208. package/dist/src/sessions/coverage-preflight.js +1 -1
  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 +133 -25
  216. package/dist/src/sessions/readers.js +106 -53
  217. package/dist/src/sessions/refresh.js +106 -0
  218. package/dist/src/sessions/repository.js +147 -52
  219. package/dist/src/sessions/workspace-discovery.js +221 -56
  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,51 @@ 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
+ // Ambiguous token components are bounded; whitespace and key blocks use disjoint scans.
57
60
  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],
61
+ ['bearer-token', /\bBearer[ \t]+[A-Za-z0-9._~+/=-]{12,}/gi],
62
+ ['authorization', /\bAuthorization[ \t]*:[ \t]*[^\r\n]+/gi],
63
+ ['cookie', /\b(?:Cookie|Set-Cookie)[ \t]*:[ \t]*[^\r\n]+/gi],
64
+ ['environment-assignment', /\b(?:AWS_SECRET_ACCESS_KEY|[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY))[ \t]*=[ \t]*(?:"[^"\r\n]+"|'[^'\r\n]+'|[^\s]+)/gi],
65
+ ['credential', /\b(?:api[_-]?key|token|password|secret)[ \t]*[:=][ \t]*["']?[^\s"',;]{6,}/gi],
66
+ ['credential', /^[ \t]*password[ \t]+[^\s]+[ \t]*$/gim],
67
+ // Space-separated values only when they look generated (20+ chars, letters and digits), so prose stays.
68
+ ['credential', /\b(?:api[_-]?key|token|secret)[ \t]+(?=[A-Za-z0-9._~+/=-]*[0-9])(?=[A-Za-z0-9._~+/=-]*[A-Za-z])[A-Za-z0-9._~+/=-]{20,}/gi],
69
+ ['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],
70
+ ['provider-token', /https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+/g],
64
71
  ['connection-string', /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis):\/\/[^\s]+/gi],
65
72
  ['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],
67
73
  ['fixture-credential', /\bredaction-canary-[A-Za-z0-9_-]+\b/gi],
68
- ['email', /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi],
74
+ ['email', /\b[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\.[A-Z]{2,63}\b/gi],
69
75
  ];
76
+ function redactJwts(input, mark) {
77
+ // Consume even incomplete candidates. Retrying at each inner `eyJ` in an
78
+ // unterminated segment would repeatedly scan the remainder of a large record.
79
+ return input.replace(/\beyJ[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+){0,2}/g, (candidate) => {
80
+ const parts = candidate.split('.');
81
+ return parts.length === 3 && parts[0].length >= 8 && parts[1].length >= 2 && parts[2].length >= 8
82
+ ? mark('provider-token') : candidate;
83
+ });
84
+ }
85
+ const PRIVATE_KEY_BEGIN = /-----BEGIN ((?:(?:RSA|EC|DSA|OPENSSH|ENCRYPTED) )?PRIVATE KEY|PGP PRIVATE KEY BLOCK)-----/g;
86
+ function redactPrivateKeys(input, mark) {
87
+ const parts = [];
88
+ let cursor = 0;
89
+ PRIVATE_KEY_BEGIN.lastIndex = 0;
90
+ let match;
91
+ while ((match = PRIVATE_KEY_BEGIN.exec(input))) {
92
+ parts.push(input.slice(cursor, match.index), mark('private-key'));
93
+ const endMarker = `-----END ${match[1]}-----`;
94
+ const end = input.indexOf(endMarker, PRIVATE_KEY_BEGIN.lastIndex);
95
+ // An unfinished key block is sensitive through the end of the record.
96
+ cursor = end < 0 ? input.length : end + endMarker.length;
97
+ PRIVATE_KEY_BEGIN.lastIndex = cursor;
98
+ }
99
+ parts.push(input.slice(cursor));
100
+ return parts.join('');
101
+ }
70
102
  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;
103
+ const CONTENT_KEY = /(?:^|[_-])(?:data|text|content|prompt|command|arguments?|args|result|output|body|request|response|source|code|query)(?:$|[_-])/i;
72
104
  const PATH_KEY = /(?:^|[_-])(?:path|cwd|directory|filename|file)(?:$|[_-])/i;
73
105
  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
106
  const CONTROL_CHARACTERS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g;
@@ -103,23 +135,28 @@ export function sanitizeNativeExtensions(input, overrides = {}) {
103
135
  return marker('depth-limit');
104
136
  if (value === null || typeof value === 'boolean' || typeof value === 'number')
105
137
  return value;
138
+ if (typeof value === 'string' && /^\[REDACTED:[a-z-]+\]$/.test(value))
139
+ return value;
140
+ if (SENSITIVE_KEY.test(classifiedKey))
141
+ return marker('sensitive-field');
142
+ if (CONTENT_KEY.test(classifiedKey))
143
+ return marker('content');
144
+ if (PATH_KEY.test(classifiedKey))
145
+ return marker('path');
106
146
  if (typeof value === 'bigint')
107
147
  return String(value);
108
148
  if (typeof value !== 'string' && typeof value !== 'object') {
109
149
  return marker('unsupported-value');
110
150
  }
111
151
  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
152
  if (Buffer.byteLength(value) > limits.maxStringBytes)
119
153
  return marker('string-limit');
120
154
  const redacted = redactSessionText(value);
121
155
  for (const classification of redacted.classes)
122
156
  classes.add(classification);
157
+ for (const [classification, count] of Object.entries(redacted.decisions)) {
158
+ decisions[classification] = (decisions[classification] ?? 0) + count;
159
+ }
123
160
  if (redacted.classes.length) {
124
161
  decisions['classified-value'] = (decisions['classified-value'] ?? 0) + 1;
125
162
  return redacted.text.replace(CONTROL_CHARACTERS, ' ');
@@ -142,8 +179,15 @@ export function sanitizeNativeExtensions(input, overrides = {}) {
142
179
  const output = {};
143
180
  const entries = Object.entries(value);
144
181
  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);
182
+ const safeKey = redactSessionText(childKey);
183
+ for (const classification of safeKey.classes)
184
+ classes.add(classification);
185
+ for (const [classification, count] of Object.entries(safeKey.decisions)) {
186
+ decisions[classification] = (decisions[classification] ?? 0) + count;
187
+ }
188
+ Object.defineProperty(output, safeKey.text.replace(/\r?\n/g, ' ').replace(CONTROL_CHARACTERS, ''), {
189
+ value: visit(childValue, childKey, depth + 1), enumerable: true, configurable: true, writable: true,
190
+ });
147
191
  }
148
192
  if (entries.length > limits.maxObjectKeys)
149
193
  output.__truncated__ = marker('object-key-limit');
@@ -165,15 +209,79 @@ export function sanitizeNativeExtensions(input, overrides = {}) {
165
209
  };
166
210
  }
167
211
  export function redactSessionText(input) {
168
- const classes = new Set();
169
- let text = input;
212
+ const decisions = {};
213
+ const mark = (classification) => {
214
+ decisions[classification] = (decisions[classification] ?? 0) + 1;
215
+ return `[REDACTED:${classification}]`;
216
+ };
217
+ let text = redactJwts(redactPrivateKeys(input, mark), mark);
170
218
  for (const [classification, pattern] of SECRET_PATTERNS) {
171
- text = text.replace(pattern, () => {
172
- classes.add(classification);
173
- return `[REDACTED:${classification}]`;
174
- });
219
+ // Every email match requires an at-sign. Avoid costly bounded local-part
220
+ // backtracking on large records containing only email-shaped near matches.
221
+ if (classification === 'email' && !text.includes('@'))
222
+ continue;
223
+ text = text.replace(pattern, () => mark(classification));
175
224
  }
176
- return { text, sensitivity: classes.size ? 'sensitive' : 'none', classes: [...classes].sort() };
225
+ const classes = Object.keys(decisions).sort();
226
+ return { text, sensitivity: classes.length ? 'sensitive' : 'none', classes, decisions };
227
+ }
228
+ /** Redact every string, including object keys, without changing canonical field shapes. */
229
+ export function redactSessionValue(input) {
230
+ const decisions = {};
231
+ const active = new WeakSet();
232
+ const text = (value) => {
233
+ const result = redactSessionText(value);
234
+ for (const [key, count] of Object.entries(result.decisions)) {
235
+ decisions[key] = (decisions[key] ?? 0) + count;
236
+ }
237
+ return result.text;
238
+ };
239
+ const visit = (value, depth) => {
240
+ if (typeof value === 'string')
241
+ return text(value);
242
+ if (!value || typeof value !== 'object')
243
+ return value;
244
+ if (depth > 64 || active.has(value))
245
+ return '[REDACTED:structure-limit]';
246
+ active.add(value);
247
+ try {
248
+ return Array.isArray(value)
249
+ ? value.map((item) => visit(item, depth + 1))
250
+ : Object.fromEntries(Object.entries(value).map(([key, item]) => [text(key), visit(item, depth + 1)]));
251
+ }
252
+ finally {
253
+ active.delete(value);
254
+ }
255
+ };
256
+ const value = visit(input, 0);
257
+ return { value, classes: Object.keys(decisions).sort(), decisions };
258
+ }
259
+ /** Shared import boundary for canonical text, metadata and bounded native attributes. */
260
+ export function sanitizeSessionEvent(input) {
261
+ const native = sanitizeNativeExtensions(input.extensions);
262
+ const result = redactSessionValue({ ...input, extensions: native.value });
263
+ const event = result.value;
264
+ const classes = [...new Set([...event.sensitivity.classes, ...native.classes, ...result.classes])].sort();
265
+ event.sensitivity = {
266
+ classification: classes.length ? 'sensitive' : 'none', classes,
267
+ };
268
+ // Counts contain classifications only, never matched input or excerpts.
269
+ const prior = event.extensions['native.redaction'];
270
+ const counts = {};
271
+ for (const item of Array.isArray(prior?.decisions) ? prior.decisions : []) {
272
+ if (item && typeof item.classification === 'string' && Number.isSafeInteger(item.count) && item.count >= 0) {
273
+ counts[item.classification] = item.count;
274
+ }
275
+ }
276
+ for (const decisions of [native.decisions, result.decisions]) {
277
+ for (const [key, count] of Object.entries(decisions)) {
278
+ counts[key] = (counts[key] ?? 0) + count;
279
+ }
280
+ }
281
+ event.extensions['native.redaction'] = {
282
+ decisions: Object.entries(counts).map(([classification, count]) => ({ classification, count })),
283
+ };
284
+ return event;
177
285
  }
178
286
  export function contentFreeAuditEvent(event) {
179
287
  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,106 @@
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 an existing catalog without inferring or granting provider access. */
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
+ expectedProviders: providers,
60
+ authorizedRoots,
61
+ registry: createSessionAdapterRegistry(limits),
62
+ exclude,
63
+ knownSources: previous.sources,
64
+ });
65
+ manifests.push({ path: manifestPath, manifest });
66
+ }
67
+ if (!found)
68
+ return 'no authorized discovery manifest';
69
+ if (!manifests.length)
70
+ return 'no authorized provider roots';
71
+ }
72
+ const lease = await acquireImportLease(database, `index-refresh-${process.pid}`, { waitMs });
73
+ try {
74
+ const repository = new SessionRepository(database);
75
+ try {
76
+ if (full)
77
+ repository.reindex();
78
+ else {
79
+ const receipts = [];
80
+ for (const entry of manifests) {
81
+ const prior = repository.getBatchImportRunForManifest(entry.manifest.manifestId, canonicalWorkspace);
82
+ if (prior?.status === 'complete')
83
+ continue;
84
+ // Persist the exact discovered batch before its confirmed import.
85
+ await writeDiscoveryManifest(entry.path, entry.manifest);
86
+ const receipt = await importDiscoveryManifest({ manifest: entry.manifest, repository, signal, limits, exclude });
87
+ if (receipt.totals.pending) {
88
+ throw new Error(`session import incomplete: ${receipt.totals.rejected} rejected, ${receipt.totals.pending} pending`);
89
+ }
90
+ receipts.push(receipt);
91
+ }
92
+ if (!receipts.length)
93
+ return 'unchanged';
94
+ return receipts.length === 1 ? receipts[0] : receipts;
95
+ }
96
+ }
97
+ finally {
98
+ repository.close();
99
+ }
100
+ }
101
+ finally {
102
+ await lease.release();
103
+ }
104
+ return undefined;
105
+ }
106
+ //# sourceMappingURL=refresh.js.map