@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,8 +1,8 @@
1
1
  import { opendir } from 'node:fs/promises';
2
2
  import { basename, resolve } from 'node:path';
3
3
  import { z } from 'zod';
4
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
5
- import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
4
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
5
+ import { streamBoundedJsonLines, } from '../readers.js';
6
6
  export const CODEX_ADAPTER_VERSION = '1.0.0';
7
7
  export const CODEX_SOURCE_SCHEMA_VERSION = '1.0.0';
8
8
  const AppServerEnvelopeSchema = z.object({
@@ -57,7 +57,7 @@ export class CodexSessionAdapter {
57
57
  for (const root of [...scope.allowedRoots].sort()) {
58
58
  for await (const locator of discoverJsonl(resolve(root), this.discoveryLimits.maxDepth)) {
59
59
  if (++emitted > this.discoveryLimits.maxFiles) {
60
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Codex source discovery exceeded the authorized file limit');
60
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Codex source discovery exceeded the authorized file limit', { limit: 'maxFiles', configured: this.discoveryLimits.maxFiles, observed: emitted });
61
61
  }
62
62
  yield {
63
63
  provider: 'codex',
@@ -70,30 +70,34 @@ export class CodexSessionAdapter {
70
70
  }
71
71
  }
72
72
  async inspect(source) {
73
- const parsed = await this.readSource(source);
74
- return {
75
- sourceSchemaVersion: parsed.schemaVersion,
76
- consistency: parsed.consistency,
77
- operationalState: 'available',
78
- };
73
+ const records = this.readSource(source);
74
+ let result = await records.next();
75
+ while (!result.done)
76
+ result = await records.next();
77
+ return result.value;
79
78
  }
80
79
  async *stream(source, cursor) {
80
+ yield* this.readSource(source, cursor);
81
+ }
82
+ async *readSource(source, cursor) {
81
83
  const start = parseCursor(cursor?.value);
82
84
  const input = await streamBoundedJsonLines({ selectedPath: source.locator, allowedRoots: source.authorizedScope.allowedRoots }, { consistency: 'provisional', limits: this.limits });
83
85
  let mode = isAppServerLocator(source.locator)
84
86
  ? 'app-server' : null;
85
87
  let schemaVersion = null;
86
88
  const sourceArtifactSessionId = rolloutIdFromFilename(source.locator);
87
- let nativeSessionId = sourceArtifactSessionId;
89
+ const rolloutState = { nativeSessionId: sourceArtifactSessionId };
88
90
  const identities = new Map();
89
91
  let outputIndex = 0;
90
92
  let sawRecord = false;
93
+ let complete = false;
94
+ let sawActive = false;
91
95
  for await (const line of input) {
92
96
  sawRecord = true;
93
97
  mode ??= isAppServerRecord(line.value) ? 'app-server' : 'rollout';
94
98
  const currentSchema = declaredSchemaVersion([line]);
95
99
  if (schemaVersion && schemaVersion !== currentSchema) {
96
- throw new SessionContractError('SCHEMA_DRIFT', 'Codex source declares mixed schema versions');
100
+ throw new SessionContractError('SCHEMA_DRIFT', 'Codex source declares mixed schema versions', schemaIssueDetail('schemaVersion'));
97
101
  }
98
102
  schemaVersion = currentSchema;
99
103
  assertSupportedSchemaMajor(schemaVersion);
@@ -101,30 +105,22 @@ export class CodexSessionAdapter {
101
105
  if (mode === 'app-server') {
102
106
  const parsed = AppServerEnvelopeSchema.safeParse(line.value);
103
107
  if (!parsed.success) {
104
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex App Server record is malformed');
108
+ throw new SessionContractError('MALFORMED_SOURCE', 'Codex App Server record is malformed', schemaRejectionDetail(parsed.error));
105
109
  }
106
110
  for (const thread of extractThreads(parsed.data)) {
107
111
  validateThreadIdentity(thread, identities);
112
+ sawActive ||= statusType(thread.status) === 'active';
108
113
  }
109
- normalized = normalizeAppServer([line]).records;
114
+ const appServer = normalizeAppServer([line]);
115
+ complete ||= appServer.complete;
116
+ normalized = appServer.records;
110
117
  }
111
118
  else {
112
119
  const parsed = RolloutEnvelopeSchema.safeParse(line.value);
113
120
  if (!parsed.success) {
114
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex rollout record is malformed');
115
- }
116
- const payload = asObject(parsed.data.payload);
117
- if (parsed.data.type === 'session_meta') {
118
- const declaredId = stringValue(payload.id);
119
- if (!declaredId) {
120
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex session metadata is missing its id');
121
- }
122
- nativeSessionId = declaredId;
121
+ throw new SessionContractError('MALFORMED_SOURCE', 'Codex rollout record is malformed', schemaRejectionDetail(parsed.error));
123
122
  }
124
- if (!nativeSessionId) {
125
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex rollout has no session identity before content records');
126
- }
127
- normalized = [rolloutRecord(nativeSessionId, parsed.data, payload, line, sourceArtifactSessionId)];
123
+ normalized = [normalizeRolloutRecord(rolloutState, parsed.data, line, sourceArtifactSessionId)];
128
124
  }
129
125
  for (const record of normalized) {
130
126
  if (outputIndex++ >= start)
@@ -132,25 +128,12 @@ export class CodexSessionAdapter {
132
128
  }
133
129
  }
134
130
  if (!sawRecord && !input.incompleteTail) {
135
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex JSONL source is empty');
136
- }
137
- }
138
- async readSource(source) {
139
- const input = await readBoundedJsonLines({ selectedPath: source.locator, allowedRoots: source.authorizedScope.allowedRoots }, { consistency: 'provisional', limits: this.limits });
140
- if (input.records.length === 0 && !input.incompleteTail) {
141
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex JSONL source is empty');
131
+ throw new SessionContractError('MALFORMED_SOURCE', 'Codex JSONL source is empty', schemaIssueDetail('<root>'));
142
132
  }
143
- const appServer = isAppServerLocator(source.locator)
144
- || input.records.some((record) => isAppServerRecord(record.value));
145
- const schemaVersion = declaredSchemaVersion(input.records);
146
- assertSupportedSchemaMajor(schemaVersion);
147
- const normalized = appServer
148
- ? normalizeAppServer(input.records)
149
- : normalizeRollout(input.records, source.locator);
150
133
  return {
151
- records: normalized.records,
152
- schemaVersion,
153
- consistency: normalized.complete ? 'complete' : 'provisional',
134
+ sourceSchemaVersion: schemaVersion ?? CODEX_SOURCE_SCHEMA_VERSION,
135
+ consistency: complete && !sawActive ? 'complete' : 'provisional',
136
+ operationalState: 'available',
154
137
  };
155
138
  }
156
139
  }
@@ -162,7 +145,7 @@ function normalizeAppServer(input) {
162
145
  for (const line of input) {
163
146
  const parsed = AppServerEnvelopeSchema.safeParse(line.value);
164
147
  if (!parsed.success) {
165
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex App Server record is malformed');
148
+ throw new SessionContractError('MALFORMED_SOURCE', 'Codex App Server record is malformed', schemaRejectionDetail(parsed.error));
166
149
  }
167
150
  const envelope = parsed.data;
168
151
  const threads = extractThreads(envelope);
@@ -207,30 +190,75 @@ function normalizeAppServer(input) {
207
190
  }
208
191
  return { records, complete: complete && !sawActive };
209
192
  }
210
- function normalizeRollout(input, locator) {
211
- const sourceArtifactSessionId = rolloutIdFromFilename(locator);
212
- let nativeSessionId = sourceArtifactSessionId;
213
- const records = [];
214
- for (const line of input) {
215
- const parsed = RolloutEnvelopeSchema.safeParse(line.value);
216
- if (!parsed.success) {
217
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex rollout record is malformed');
193
+ function normalizeRolloutRecord(state, envelope, line, sourceArtifactSessionId) {
194
+ const payload = asObject(envelope.payload);
195
+ let firstMetadata = false;
196
+ if (envelope.type === 'session_meta') {
197
+ const declaredId = stringValue(payload.id);
198
+ if (!declaredId) {
199
+ throw new SessionContractError('MALFORMED_SOURCE', 'Codex session metadata is missing its id', schemaIssueDetail('payload.id'));
218
200
  }
219
- const envelope = parsed.data;
220
- const payload = asObject(envelope.payload);
221
- if (envelope.type === 'session_meta') {
222
- const declaredId = stringValue(payload.id);
223
- if (!declaredId) {
224
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex session metadata is missing its id');
225
- }
226
- nativeSessionId = declaredId;
201
+ if (!state.metadataId) {
202
+ firstMetadata = true;
203
+ state.metadataId = declaredId;
204
+ state.nativeSessionId = declaredId;
205
+ const lineage = rolloutLineage(payload);
206
+ if (Object.keys(lineage).length > 0)
207
+ state.lineage = lineage;
227
208
  }
228
- if (!nativeSessionId) {
229
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex rollout has no session identity before content records');
209
+ else if (declaredId !== state.metadataId) {
210
+ state.replayParentId ??= declaredId;
230
211
  }
231
- records.push(rolloutRecord(nativeSessionId, envelope, payload, line, sourceArtifactSessionId));
232
212
  }
233
- return { records, complete: false };
213
+ if (!state.nativeSessionId) {
214
+ throw new SessionContractError('MALFORMED_SOURCE', 'Codex rollout has no session identity before content records', schemaIssueDetail('payload.id'));
215
+ }
216
+ const historyStart = state.lineage?.subagentHistoryStartOrdinal;
217
+ // Legacy rollouts have no ordinal boundary; a new turn ends the replay prefix.
218
+ if (historyStart === undefined && (envelope.type === 'turn_context'
219
+ || (envelope.type === 'event_msg' && payload.type === 'task_started'))) {
220
+ state.replayParentId = undefined;
221
+ }
222
+ const parentId = stringValue(state.lineage?.parentThreadId)
223
+ ?? stringValue(state.lineage?.forkedFromId)
224
+ ?? state.replayParentId;
225
+ const inherited = !firstMetadata && parentId && (typeof historyStart === 'number'
226
+ ? (nonnegativeInteger(envelope.ordinal) ?? line.sequence) < historyStart
227
+ : state.replayParentId !== undefined);
228
+ const record = rolloutRecord(state.nativeSessionId, envelope, payload, line, sourceArtifactSessionId);
229
+ if (state.lineage)
230
+ record.extensions.lineage = state.lineage;
231
+ if (inherited) {
232
+ record.extensions.inheritedFromId = parentId;
233
+ record.extensions.replayed = true;
234
+ }
235
+ return record;
236
+ }
237
+ /** Apply the rollout identity rule to legacy catalog rows, including split parent replay. */
238
+ export function recoverCodexRolloutIdentity(events) {
239
+ const metadata = [...events].sort((a, b) => a.sequence - b.sequence || a.eventId.localeCompare(b.eventId))
240
+ .find(event => asObject(event.extensions['native.codex']).rolloutType === 'session_meta');
241
+ if (!metadata?.nativeId)
242
+ return undefined; // App Server sources can contain multiple independent threads.
243
+ const native = asObject(metadata.extensions['native.codex']);
244
+ return { nativeSessionId: metadata.nativeId,
245
+ lineage: { ...rolloutLineage(asObject(native.unknownFields)), ...asObject(native.lineage) } };
246
+ }
247
+ function rolloutLineage(payload) {
248
+ const spawn = asObject(asObject(asObject(payload.source).subagent).thread_spawn);
249
+ return Object.fromEntries(Object.entries({
250
+ forkedFromId: stringValue(payload.forked_from_id),
251
+ parentThreadId: stringValue(spawn.parent_thread_id),
252
+ depth: nonnegativeInteger(spawn.depth ?? payload.depth),
253
+ agentPath: stringValue(payload.agent_path ?? spawn.agent_path),
254
+ agentNickname: stringValue(payload.agent_nickname ?? spawn.agent_nickname),
255
+ subagentHistoryStartOrdinal: nonnegativeInteger(payload.subagent_history_start_ordinal),
256
+ threadSource: stringValue(payload.thread_source),
257
+ }).filter(([, value]) => value !== undefined));
258
+ }
259
+ function nonnegativeInteger(value) {
260
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0
261
+ ? value : undefined;
234
262
  }
235
263
  function threadEvidence(thread, envelope, line) {
236
264
  const status = statusType(thread.status);
@@ -350,7 +378,7 @@ function extractThreads(envelope) {
350
378
  return candidates.map((candidate) => {
351
379
  const parsed = ThreadSchema.safeParse(candidate);
352
380
  if (!parsed.success) {
353
- throw new SessionContractError('MALFORMED_SOURCE', 'Codex App Server thread is malformed');
381
+ throw new SessionContractError('MALFORMED_SOURCE', 'Codex App Server thread is malformed', schemaRejectionDetail(parsed.error));
354
382
  }
355
383
  return parsed.data;
356
384
  });
@@ -359,7 +387,7 @@ function validateThreadIdentity(thread, identities) {
359
387
  const sessionTree = thread.sessionId ?? thread.id;
360
388
  const previous = identities.get(thread.id);
361
389
  if (previous && previous !== sessionTree) {
362
- throw new SessionContractError('SCHEMA_DRIFT', 'Codex thread session-tree identity changed');
390
+ throw new SessionContractError('SCHEMA_DRIFT', 'Codex thread session-tree identity changed', schemaIssueDetail('sessionId'));
363
391
  }
364
392
  identities.set(thread.id, sessionTree);
365
393
  }
@@ -514,7 +542,7 @@ function parseCursor(value) {
514
542
  if (value === undefined || value === '')
515
543
  return 0;
516
544
  if (!/^\d+$/.test(value))
517
- throw new SessionContractError('SCHEMA_DRIFT', 'Codex record cursor is invalid');
545
+ throw new SessionContractError('SCHEMA_DRIFT', 'Codex record cursor is invalid', schemaIssueDetail('cursor'));
518
546
  return Number(value);
519
547
  }
520
548
  function unixTimestamp(value) {
@@ -1,6 +1,6 @@
1
1
  import { basename, extname } from 'node:path';
2
2
  import { z } from 'zod';
3
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
4
4
  import { readBoundedJson } from '../readers.js';
5
5
  export const COPILOT_ADAPTER_VERSION = '1.0.0';
6
6
  export const COPILOT_EXPORT_SCHEMA_VERSION = '1.0.0';
@@ -76,7 +76,7 @@ export class CopilotSessionAdapter {
76
76
  }, this.limits);
77
77
  const parsed = ExportSchema.safeParse(value);
78
78
  if (!parsed.success) {
79
- throw new SessionContractError('MALFORMED_SOURCE', 'Copilot chat export is malformed');
79
+ throw new SessionContractError('MALFORMED_SOURCE', 'Copilot chat export is malformed', schemaRejectionDetail(parsed.error));
80
80
  }
81
81
  const schemaVersion = declaredSchemaVersion(parsed.data);
82
82
  assertSupportedSchemaMajor(schemaVersion);
@@ -193,7 +193,7 @@ function declaredSchemaVersion(value) {
193
193
  ? value.version
194
194
  : Number(String(value.version).split('.')[0]);
195
195
  if (!Number.isInteger(major) || major < 1) {
196
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Copilot export version');
196
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Copilot export version', schemaIssueDetail('version'));
197
197
  }
198
198
  return `${major}.0.0`;
199
199
  }
@@ -210,7 +210,7 @@ function parseCursor(value) {
210
210
  if (!value)
211
211
  return 0;
212
212
  if (!/^\d+$/.test(value)) {
213
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Copilot record cursor');
213
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Copilot record cursor', schemaIssueDetail('cursor'));
214
214
  }
215
215
  return Number(value);
216
216
  }
@@ -1,8 +1,8 @@
1
1
  import { opendir } from 'node:fs/promises';
2
2
  import { basename, dirname, extname, resolve, } from 'node:path';
3
3
  import { z } from 'zod';
4
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
5
- import { readBoundedJsonLines, readBoundedText, streamBoundedJsonLines, } from '../readers.js';
4
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
5
+ import { readBoundedText, streamBoundedJsonLines, } from '../readers.js';
6
6
  export const CURSOR_ADAPTER_VERSION = '1.0.0';
7
7
  export const CURSOR_SOURCE_SCHEMA_VERSION = '1.0.0';
8
8
  const CliEventSchema = z.object({
@@ -82,7 +82,7 @@ export class CursorSessionAdapter {
82
82
  for (const root of [...scope.allowedRoots].sort()) {
83
83
  for await (const locator of discoverJsonl(resolve(root), this.discoveryLimits.maxDepth)) {
84
84
  if (++emitted > this.discoveryLimits.maxFiles) {
85
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Cursor source discovery exceeded the authorized file limit');
85
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Cursor source discovery exceeded the authorized file limit', { limit: 'maxFiles', configured: this.discoveryLimits.maxFiles, observed: emitted });
86
86
  }
87
87
  yield {
88
88
  provider: 'cursor',
@@ -93,6 +93,16 @@ export class CursorSessionAdapter {
93
93
  }
94
94
  }
95
95
  async inspect(source) {
96
+ if (source.locatorClass === 'cursor-cli-stream-json'
97
+ || source.locatorClass === 'cursor-cloud-events-jsonl'
98
+ || source.locatorClass === 'cursor-agent-transcript-jsonl') {
99
+ const records = this.stream(source);
100
+ let result = await records.next();
101
+ while (!result.done)
102
+ result = await records.next();
103
+ if (result.value)
104
+ return result.value;
105
+ }
96
106
  const parsed = await this.readSource(source);
97
107
  return {
98
108
  sourceSchemaVersion: parsed.schemaVersion,
@@ -102,7 +112,8 @@ export class CursorSessionAdapter {
102
112
  }
103
113
  async *stream(source, cursor) {
104
114
  if (source.locatorClass === 'cursor-cli-stream-json'
105
- || source.locatorClass === 'cursor-cloud-events-jsonl') {
115
+ || source.locatorClass === 'cursor-cloud-events-jsonl'
116
+ || source.locatorClass === 'cursor-agent-transcript-jsonl') {
106
117
  const input = await streamBoundedJsonLines({
107
118
  selectedPath: source.locator,
108
119
  allowedRoots: source.authorizedScope.allowedRoots,
@@ -114,13 +125,14 @@ export class CursorSessionAdapter {
114
125
  let agentId;
115
126
  let runId;
116
127
  let sawRecord = false;
128
+ let complete = false;
117
129
  for await (const line of input) {
118
130
  sawRecord = true;
119
131
  const raw = asObject(line.value);
120
132
  const currentSchema = typeof raw.schemaVersion === 'string'
121
133
  ? raw.schemaVersion : CURSOR_SOURCE_SCHEMA_VERSION;
122
134
  if (schemaVersion && schemaVersion !== currentSchema) {
123
- throw new SessionContractError('SCHEMA_DRIFT', 'Cursor source declares mixed schema versions');
135
+ throw new SessionContractError('SCHEMA_DRIFT', 'Cursor source declares mixed schema versions', schemaIssueDetail('schemaVersion'));
124
136
  }
125
137
  schemaVersion = currentSchema;
126
138
  assertSupportedSchemaMajor(schemaVersion);
@@ -128,7 +140,7 @@ export class CursorSessionAdapter {
128
140
  if (source.locatorClass === 'cursor-cli-stream-json') {
129
141
  const parsed = CliEventSchema.safeParse(line.value);
130
142
  if (!parsed.success) {
131
- throw new SessionContractError('MALFORMED_SOURCE', 'Cursor CLI event is malformed');
143
+ throw new SessionContractError('MALFORMED_SOURCE', 'Cursor CLI event is malformed', schemaRejectionDetail(parsed.error));
132
144
  }
133
145
  const event = parsed.data;
134
146
  if (cliSessionId && cliSessionId !== event.session_id) {
@@ -137,10 +149,10 @@ export class CursorSessionAdapter {
137
149
  cliSessionId = event.session_id;
138
150
  record = normalizeCli([line]).records[0];
139
151
  }
140
- else {
152
+ else if (source.locatorClass === 'cursor-cloud-events-jsonl') {
141
153
  const parsed = CloudEventSchema.safeParse(line.value);
142
154
  if (!parsed.success) {
143
- throw new SessionContractError('MALFORMED_SOURCE', 'Cursor Cloud Agent event is malformed');
155
+ throw new SessionContractError('MALFORMED_SOURCE', 'Cursor Cloud Agent event is malformed', schemaRejectionDetail(parsed.error));
144
156
  }
145
157
  const event = parsed.data;
146
158
  const nextAgentId = event.agent?.id ?? agentId;
@@ -153,27 +165,38 @@ export class CursorSessionAdapter {
153
165
  }
154
166
  agentId = nextAgentId;
155
167
  runId = nextRunId;
156
- record = normalizeCloud([{
168
+ const normalized = normalizeCloud([{
157
169
  ...line,
158
170
  value: {
159
171
  ...event,
160
172
  agent: event.agent ?? (agentId ? { id: agentId } : undefined),
161
173
  run: event.run ?? (runId ? { id: runId } : undefined),
162
174
  },
163
- }]).records[0];
175
+ }]);
176
+ complete ||= normalized.complete;
177
+ record = normalized.records[0];
178
+ }
179
+ else {
180
+ record = normalizeAgentTranscript([line], source.locator).records[0];
164
181
  }
182
+ complete ||= record.extensions?.lifecycle === 'complete';
165
183
  if (outputIndex++ >= start)
166
184
  yield record;
167
185
  }
168
186
  if (!sawRecord && !input.incompleteTail) {
169
- throw new SessionContractError('MALFORMED_SOURCE', 'Cursor structured source is empty');
187
+ throw new SessionContractError('MALFORMED_SOURCE', 'Cursor structured source is empty', schemaIssueDetail('<root>'));
170
188
  }
171
- return;
189
+ return {
190
+ sourceSchemaVersion: schemaVersion ?? CURSOR_SOURCE_SCHEMA_VERSION,
191
+ consistency: complete && !input.incompleteTail ? 'complete' : 'provisional',
192
+ operationalState: 'available',
193
+ };
172
194
  }
173
195
  const parsed = await this.readSource(source);
174
196
  const start = parseCursor(cursor?.value);
175
197
  for (const record of parsed.records.slice(start))
176
198
  yield record;
199
+ return undefined;
177
200
  }
178
201
  async readSource(source) {
179
202
  const authorization = {
@@ -191,36 +214,6 @@ export class CursorSessionAdapter {
191
214
  records: normalizeMarkdown(input.value, source.locator),
192
215
  };
193
216
  }
194
- const input = await readBoundedJsonLines(authorization, { consistency: 'provisional', limits: this.limits });
195
- if (input.records.length === 0 && !input.incompleteTail) {
196
- throw new SessionContractError('MALFORMED_SOURCE', 'Cursor structured source is empty');
197
- }
198
- const schemaVersion = declaredVersion(input.records);
199
- assertSupportedSchemaMajor(schemaVersion);
200
- if (source.locatorClass === 'cursor-cli-stream-json') {
201
- const normalized = normalizeCli(input.records);
202
- return {
203
- schemaVersion,
204
- consistency: normalized.complete && !input.incompleteTail ? 'complete' : 'provisional',
205
- records: normalized.records,
206
- };
207
- }
208
- if (source.locatorClass === 'cursor-cloud-events-jsonl') {
209
- const normalized = normalizeCloud(input.records);
210
- return {
211
- schemaVersion,
212
- consistency: normalized.complete && !input.incompleteTail ? 'complete' : 'provisional',
213
- records: normalized.records,
214
- };
215
- }
216
- if (source.locatorClass === 'cursor-agent-transcript-jsonl') {
217
- const normalized = normalizeAgentTranscript(input.records, source.locator);
218
- return {
219
- schemaVersion,
220
- consistency: normalized.complete && !input.incompleteTail ? 'complete' : 'provisional',
221
- records: normalized.records,
222
- };
223
- }
224
217
  throw new SessionContractError('UNSUPPORTED_OPERATION', 'unsupported Cursor source class');
225
218
  }
226
219
  }
@@ -257,7 +250,7 @@ function normalizeCli(input) {
257
250
  for (const line of input) {
258
251
  const parsed = CliEventSchema.safeParse(line.value);
259
252
  if (!parsed.success) {
260
- throw new SessionContractError('MALFORMED_SOURCE', 'Cursor CLI event is malformed');
253
+ throw new SessionContractError('MALFORMED_SOURCE', 'Cursor CLI event is malformed', schemaRejectionDetail(parsed.error));
261
254
  }
262
255
  const event = parsed.data;
263
256
  if (sessionId && sessionId !== event.session_id) {
@@ -307,7 +300,7 @@ function normalizeCloud(input) {
307
300
  for (const line of input) {
308
301
  const parsed = CloudEventSchema.safeParse(line.value);
309
302
  if (!parsed.success) {
310
- throw new SessionContractError('MALFORMED_SOURCE', 'Cursor Cloud Agent event is malformed');
303
+ throw new SessionContractError('MALFORMED_SOURCE', 'Cursor Cloud Agent event is malformed', schemaRejectionDetail(parsed.error));
311
304
  }
312
305
  const event = parsed.data;
313
306
  const nextAgentId = event.agent?.id ?? agentId;
@@ -371,7 +364,7 @@ function normalizeAgentTranscript(input, locator) {
371
364
  for (const line of input) {
372
365
  const parsed = AgentTranscriptEventSchema.safeParse(line.value);
373
366
  if (!parsed.success) {
374
- throw new SessionContractError('MALFORMED_SOURCE', 'Cursor agent transcript event is malformed');
367
+ throw new SessionContractError('MALFORMED_SOURCE', 'Cursor agent transcript event is malformed', schemaRejectionDetail(parsed.error));
375
368
  }
376
369
  const event = parsed.data;
377
370
  const terminal = event.type === 'turn_ended' && event.status === 'success';
@@ -411,7 +404,7 @@ function normalizeMarkdown(value, locator) {
411
404
  const heading = /^#{1,3}\s+(User|Assistant|Cursor)\s*$/gim;
412
405
  const matches = [...value.matchAll(heading)];
413
406
  if (matches.length === 0) {
414
- throw new SessionContractError('MALFORMED_SOURCE', 'Cursor Markdown export has no role headings');
407
+ throw new SessionContractError('MALFORMED_SOURCE', 'Cursor Markdown export has no role headings', schemaIssueDetail('headings'));
415
408
  }
416
409
  const nativeSessionId = basename(locator, extname(locator));
417
410
  return matches.map((match, index) => {
@@ -462,16 +455,6 @@ function messageText(value) {
462
455
  return '';
463
456
  }).filter(Boolean).join('\n');
464
457
  }
465
- function declaredVersion(records) {
466
- const versions = new Set(records.map((line) => {
467
- const value = asObject(line.value);
468
- return typeof value.schemaVersion === 'string' ? value.schemaVersion : CURSOR_SOURCE_SCHEMA_VERSION;
469
- }));
470
- if (versions.size !== 1) {
471
- throw new SessionContractError('SCHEMA_DRIFT', 'Cursor source declares mixed schema versions');
472
- }
473
- return [...versions][0];
474
- }
475
458
  function declaredEventVersion(event) {
476
459
  return event.schemaVersion ?? CURSOR_SOURCE_SCHEMA_VERSION;
477
460
  }
@@ -511,7 +494,7 @@ function parseCursor(value) {
511
494
  if (!value)
512
495
  return 0;
513
496
  if (!/^\d+$/.test(value)) {
514
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Cursor record cursor');
497
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Cursor record cursor', schemaIssueDetail('cursor'));
515
498
  }
516
499
  return Number(value);
517
500
  }
@@ -1,8 +1,8 @@
1
1
  import { opendir } from 'node:fs/promises';
2
2
  import { resolve } from 'node:path';
3
3
  import { z } from 'zod';
4
- import { SessionContractError, } from '../contracts.js';
5
- import { readBoundedJsonLines, streamBoundedJsonLines } from '../readers.js';
4
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, } from '../contracts.js';
5
+ import { streamBoundedJsonLines } from '../readers.js';
6
6
  export const DEEPSEEK_HARNESS_ADAPTER_VERSION = '1.0.0';
7
7
  export const DEEPSEEK_HARNESS_SOURCE_SCHEMA_VERSION = '2.0.0';
8
8
  const Header = z.object({
@@ -39,7 +39,7 @@ export class DeepSeekHarnessSessionAdapter {
39
39
  for (const root of [...scope.allowedRoots].sort()) {
40
40
  for await (const locator of jsonlFiles(resolve(root))) {
41
41
  if (++count > this.maxFiles)
42
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'DeepSeek Harness discovery file limit exceeded');
42
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'DeepSeek Harness discovery file limit exceeded', { limit: 'maxFiles', configured: this.maxFiles, observed: count });
43
43
  yield { provider: this.provider, locator, locatorClass: 'deepseek-harness-session-v2-jsonl' };
44
44
  }
45
45
  }
@@ -47,12 +47,15 @@ export class DeepSeekHarnessSessionAdapter {
47
47
  async inspect(source) {
48
48
  let input;
49
49
  try {
50
- input = await readBoundedJsonLines({ selectedPath: source.locator, allowedRoots: source.authorizedScope.allowedRoots }, { consistency: 'provisional', limits: this.limits });
50
+ input = await streamBoundedJsonLines({ selectedPath: source.locator, allowedRoots: source.authorizedScope.allowedRoots }, { consistency: 'provisional', limits: this.limits });
51
51
  }
52
52
  catch (error) {
53
53
  throw normalizeAuthorizationError(error);
54
54
  }
55
- const header = parseHeader(input.records[0]?.value);
55
+ let header;
56
+ for await (const line of input)
57
+ header ??= parseHeader(line.value);
58
+ header ??= parseHeader(undefined);
56
59
  if (input.incompleteTail)
57
60
  throw new SessionContractError('TRUNCATED_SOURCE', 'DeepSeek Harness session has a truncated JSONL tail');
58
61
  return { sourceSchemaVersion: `${header.version}.0.0`, consistency: 'complete', operationalState: 'available' };
@@ -67,24 +70,24 @@ export class DeepSeekHarnessSessionAdapter {
67
70
  }
68
71
  const start = cursor?.value.replace(/^byte:/, '') ?? '0';
69
72
  if (!/^\d+$/.test(start))
70
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid DeepSeek Harness cursor');
73
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid DeepSeek Harness cursor', schemaIssueDetail('cursor'));
71
74
  let header;
72
75
  let expectedSeq = 0;
73
76
  let end = 0;
74
77
  for await (const line of input) {
75
78
  end = line.byteOffset + line.byteLength;
76
79
  if (Number(start) > line.byteOffset && Number(start) < end)
77
- throw new SessionContractError('SCHEMA_DRIFT', 'DeepSeek Harness cursor is not a record boundary');
80
+ throw new SessionContractError('SCHEMA_DRIFT', 'DeepSeek Harness cursor is not a record boundary', schemaIssueDetail('cursor'));
78
81
  if (!header) {
79
82
  header = parseHeader(line.value);
80
83
  continue;
81
84
  }
82
85
  const parsed = Event.safeParse(line.value);
83
86
  if (!parsed.success)
84
- throw new SessionContractError('MALFORMED_SOURCE', 'DeepSeek Harness session event is malformed');
87
+ throw new SessionContractError('MALFORMED_SOURCE', 'DeepSeek Harness session event is malformed', schemaRejectionDetail(parsed.error));
85
88
  const event = parsed.data;
86
89
  if (event.seq !== expectedSeq)
87
- throw new SessionContractError(event.seq < expectedSeq ? 'DUPLICATE_NATIVE_ID' : 'SCHEMA_DRIFT', `DeepSeek Harness sequence expected ${expectedSeq}, received ${event.seq}`);
90
+ throw new SessionContractError(event.seq < expectedSeq ? 'DUPLICATE_NATIVE_ID' : 'SCHEMA_DRIFT', `DeepSeek Harness sequence expected ${expectedSeq}, received ${event.seq}`, schemaIssueDetail('seq'));
88
91
  expectedSeq++;
89
92
  if (end <= Number(start))
90
93
  continue;
@@ -121,9 +124,9 @@ export class DeepSeekHarnessSessionAdapter {
121
124
  };
122
125
  }
123
126
  if (!header)
124
- throw new SessionContractError('MALFORMED_SOURCE', 'DeepSeek Harness session header is missing');
127
+ throw new SessionContractError('MALFORMED_SOURCE', 'DeepSeek Harness session header is missing', schemaIssueDetail('header'));
125
128
  if (Number(start) > end)
126
- throw new SessionContractError('SCHEMA_DRIFT', 'DeepSeek Harness cursor exceeds source size');
129
+ throw new SessionContractError('SCHEMA_DRIFT', 'DeepSeek Harness cursor exceeds source size', schemaIssueDetail('cursor'));
127
130
  if (input.incompleteTail)
128
131
  throw new SessionContractError('TRUNCATED_SOURCE', 'DeepSeek Harness session has a truncated JSONL tail');
129
132
  }
@@ -131,7 +134,7 @@ export class DeepSeekHarnessSessionAdapter {
131
134
  function parseHeader(value) {
132
135
  const parsed = Header.safeParse(value);
133
136
  if (!parsed.success)
134
- throw new SessionContractError('MALFORMED_SOURCE', 'DeepSeek Harness session header is malformed');
137
+ throw new SessionContractError('MALFORMED_SOURCE', 'DeepSeek Harness session header is malformed', schemaRejectionDetail(parsed.error));
135
138
  if (parsed.data.version !== 2)
136
139
  throw new SessionContractError('UNKNOWN_SCHEMA_MAJOR', `unsupported DeepSeek Harness session version: ${parsed.data.version}`);
137
140
  return parsed.data;