@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
@@ -1,7 +1,7 @@
1
1
  import { basename } from 'node:path';
2
2
  import { z } from 'zod';
3
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
4
- import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
3
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
4
+ import { streamBoundedJsonLines, } from '../readers.js';
5
5
  export const DEVIN_DESKTOP_ADAPTER_VERSION = '1.1.0';
6
6
  export const DEVIN_DESKTOP_TRANSCRIPT_SCHEMA_VERSION = '1.0.0';
7
7
  /** @deprecated Compatibility export. */
@@ -38,19 +38,14 @@ export class DevinDesktopSessionAdapter {
38
38
  // Deliberately empty: hook enablement and transcript selection are explicit user actions.
39
39
  }
40
40
  async inspect(source) {
41
- const parsed = await this.readSource(source);
42
- return {
43
- sourceSchemaVersion: parsed.schemaVersion,
44
- consistency: 'provisional',
45
- operationalState: 'available',
46
- };
41
+ const records = this.stream(source);
42
+ let result = await records.next();
43
+ while (!result.done)
44
+ result = await records.next();
45
+ return result.value;
47
46
  }
48
47
  async *stream(source, cursor) {
49
- if (!isCurrentHookLocator(source.locatorClass)) {
50
- // Preserve the precise unsupported-operation diagnostics.
51
- await this.readSource(source);
52
- return;
53
- }
48
+ this.assertSupportedLocator(source);
54
49
  const start = parseCursor(cursor?.value);
55
50
  const input = await streamBoundedJsonLines({
56
51
  selectedPath: source.locator,
@@ -62,12 +57,12 @@ export class DevinDesktopSessionAdapter {
62
57
  for await (const line of input) {
63
58
  const parsed = StepSchema.safeParse(line.value);
64
59
  if (!parsed.success) {
65
- throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript step is malformed');
60
+ throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript step is malformed', schemaRejectionDetail(parsed.error));
66
61
  }
67
62
  const step = parsed.data;
68
63
  const currentVersion = step.schema_version ?? DEVIN_DESKTOP_TRANSCRIPT_SCHEMA_VERSION;
69
64
  if (schemaVersion && schemaVersion !== currentVersion) {
70
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf transcript schemas');
65
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf transcript schemas', schemaIssueDetail('schema_version'));
71
66
  }
72
67
  schemaVersion = currentVersion;
73
68
  assertSupportedSchemaMajor(schemaVersion);
@@ -75,7 +70,7 @@ export class DevinDesktopSessionAdapter {
75
70
  ?? nativeSessionId
76
71
  ?? basename(source.locator, '.jsonl');
77
72
  if (step.trajectory_id && nativeSessionId && step.trajectory_id !== nativeSessionId) {
78
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf trajectory identities');
73
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf trajectory identities', schemaIssueDetail('trajectory_id'));
79
74
  }
80
75
  nativeSessionId = currentSession;
81
76
  if (seen++ < start)
@@ -83,45 +78,21 @@ export class DevinDesktopSessionAdapter {
83
78
  yield normalizeStep(step, currentSession, line.sequence, line.byteOffset, canonicalLocatorClass(source.locatorClass), schemaVersion);
84
79
  }
85
80
  if (seen === 0) {
86
- throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript is empty or malformed');
81
+ throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript is empty or malformed', schemaIssueDetail('<root>'));
87
82
  }
83
+ return {
84
+ sourceSchemaVersion: schemaVersion,
85
+ consistency: 'provisional',
86
+ operationalState: 'available',
87
+ };
88
88
  }
89
- async readSource(source) {
89
+ assertSupportedLocator(source) {
90
90
  if (isLegacyLocator(source.locatorClass)) {
91
91
  throw new SessionContractError('UNSUPPORTED_OPERATION', 'legacy Devin Desktop/Windsurf protobuf stores are unsupported; enable post_cascade_response_with_transcript and select its JSONL output');
92
92
  }
93
93
  if (!isCurrentHookLocator(source.locatorClass)) {
94
94
  throw new SessionContractError('UNSUPPORTED_OPERATION', 'Devin Desktop (Windsurf compatibility) requires an explicitly selected post_cascade_response_with_transcript JSONL file');
95
95
  }
96
- const input = await readBoundedJsonLines({
97
- selectedPath: source.locator,
98
- allowedRoots: source.authorizedScope.allowedRoots,
99
- }, { consistency: 'provisional', limits: this.limits });
100
- if (input.records.length === 0) {
101
- throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript is empty or malformed');
102
- }
103
- const steps = input.records.map((record) => {
104
- const parsed = StepSchema.safeParse(record.value);
105
- if (!parsed.success) {
106
- throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript step is malformed');
107
- }
108
- return { ...record, value: parsed.data };
109
- });
110
- const versions = new Set(steps.map(({ value }) => value.schema_version ?? DEVIN_DESKTOP_TRANSCRIPT_SCHEMA_VERSION));
111
- if (versions.size !== 1) {
112
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf transcript schemas');
113
- }
114
- const schemaVersion = [...versions][0];
115
- assertSupportedSchemaMajor(schemaVersion);
116
- const trajectoryIds = new Set(steps.flatMap(({ value }) => value.trajectory_id ? [value.trajectory_id] : []));
117
- if (trajectoryIds.size > 1) {
118
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf trajectory identities');
119
- }
120
- const nativeSessionId = [...trajectoryIds][0] ?? basename(source.locator, '.jsonl');
121
- return {
122
- schemaVersion,
123
- records: steps.map(({ value, sequence, byteOffset }) => normalizeStep(value, nativeSessionId, sequence, byteOffset, canonicalLocatorClass(source.locatorClass), schemaVersion)),
124
- };
125
96
  }
126
97
  }
127
98
  /** @deprecated Use DevinDesktopSessionAdapter; retained through the alias window. */
@@ -206,7 +177,7 @@ function parseCursor(value) {
206
177
  if (!value)
207
178
  return 0;
208
179
  if (!/^\d+$/.test(value))
209
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Windsurf cursor');
180
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Windsurf cursor', schemaIssueDetail('cursor'));
210
181
  return Number(value);
211
182
  }
212
183
  //# sourceMappingURL=windsurf.js.map
@@ -1,6 +1,6 @@
1
1
  import { reconcileCoveragePreflight } from './coverage-preflight.js';
2
- export const BATCH_IMPORT_VERSION = '1.0.0';
3
- export const COVERAGE_VERSION = '1.0.0';
2
+ export const BATCH_IMPORT_VERSION = '2.0.0';
3
+ export const COVERAGE_VERSION = '2.0.0';
4
4
  export function coverageFromBatchRun(run, now = new Date(), staleAfterMs = 24 * 60 * 60 * 1_000) {
5
5
  if (!run)
6
6
  return unknownCoverage();
@@ -9,6 +9,7 @@ export function coverageFromBatchRun(run, now = new Date(), staleAfterMs = 24 *
9
9
  ]);
10
10
  const accepted = run.sources.filter((source) => source.status === 'committed').length;
11
11
  const rejected = run.sources.filter((source) => source.status === 'rejected').length;
12
+ const excluded = run.sources.filter(source => source.status === 'excluded').length;
12
13
  const skipped = run.sources.filter((source) => source.status === 'skipped').length;
13
14
  const duplicated = run.sources.filter((source) => source.status === 'duplicate').length;
14
15
  const previouslyCommitted = run.sources.filter((source) => source.status === 'previously-committed').length;
@@ -20,9 +21,12 @@ export function coverageFromBatchRun(run, now = new Date(), staleAfterMs = 24 *
20
21
  const manifestAgeMs = Math.max(0, now.getTime() - Date.parse(run.manifestCreatedAt));
21
22
  const isStale = manifestAgeMs > staleAfterMs;
22
23
  const preflight = run.preflight ? reconcileCoveragePreflight(run.preflight, run.sources) : undefined;
23
- const incomplete = !preflight || preflight.status !== 'complete'
24
- || unavailable.length > 0 || accepted + duplicated + previouslyCommitted === 0
25
- || rejected > 0 || pending > 0 || skipped > 0
24
+ const unresolvedProviders = preflight?.unresolvedProviders
25
+ ?? [...new Set([...unavailable, ...exportRequired, ...notChecked])].sort();
26
+ const acquisitionIncomplete = rejected > 0 || pending > 0 || skipped > 0
27
+ || (preflight && ['explicit', 'workspace-config'].includes(preflight.expectedProviderBasis) && unresolvedProviders.length > 0);
28
+ const incomplete = acquisitionIncomplete || !preflight || preflight.status !== 'complete'
29
+ || unavailable.length > 0 || accepted + duplicated + previouslyCommitted + excluded === 0
26
30
  || exportRequired.length > 0 || notChecked.length > 0;
27
31
  const rejectionCounts = {};
28
32
  for (const source of run.sources) {
@@ -54,7 +58,8 @@ export function coverageFromBatchRun(run, now = new Date(), staleAfterMs = 24 *
54
58
  return {
55
59
  schemaVersion: COVERAGE_VERSION,
56
60
  status: isStale ? 'stale' : incomplete ? 'partial' : 'complete',
57
- acquisitionStatus: incomplete ? 'partial' : 'complete',
61
+ acquisitionStatus: acquisitionIncomplete ? 'partial' : 'complete',
62
+ unresolvedProviders,
58
63
  preflight,
59
64
  semanticReview: 'not-assessed',
60
65
  workspaceId: run.workspaceId,
@@ -67,6 +72,7 @@ export function coverageFromBatchRun(run, now = new Date(), staleAfterMs = 24 *
67
72
  discovered: run.sources.length,
68
73
  accepted,
69
74
  rejected,
75
+ excluded,
70
76
  skipped,
71
77
  duplicated,
72
78
  previouslyCommitted,
@@ -74,9 +80,9 @@ export function coverageFromBatchRun(run, now = new Date(), staleAfterMs = 24 *
74
80
  },
75
81
  sessionsAccepted: run.sources.reduce((sum, source) => sum + source.sessionsAccepted, 0),
76
82
  eventsAccepted: run.sources.reduce((sum, source) => sum + source.eventsAccepted, 0),
77
- coverageRatio: run.sources.length === 0
83
+ coverageRatio: run.sources.length === excluded
78
84
  ? null
79
- : (accepted + duplicated + previouslyCommitted) / run.sources.length,
85
+ : (accepted + duplicated + previouslyCommitted) / (run.sources.length - excluded),
80
86
  rejectionCounts,
81
87
  sourceDateRange: {
82
88
  earliest: sourceTimestamps.at(0) ?? null,
@@ -100,6 +106,7 @@ function unknownCoverage() {
100
106
  schemaVersion: COVERAGE_VERSION,
101
107
  status: 'unknown',
102
108
  acquisitionStatus: 'unknown',
109
+ unresolvedProviders: [],
103
110
  semanticReview: 'not-assessed',
104
111
  workspaceId: '',
105
112
  manifestId: null,
@@ -116,6 +123,7 @@ function unknownCoverage() {
116
123
  discovered: 0,
117
124
  accepted: 0,
118
125
  rejected: 0,
126
+ excluded: 0,
119
127
  skipped: 0,
120
128
  duplicated: 0,
121
129
  previouslyCommitted: 0,
@@ -1,11 +1,25 @@
1
- import { stat } from 'node:fs/promises';
1
+ import { mkdtemp, rm, stat } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
2
4
  import { createSessionAdapterRegistry } from './adapter-registry.js';
3
5
  import { coveragePreflight, sessionRuntimeIdentity } from './coverage-preflight.js';
4
- import { SESSION_CONTRACT_VERSION, SessionContractError, SessionSourceSchema, sha256, } from './contracts.js';
6
+ import { SESSION_CONTRACT_VERSION, SessionContractError, schemaIssueDetail, SessionSourceSchema, sha256, stableSessionId, stableEventId, } from './contracts.js';
5
7
  import { BATCH_IMPORT_VERSION, coverageFromBatchRun, } from './batch-contracts.js';
6
8
  import { IncrementalSessionImporter, SessionImportFailure } from './importer.js';
9
+ import { matchImportExclusion, resolveImportExclusions } from './import-exclusions.js';
10
+ import { resolveImportLimits } from './import-limits.js';
7
11
  import { fingerprintSourceFile } from './readers.js';
12
+ import { SessionRepository } from './repository.js';
13
+ import { redactSessionValue, sanitizeSessionEvent } from './policy.js';
14
+ import { deriveSessionIntent } from './origin.js';
15
+ import { recoverCodexRolloutIdentity } from './adapters/codex.js';
8
16
  export async function importDiscoveryManifest(options) {
17
+ if (options.carryForward)
18
+ return rebuildFromCatalog(options);
19
+ const exclusions = resolveImportExclusions(undefined, options.exclude);
20
+ const limits = { ...resolveImportLimits(undefined, {
21
+ maxRecordBytes: options.limits?.maxRecordBytes, maxSourceBytes: options.limits?.maxTotalBytes,
22
+ }), ...options.limits };
9
23
  const now = options.now ?? (() => new Date());
10
24
  const runId = sha256([
11
25
  'workspace-import-v1',
@@ -17,7 +31,10 @@ export async function importDiscoveryManifest(options) {
17
31
  if (run.runId !== runId) {
18
32
  throw new SessionContractError('IMPORT_CONFLICT', 'discovery manifest is already associated with a different batch run');
19
33
  }
34
+ run.schemaVersion = BATCH_IMPORT_VERSION;
20
35
  run.preflight = coveragePreflight({
36
+ configuredProviders: options.manifest.preflight?.configuredProviders,
37
+ scopeWarnings: options.manifest.preflight?.scopeWarnings,
21
38
  expectedProviders: options.manifest.preflight?.expectedProviders,
22
39
  expectedProviderBasis: options.manifest.preflight?.expectedProviderBasis ?? 'legacy',
23
40
  runtime: options.runtime ?? sessionRuntimeIdentity(),
@@ -33,6 +50,12 @@ export async function importDiscoveryManifest(options) {
33
50
  if (!disposition) {
34
51
  throw new SessionContractError('IMPORT_CONFLICT', `batch run is missing discovered source ${manifestSource.sourceId}`);
35
52
  }
53
+ const exclusion = matchImportExclusion(manifestSource, exclusions) ?? manifestSource.exclusion;
54
+ if (exclusion) {
55
+ excludeSource(disposition, exclusion, now());
56
+ continue;
57
+ }
58
+ delete disposition.exclusion;
36
59
  if (isAccepted(disposition.status)) {
37
60
  disposition.status = 'previously-committed';
38
61
  disposition.updatedAt = now().toISOString();
@@ -48,6 +71,7 @@ export async function importDiscoveryManifest(options) {
48
71
  disposition.attempts += 1;
49
72
  disposition.errorCode = null;
50
73
  disposition.diagnostic = null;
74
+ disposition.detail = null;
51
75
  disposition.updatedAt = now().toISOString();
52
76
  persistProgress(options.repository, run, now());
53
77
  try {
@@ -62,7 +86,7 @@ export async function importDiscoveryManifest(options) {
62
86
  allowedRoots: [manifestSource.authorizedRoot],
63
87
  },
64
88
  };
65
- const adapter = adapterFor(manifestSource.provider);
89
+ const adapter = adapterFor(manifestSource.provider, limits);
66
90
  const source = await sessionSource(selectedSource, adapter);
67
91
  const receipts = await new IncrementalSessionImporter(options.repository).import({
68
92
  source,
@@ -74,6 +98,7 @@ export async function importDiscoveryManifest(options) {
74
98
  publish: false,
75
99
  signal: options.signal,
76
100
  inactivityThresholdMs: options.inactivityThresholdMs,
101
+ limits,
77
102
  });
78
103
  await assertManifestSourceUnchanged(manifestSource);
79
104
  const totals = options.repository.sourceImportTotals(manifestSource.sourceId, run.runId);
@@ -89,10 +114,12 @@ export async function importDiscoveryManifest(options) {
89
114
  catch (error) {
90
115
  disposition.status = 'rejected';
91
116
  disposition.errorCode = importErrorCode(error);
117
+ disposition.detail = error instanceof SessionContractError ? error.detail ?? null : null;
92
118
  disposition.diagnostic = [
93
119
  `provider=${manifestSource.provider}`,
94
120
  `source=${manifestSource.sourceId}`,
95
121
  `code=${disposition.errorCode}`,
122
+ ...(disposition.detail ? [`detail=${JSON.stringify(disposition.detail)}`] : []),
96
123
  ].join(' ');
97
124
  }
98
125
  disposition.updatedAt = now().toISOString();
@@ -101,14 +128,14 @@ export async function importDiscoveryManifest(options) {
101
128
  }
102
129
  const rejected = run.sources.some((source) => source.status === 'rejected');
103
130
  const pending = run.sources.some((source) => source.status === 'pending' || source.status === 'running');
104
- const providerGap = coverageFromBatchRun(run, now()).acquisitionStatus !== 'complete';
131
+ const acquisitionIncomplete = coverageFromBatchRun(run, now()).acquisitionStatus !== 'complete';
105
132
  const publishable = run.sources
106
133
  .filter((source) => isAccepted(source.status))
107
134
  .map((source) => source.sourceId);
108
135
  injectFault(options, run, 'before-publication');
109
136
  options.repository.commitStagedBatch(run.runId, publishable);
110
137
  injectFault(options, run, 'after-publication');
111
- run.status = pending ? 'interrupted' : rejected || providerGap ? 'partial' : 'complete';
138
+ run.status = pending ? 'interrupted' : rejected || acquisitionIncomplete ? 'partial' : 'complete';
112
139
  run.updatedAt = now().toISOString();
113
140
  run.completedAt = pending ? null : run.updatedAt;
114
141
  options.repository.saveBatchImportRun(run);
@@ -128,11 +155,23 @@ function injectFault(options, run, boundary, sourceId) {
128
155
  throw error;
129
156
  }
130
157
  }
131
- export function previewDiscoveryImport(manifest, existing, now = new Date()) {
158
+ export function previewDiscoveryImport(manifest, existing, now = new Date(), exclude = []) {
132
159
  const run = existing ?? newBatchRun(manifest, sha256(['workspace-import-v1', manifest.manifestId, manifest.workspaceId].join('\0')), now);
160
+ const exclusions = resolveImportExclusions(undefined, exclude);
133
161
  return receiptFor({
134
162
  ...run,
163
+ sources: run.sources.map(source => {
164
+ const manifestSource = manifest.sources.find(candidate => candidate.sourceId === source.sourceId);
165
+ const exclusion = matchImportExclusion(manifestSource, exclusions) ?? manifestSource.exclusion;
166
+ if (!exclusion)
167
+ return source;
168
+ const disposition = { ...source };
169
+ excludeSource(disposition, exclusion, now);
170
+ return disposition;
171
+ }),
135
172
  preflight: coveragePreflight({
173
+ configuredProviders: manifest.preflight?.configuredProviders,
174
+ scopeWarnings: manifest.preflight?.scopeWarnings,
136
175
  expectedProviders: manifest.preflight?.expectedProviders,
137
176
  expectedProviderBasis: manifest.preflight?.expectedProviderBasis ?? 'legacy',
138
177
  reports: manifest.providers,
@@ -157,7 +196,8 @@ function newBatchRun(manifest, runId, now) {
157
196
  sources: manifest.sources.map((source) => ({
158
197
  sourceId: source.sourceId,
159
198
  provider: source.provider,
160
- status: 'pending',
199
+ status: source.status ?? 'pending',
200
+ ...(source.exclusion ? { exclusion: source.exclusion } : {}),
161
201
  attempts: 0,
162
202
  sessionsAccepted: 0,
163
203
  eventsAccepted: 0,
@@ -173,10 +213,13 @@ function receiptFor(run, now) {
173
213
  schemaVersion: BATCH_IMPORT_VERSION,
174
214
  run,
175
215
  coverage,
216
+ interruptedRuns: [],
217
+ ...(run.carryForward ? { carryForward: run.carryForward } : {}),
176
218
  totals: {
177
219
  discovered: coverage.sources.discovered,
178
220
  accepted: coverage.sources.accepted,
179
221
  rejected: coverage.sources.rejected,
222
+ excluded: coverage.sources.excluded,
180
223
  skipped: coverage.sources.skipped,
181
224
  duplicated: coverage.sources.duplicated,
182
225
  previouslyCommitted: coverage.sources.previouslyCommitted,
@@ -186,6 +229,16 @@ function receiptFor(run, now) {
186
229
  },
187
230
  };
188
231
  }
232
+ function excludeSource(source, exclusion, now) {
233
+ source.status = 'excluded';
234
+ source.exclusion = exclusion;
235
+ source.errorCode = null;
236
+ source.diagnostic = null;
237
+ source.detail = null;
238
+ source.sessionsAccepted = 0;
239
+ source.eventsAccepted = 0;
240
+ source.updatedAt = now.toISOString();
241
+ }
189
242
  function persistProgress(repository, run, now) {
190
243
  run.updatedAt = now.toISOString();
191
244
  repository.saveBatchImportRun(run);
@@ -196,21 +249,21 @@ async function assertManifestSourceUnchanged(source) {
196
249
  details = await stat(source.locator);
197
250
  }
198
251
  catch {
199
- throw new SessionContractError('SCHEMA_DRIFT', 'discovered source is no longer available; run discovery again');
252
+ throw new SessionContractError('SCHEMA_DRIFT', 'discovered source is no longer available; run discovery again', schemaIssueDetail('locator'));
200
253
  }
201
254
  if (details.size !== source.sizeBytes || details.mtime.toISOString() !== source.modifiedAt) {
202
- throw new SessionContractError('SCHEMA_DRIFT', 'discovered source changed after manifest creation; run discovery again');
255
+ throw new SessionContractError('SCHEMA_DRIFT', 'discovered source changed after manifest creation; run discovery again', schemaIssueDetail(details.size !== source.sizeBytes ? 'sizeBytes' : 'modifiedAt'));
203
256
  }
204
257
  const fingerprint = await fingerprintSourceFile({
205
258
  selectedPath: source.locator,
206
259
  allowedRoots: [source.authorizedRoot],
207
260
  });
208
261
  if (fingerprint.digest !== source.digest || fingerprint.size !== source.sizeBytes) {
209
- throw new SessionContractError('SCHEMA_DRIFT', 'discovered source content changed after manifest creation; run discovery again');
262
+ throw new SessionContractError('SCHEMA_DRIFT', 'discovered source content changed after manifest creation; run discovery again', schemaIssueDetail('digest'));
210
263
  }
211
264
  }
212
- function adapterFor(provider) {
213
- return createSessionAdapterRegistry().get(provider);
265
+ function adapterFor(provider, limits) {
266
+ return createSessionAdapterRegistry(limits).get(provider);
214
267
  }
215
268
  async function sessionSource(selected, adapter) {
216
269
  const probe = await adapter.inspect(selected);
@@ -264,4 +317,191 @@ function isAccepted(status) {
264
317
  || status === 'duplicate'
265
318
  || status === 'previously-committed';
266
319
  }
320
+ /** Reuse source import and normalized repository gates, without copying FTS/analytics. */
321
+ async function rebuildFromCatalog(options) {
322
+ const previous = options.carryForward;
323
+ if (previous === options.repository) {
324
+ throw new SessionContractError('INVALID_ARGUMENT', 'carry-forward requires a separate destination catalog');
325
+ }
326
+ const workspaceId = options.manifest.workspaceId;
327
+ const exclusions = resolveImportExclusions(undefined, options.exclude);
328
+ const byProvider = {};
329
+ const counts = (provider) => byProvider[provider] ??= {
330
+ reimported: 0, carried: 0, droppedAsDuplicate: 0, excluded: 0,
331
+ };
332
+ const oldSessions = catalogSessions(previous, workspaceId);
333
+ const available = [];
334
+ const present = new Set();
335
+ const excludedSources = new Set();
336
+ for (const source of options.manifest.sources) {
337
+ if (matchImportExclusion(source, exclusions) ?? source.exclusion) {
338
+ excludedSources.add(source.sourceId);
339
+ continue;
340
+ }
341
+ let details;
342
+ try {
343
+ details = await stat(source.locator);
344
+ }
345
+ catch (error) {
346
+ if (['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
347
+ continue;
348
+ throw error;
349
+ }
350
+ // Refresh the snapshot, but retain the explicitly saved authorization and source identity.
351
+ const fingerprint = await fingerprintSourceFile({ selectedPath: source.locator,
352
+ allowedRoots: [source.authorizedRoot] });
353
+ available.push({ ...source, sizeBytes: details.size, modifiedAt: details.mtime.toISOString(),
354
+ digest: fingerprint.digest });
355
+ present.add(source.sourceId);
356
+ }
357
+ const manifest = { ...options.manifest, sources: available,
358
+ manifestId: sha256(JSON.stringify(['carry-forward-v1', options.manifest.manifestId, available])) };
359
+ const stagingDirectory = await mkdtemp(join(tmpdir(), 'aiwg-session-rebuild-'));
360
+ let staged;
361
+ try {
362
+ staged = new SessionRepository(join(stagingDirectory, 'staging.sqlite'));
363
+ const receipt = await importDiscoveryManifest({ ...options, manifest, repository: staged,
364
+ carryForward: undefined, fault: undefined });
365
+ const identities = new Set();
366
+ const destinationCodex = new Map(catalogSessions(options.repository, workspaceId)
367
+ .filter(session => session.provider === 'codex').map(session => [session.nativeSessionId, session.sourceId]));
368
+ const transfer = (repository, sessions, category) => {
369
+ // A legacy Codex rollout may have split replayed parent events into phantom rows.
370
+ // Recover the first session_meta ID for the whole source before grouping events.
371
+ const sourceGroups = new Map();
372
+ for (const session of sessions) {
373
+ const group = sourceGroups.get(session.sourceId) ?? [];
374
+ group.push(session);
375
+ sourceGroups.set(session.sourceId, group);
376
+ }
377
+ for (const [sourceId, group] of sourceGroups) {
378
+ if (options.signal?.aborted)
379
+ throw new SessionContractError('IMPORT_INTERRUPTED', 'catalog rebuild interrupted');
380
+ const source = repository.getSource(sourceId, workspaceId);
381
+ if (!source)
382
+ throw new SessionContractError('IMPORT_CONFLICT', 'catalog session has no authorized source');
383
+ const allEvents = group.flatMap(session => repository.listEvents(session.sessionId, workspaceId))
384
+ .sort((a, b) => a.sequence - b.sequence || a.eventId.localeCompare(b.eventId));
385
+ const rollout = source.provider === 'codex' ? recoverCodexRolloutIdentity(allEvents) : undefined;
386
+ const childId = rollout?.nativeSessionId;
387
+ // Prefer the child's aggregate when the legacy source also has phantom parent aggregates.
388
+ group.sort((a, b) => Number(b.nativeSessionId === childId) - Number(a.nativeSessionId === childId));
389
+ const canonical = new Map();
390
+ for (const session of group) {
391
+ const nativeId = childId ?? session.nativeSessionId;
392
+ const locator = options.manifest.sources.find(item => item.sourceId === sourceId)?.locator
393
+ ?? source.redactedLocator;
394
+ if (excludedSources.has(sourceId) || matchImportExclusion({ provider: session.provider,
395
+ locator, locatorClass: source.locatorClass, nativeSessionId: nativeId }, exclusions)) {
396
+ counts(session.provider).excluded++;
397
+ continue;
398
+ }
399
+ const key = session.provider === 'codex' ? `${session.provider}\0${nativeId}` : session.sessionId;
400
+ if (identities.has(key)) {
401
+ counts(session.provider).droppedAsDuplicate++;
402
+ continue;
403
+ }
404
+ const existing = canonical.get(key);
405
+ if (existing) {
406
+ counts(session.provider).droppedAsDuplicate++;
407
+ existing.events.push(...repository.listEvents(session.sessionId, workspaceId));
408
+ }
409
+ else
410
+ canonical.set(key, { session: { ...session, nativeSessionId: nativeId },
411
+ events: repository.listEvents(session.sessionId, workspaceId) });
412
+ }
413
+ for (const [key, entry] of canonical) {
414
+ if (source.provider === 'codex' && destinationCodex.has(entry.session.nativeSessionId)
415
+ && destinationCodex.get(entry.session.nativeSessionId) !== sourceId) {
416
+ throw new SessionContractError('IMPORT_CONFLICT', 'destination has a different source for this Codex identity; select a fresh catalog');
417
+ }
418
+ const session = redactSessionValue({ ...entry.session,
419
+ sessionId: stableSessionId(source.provider, sourceId, entry.session.nativeSessionId),
420
+ }).value;
421
+ // Hash only sanitized content; receipts and IDs must never retain old plaintext.
422
+ const sanitized = entry.events.map(sanitizeSessionEvent);
423
+ const runId = sha256(JSON.stringify(['catalog-carry-v1', session.sessionId, sanitized]));
424
+ const events = sanitized.map(event => {
425
+ const digest = event.digest;
426
+ return { ...event, sessionId: session.sessionId, importRunId: runId, digest,
427
+ eventId: stableEventId(source.provider, sourceId, {
428
+ nativeSessionId: session.nativeSessionId, nativeEventId: event.nativeId ?? event.eventId,
429
+ sequence: event.sequence, kind: event.kind, text: event.searchableText,
430
+ rawReference: event.rawReference,
431
+ }, digest) };
432
+ });
433
+ if (rollout && Object.keys(rollout.lineage).length > 0) {
434
+ session.extensions['native.codex'] = { lineage: rollout.lineage };
435
+ }
436
+ session.intent = deriveSessionIntent(events);
437
+ const checkpoint = repository.getCheckpoint(sourceId, source.adapterVersion)
438
+ ?? { cursor: '', recordsRead: events.length, bytesRead: Buffer.byteLength(JSON.stringify(events)) };
439
+ options.repository.applyImport({ source, sessions: [session], events,
440
+ run: { contractVersion: SESSION_CONTRACT_VERSION, importRunId: runId, sourceId,
441
+ parserVersion: source.adapterVersion, policyVersion: '1.0.0',
442
+ sourceSchemaVersion: source.sourceSchemaVersion, consistency: source.consistency,
443
+ status: 'running', checkpoint, startedAt: (options.now?.() ?? new Date()).toISOString(),
444
+ completedAt: null, errorCode: null } }, checkpoint);
445
+ for (const old of group.filter(item => (childId ?? item.nativeSessionId) === entry.session.nativeSessionId)) {
446
+ for (const tag of repository.listTags(old.sessionId, workspaceId)) {
447
+ options.repository.tagSession(session.sessionId, tag, workspaceId);
448
+ }
449
+ }
450
+ identities.add(key);
451
+ counts(source.provider)[category]++;
452
+ }
453
+ }
454
+ };
455
+ const reimported = catalogSessions(staged, workspaceId);
456
+ transfer(staged, reimported, 'reimported');
457
+ for (const sourceId of present) {
458
+ const old = oldSessions.filter(session => session.sourceId === sourceId && session.provider === 'codex');
459
+ const imported = reimported.filter(session => session.sourceId === sourceId);
460
+ // Account for phantom rows eliminated by current source parsing, separately from copied duplicates.
461
+ if (imported.length > 0 && old.length > imported.length) {
462
+ counts('codex').droppedAsDuplicate += old.length - imported.length;
463
+ }
464
+ }
465
+ transfer(previous, oldSessions.filter(session => !present.has(session.sourceId)), 'carried');
466
+ options.repository.reindex(workspaceId);
467
+ const finalSessions = catalogSessions(options.repository, workspaceId);
468
+ const sourceIds = new Set([...receipt.run.sources.map(item => item.sourceId),
469
+ ...oldSessions.map(session => session.sourceId)]);
470
+ for (const sourceId of sourceIds) {
471
+ const accepted = finalSessions.filter(session => session.sourceId === sourceId);
472
+ let disposition = receipt.run.sources.find(item => item.sourceId === sourceId);
473
+ if (!disposition) {
474
+ const old = oldSessions.find(session => session.sourceId === sourceId);
475
+ const excluded = excludedSources.has(sourceId) || matchImportExclusion({
476
+ provider: old.provider, nativeSessionId: old.nativeSessionId,
477
+ locator: previous.getSource(sourceId, workspaceId)?.redactedLocator ?? '', locatorClass: 'catalog',
478
+ }, exclusions);
479
+ disposition = { sourceId, provider: old.provider,
480
+ status: excluded ? 'excluded' : accepted.length > 0 ? 'committed' : 'duplicate',
481
+ attempts: 1, sessionsAccepted: 0, eventsAccepted: 0,
482
+ errorCode: null, diagnostic: null, updatedAt: receipt.run.updatedAt };
483
+ receipt.run.sources.push(disposition);
484
+ }
485
+ disposition.sessionsAccepted = accepted.length;
486
+ disposition.eventsAccepted = accepted.reduce((sum, session) => sum + options.repository.listEvents(session.sessionId, workspaceId).length, 0);
487
+ }
488
+ receipt.run.carryForward = { byProvider };
489
+ options.repository.saveBatchImportRun(receipt.run);
490
+ return redactSessionValue(receiptFor(receipt.run, options.now?.() ?? new Date())).value;
491
+ }
492
+ finally {
493
+ staged?.close();
494
+ await rm(stagingDirectory, { recursive: true, force: true });
495
+ }
496
+ }
497
+ function catalogSessions(repository, workspaceId) {
498
+ const sessions = [];
499
+ let cursor;
500
+ do {
501
+ const page = repository.listSessions({ workspaceId, limit: 500, cursor });
502
+ sessions.push(...page.items);
503
+ cursor = page.nextCursor ?? undefined;
504
+ } while (cursor);
505
+ return sessions;
506
+ }
267
507
  //# sourceMappingURL=batch-import.js.map
@@ -1,5 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { z } from 'zod';
3
+ import { redactSessionText, redactSessionValue } from './policy.js';
3
4
  export const SESSION_CONTRACT_VERSION = '1.0.0';
4
5
  export const SESSION_PROVIDER_IDS = [
5
6
  'claude', 'codex', 'copilot', 'cursor', 'factory', 'hermes',
@@ -19,9 +20,18 @@ export const SessionRuntimeIdentitySchema = z.object({
19
20
  canonicalRoot: z.string().nullable(),
20
21
  diagnostics: z.array(z.string()),
21
22
  });
23
+ export const SessionProviderScopeWarningSchema = z.object({
24
+ code: z.literal('CONFIGURED_PROVIDERS_OUTSIDE_SCOPE'),
25
+ configuredProviders: z.array(ExpectedSessionProviderSchema),
26
+ expectedProviders: z.array(ExpectedSessionProviderSchema),
27
+ missingProviders: z.array(ExpectedSessionProviderSchema),
28
+ message: z.string(),
29
+ });
22
30
  export const SessionCoveragePreflightSchema = z.object({
23
31
  expectedProviders: z.array(ExpectedSessionProviderSchema).min(1),
24
- expectedProviderBasis: z.enum(['explicit', 'declared-default', 'legacy']),
32
+ expectedProviderBasis: z.enum(['explicit', 'workspace-config', 'declared-default', 'legacy']),
33
+ configuredProviders: z.array(ExpectedSessionProviderSchema).optional(),
34
+ scopeWarnings: z.array(SessionProviderScopeWarningSchema).optional(),
25
35
  supportedProviders: z.array(ExpectedSessionProviderSchema),
26
36
  runtime: SessionRuntimeIdentitySchema,
27
37
  status: z.enum(['partial', 'complete']),
@@ -362,11 +372,48 @@ export function sha256(value) {
362
372
  function stableId(prefix, ...parts) {
363
373
  return `${prefix}_${createHash('sha256').update(parts.join('\0')).digest('hex')}`;
364
374
  }
375
+ export const SessionRejectionDetailSchema = z.union([
376
+ z.object({ limit: z.string(), configured: z.number(), observed: z.number(),
377
+ setting: z.string().optional(), origin: z.enum(['flag', 'env', 'config', 'default']).optional() }),
378
+ z.object({ issues: z.array(z.object({ path: z.string(), code: z.string() })) }),
379
+ ]);
380
+ export function schemaIssueDetail(path, code = 'custom') {
381
+ return { issues: [{ path, code }] };
382
+ }
383
+ export function assertSessionResourceLimit(limit, configured, observed) {
384
+ if (observed > configured) {
385
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'session resource limit exceeded', {
386
+ limit, configured, observed,
387
+ });
388
+ }
389
+ }
390
+ /** Extract schema locations and validator codes without messages or input values. */
391
+ export function schemaRejectionDetail(error) {
392
+ const issues = [];
393
+ const visit = (entries) => {
394
+ for (const issue of entries) {
395
+ if (issues.length >= 20)
396
+ return;
397
+ if (issue.code === 'invalid_union') {
398
+ for (const branch of issue.unionErrors)
399
+ visit(branch.issues);
400
+ }
401
+ else {
402
+ issues.push({ path: issue.path.join('.') || '<root>', code: issue.code });
403
+ }
404
+ }
405
+ };
406
+ visit(error.issues);
407
+ return redactSessionValue({ issues }).value;
408
+ }
365
409
  export class SessionContractError extends Error {
366
410
  code;
367
- constructor(code, message) {
368
- super(message);
411
+ detail;
412
+ constructor(code, message, detail) {
413
+ super(redactSessionText(message).text);
369
414
  this.code = code;
415
+ this.detail = detail;
416
+ this.detail = redactSessionValue(detail).value;
370
417
  this.name = 'SessionContractError';
371
418
  }
372
419
  }