@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,6 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
- import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
2
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
+ import { streamBoundedJsonLines, } from '../readers.js';
4
4
  export const OPENCLAW_ADAPTER_VERSION = '1.0.0';
5
5
  export const OPENCLAW_SOURCE_SCHEMA_VERSION = '1.0.0';
6
6
  export const OPENCLAW_NATIVE_SCHEMA_VERSION = '16.0.0';
@@ -80,6 +80,13 @@ export class OpenClawSessionAdapter {
80
80
  // Gateway and consistent snapshots require explicit operator selection.
81
81
  }
82
82
  async inspect(source) {
83
+ if (SOURCE_CLASSES.has(source.locatorClass)) {
84
+ const records = this.readLocalSource(source, true);
85
+ let result = await records.next();
86
+ while (!result.done)
87
+ result = await records.next();
88
+ return result.value;
89
+ }
83
90
  const parsed = await this.readSource(source, true);
84
91
  return {
85
92
  sourceSchemaVersion: parsed.schemaVersion,
@@ -89,57 +96,7 @@ export class OpenClawSessionAdapter {
89
96
  }
90
97
  async *stream(source, cursor) {
91
98
  if (SOURCE_CLASSES.has(source.locatorClass)) {
92
- const input = await streamBoundedJsonLines({
93
- selectedPath: source.locator,
94
- allowedRoots: source.authorizedScope.allowedRoots,
95
- }, {
96
- consistency: source.locatorClass === 'openclaw-consistent-snapshot-jsonl'
97
- ? 'complete' : 'provisional',
98
- limits: this.limits,
99
- });
100
- const start = parseCursor(cursor?.value);
101
- let outputIndex = 0;
102
- let nativeSchema = null;
103
- let eventSchema = null;
104
- let sawRecord = false;
105
- for await (const line of input) {
106
- sawRecord = true;
107
- const parsed = SnapshotSchema.safeParse(line.value);
108
- if (!parsed.success) {
109
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenClaw snapshot is malformed');
110
- }
111
- const snapshot = parsed.data;
112
- const currentNative = nativeVersion(snapshot.schemaVersion);
113
- const currentEvent = nativeVersion(snapshot.eventVersion);
114
- if ((nativeSchema && nativeSchema !== currentNative)
115
- || (eventSchema && eventSchema !== currentEvent)) {
116
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenClaw snapshot schemas');
117
- }
118
- nativeSchema = currentNative;
119
- eventSchema = currentEvent;
120
- assertSupportedSchemaMajor(nativeSchema, 16);
121
- assertSupportedSchemaMajor(eventSchema, 3);
122
- if (source.locatorClass === 'openclaw-consistent-snapshot-jsonl'
123
- && snapshot.snapshotConsistency !== 'sqlite-backup') {
124
- throw new SessionContractError('SCHEMA_DRIFT', 'OpenClaw snapshot lacks sqlite3.backup() consistency evidence');
125
- }
126
- if (snapshot.incognito) {
127
- throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'OpenClaw incognito session capture is unavailable by provider policy');
128
- }
129
- if (snapshot.gateway?.mode === 'remote'
130
- && snapshot.gateway.hostId !== snapshot.gateway.expectedHostId) {
131
- throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'OpenClaw remote Gateway identity does not match the authorized host');
132
- }
133
- for (const record of normalizeSnapshot(snapshot, line, source.locatorClass)) {
134
- if (outputIndex++ >= start)
135
- yield record;
136
- }
137
- }
138
- if (!sawRecord)
139
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenClaw source is empty');
140
- if (input.incompleteTail && source.locatorClass === 'openclaw-consistent-snapshot-jsonl') {
141
- throw new SessionContractError('TRUNCATED_SOURCE', 'OpenClaw consistent snapshot is truncated');
142
- }
99
+ yield* this.readLocalSource(source, false, cursor);
143
100
  return;
144
101
  }
145
102
  const parsed = await this.readSource(source, false);
@@ -147,6 +104,79 @@ export class OpenClawSessionAdapter {
147
104
  for (const record of parsed.records.slice(start))
148
105
  yield record;
149
106
  }
107
+ async *readLocalSource(source, inspection, cursor) {
108
+ const input = await streamBoundedJsonLines({
109
+ selectedPath: source.locator,
110
+ allowedRoots: source.authorizedScope.allowedRoots,
111
+ }, {
112
+ consistency: source.locatorClass === 'openclaw-consistent-snapshot-jsonl'
113
+ ? 'complete' : 'provisional',
114
+ limits: this.limits,
115
+ });
116
+ const start = parseCursor(cursor?.value);
117
+ let outputIndex = 0;
118
+ let nativeSchema = null;
119
+ let eventSchema = null;
120
+ let sawRecord = false;
121
+ let complete = true;
122
+ let incognito = false;
123
+ let remoteMismatch = false;
124
+ let projectionIncomplete = false;
125
+ for await (const line of input) {
126
+ sawRecord = true;
127
+ const parsed = SnapshotSchema.safeParse(line.value);
128
+ if (!parsed.success) {
129
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenClaw snapshot is malformed', schemaRejectionDetail(parsed.error));
130
+ }
131
+ const snapshot = parsed.data;
132
+ const currentNative = nativeVersion(snapshot.schemaVersion);
133
+ const currentEvent = nativeVersion(snapshot.eventVersion);
134
+ if ((nativeSchema && nativeSchema !== currentNative)
135
+ || (eventSchema && eventSchema !== currentEvent)) {
136
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenClaw snapshot schemas', { issues: [{ path: nativeSchema && nativeSchema !== currentNative ? 'schemaVersion' : 'eventVersion', code: 'custom' }] });
137
+ }
138
+ nativeSchema = currentNative;
139
+ eventSchema = currentEvent;
140
+ assertSupportedSchemaMajor(nativeSchema, 16);
141
+ assertSupportedSchemaMajor(eventSchema, 3);
142
+ if (source.locatorClass === 'openclaw-consistent-snapshot-jsonl'
143
+ && snapshot.snapshotConsistency !== 'sqlite-backup') {
144
+ throw new SessionContractError('SCHEMA_DRIFT', 'OpenClaw snapshot lacks sqlite3.backup() consistency evidence', schemaIssueDetail('snapshotConsistency'));
145
+ }
146
+ incognito ||= snapshot.incognito === true;
147
+ const mismatched = snapshot.gateway?.mode === 'remote'
148
+ && snapshot.gateway.hostId !== snapshot.gateway.expectedHostId;
149
+ remoteMismatch ||= mismatched;
150
+ projectionIncomplete ||= Boolean(snapshot.projection
151
+ && (snapshot.projection.kind !== 'canonical' || !snapshot.projection.complete));
152
+ complete &&= snapshot.sessions.every(sessionComplete);
153
+ if (!inspection && snapshot.incognito) {
154
+ throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'OpenClaw incognito session capture is unavailable by provider policy');
155
+ }
156
+ if (!inspection && mismatched) {
157
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'OpenClaw remote Gateway identity does not match the authorized host');
158
+ }
159
+ if (snapshot.incognito || mismatched)
160
+ continue;
161
+ for (const record of normalizeSnapshot(snapshot, line, source.locatorClass)) {
162
+ if (outputIndex++ >= start)
163
+ yield record;
164
+ }
165
+ }
166
+ if (!sawRecord)
167
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenClaw source is empty', schemaIssueDetail('<root>'));
168
+ if (input.incompleteTail && source.locatorClass === 'openclaw-consistent-snapshot-jsonl') {
169
+ throw new SessionContractError('TRUNCATED_SOURCE', 'OpenClaw consistent snapshot is truncated');
170
+ }
171
+ return {
172
+ sourceSchemaVersion: OPENCLAW_SOURCE_SCHEMA_VERSION,
173
+ consistency: source.locatorClass === 'openclaw-consistent-snapshot-jsonl'
174
+ ? 'consistent-snapshot'
175
+ : complete && !projectionIncomplete ? 'complete' : 'provisional',
176
+ operationalState: incognito ? 'inaccessible'
177
+ : remoteMismatch || projectionIncomplete ? 'degraded' : 'available',
178
+ };
179
+ }
150
180
  async readSource(source, inspection) {
151
181
  if (source.locatorClass === 'openclaw-state-db') {
152
182
  throw new SessionContractError('UNSUPPORTED_OPERATION', 'raw OpenClaw SQLite access is unsafe; use Gateway acquisition or a verified sqlite3.backup() projection');
@@ -161,42 +191,24 @@ export class OpenClawSessionAdapter {
161
191
  }
162
192
  input = (await this.gateway.snapshot(source)).map(toRecord);
163
193
  }
164
- else if (SOURCE_CLASSES.has(source.locatorClass)) {
165
- const result = await readBoundedJsonLines({
166
- selectedPath: source.locator,
167
- allowedRoots: source.authorizedScope.allowedRoots,
168
- }, {
169
- consistency: source.locatorClass === 'openclaw-consistent-snapshot-jsonl'
170
- ? 'complete' : 'provisional',
171
- limits: this.limits,
172
- });
173
- if (result.incompleteTail && source.locatorClass === 'openclaw-consistent-snapshot-jsonl') {
174
- throw new SessionContractError('TRUNCATED_SOURCE', 'OpenClaw consistent snapshot is truncated');
175
- }
176
- input = result.records;
177
- }
178
194
  else {
179
195
  throw new SessionContractError('UNSUPPORTED_OPERATION', 'unsupported OpenClaw source class');
180
196
  }
181
197
  if (input.length === 0)
182
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenClaw source is empty');
198
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenClaw source is empty', schemaIssueDetail('<root>'));
183
199
  const parsed = input.map(({ value, ...line }) => {
184
200
  const result = SnapshotSchema.safeParse(value);
185
201
  if (!result.success)
186
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenClaw snapshot is malformed');
202
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenClaw snapshot is malformed', schemaRejectionDetail(result.error));
187
203
  return { value: result.data, ...line };
188
204
  });
189
205
  const nativeSchemas = new Set(parsed.map(({ value }) => nativeVersion(value.schemaVersion)));
190
206
  const eventSchemas = new Set(parsed.map(({ value }) => nativeVersion(value.eventVersion)));
191
207
  if (nativeSchemas.size !== 1 || eventSchemas.size !== 1) {
192
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenClaw snapshot schemas');
208
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenClaw snapshot schemas', { issues: [{ path: nativeSchemas.size !== 1 ? 'schemaVersion' : 'eventVersion', code: 'custom' }] });
193
209
  }
194
210
  assertSupportedSchemaMajor([...nativeSchemas][0], 16);
195
211
  assertSupportedSchemaMajor([...eventSchemas][0], 3);
196
- if (source.locatorClass === 'openclaw-consistent-snapshot-jsonl'
197
- && parsed.some(({ value }) => value.snapshotConsistency !== 'sqlite-backup')) {
198
- throw new SessionContractError('SCHEMA_DRIFT', 'OpenClaw snapshot lacks sqlite3.backup() consistency evidence');
199
- }
200
212
  const incognito = parsed.some(({ value }) => value.incognito);
201
213
  const remoteMismatch = parsed.some(({ value }) => value.gateway?.mode === 'remote'
202
214
  && value.gateway.hostId !== value.gateway.expectedHostId);
@@ -209,10 +221,8 @@ export class OpenClawSessionAdapter {
209
221
  const projectionIncomplete = parsed.some(({ value }) => value.projection && (value.projection.kind !== 'canonical' || !value.projection.complete));
210
222
  return {
211
223
  schemaVersion: OPENCLAW_SOURCE_SCHEMA_VERSION,
212
- consistency: source.locatorClass === 'openclaw-consistent-snapshot-jsonl'
213
- ? 'consistent-snapshot'
214
- : parsed.every(({ value }) => value.sessions.every(sessionComplete)) && !projectionIncomplete
215
- ? 'complete' : 'provisional',
224
+ consistency: parsed.every(({ value }) => value.sessions.every(sessionComplete)) && !projectionIncomplete
225
+ ? 'complete' : 'provisional',
216
226
  operationalState: incognito ? 'inaccessible' : remoteMismatch || projectionIncomplete ? 'degraded' : 'available',
217
227
  records: incognito || remoteMismatch
218
228
  ? []
@@ -354,7 +364,7 @@ function parseCursor(value) {
354
364
  if (!value)
355
365
  return 0;
356
366
  if (!/^\d+$/.test(value))
357
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid OpenClaw cursor');
367
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid OpenClaw cursor', schemaIssueDetail('cursor'));
358
368
  return Number(value);
359
369
  }
360
370
  function toRecord(value, sequence) {
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
- import { readBoundedJson, readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
2
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
+ import { readBoundedJson, streamBoundedJsonLines, } from '../readers.js';
4
4
  export const OPENCODE_ADAPTER_VERSION = '1.0.0';
5
5
  export const OPENCODE_EXPORT_SCHEMA_VERSION = '1.0.0';
6
6
  const SessionInfoSchema = z.object({
@@ -82,6 +82,14 @@ export class OpenCodeSessionAdapter {
82
82
  // Exports and loopback API/SSE endpoints require explicit selection.
83
83
  }
84
84
  async inspect(source) {
85
+ if (source.locatorClass === 'opencode-sse-jsonl') {
86
+ const records = this.stream(source);
87
+ let result = await records.next();
88
+ while (!result.done)
89
+ result = await records.next();
90
+ if (result.value)
91
+ return result.value;
92
+ }
85
93
  const parsed = await this.readSource(source);
86
94
  return {
87
95
  sourceSchemaVersion: parsed.schemaVersion,
@@ -105,12 +113,12 @@ export class OpenCodeSessionAdapter {
105
113
  for await (const line of input) {
106
114
  const parsed = EventSchema.safeParse(line.value);
107
115
  if (!parsed.success) {
108
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE event is malformed');
116
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE event is malformed', schemaRejectionDetail(parsed.error));
109
117
  }
110
118
  const event = parsed.data;
111
119
  const currentSchema = event.schemaVersion ?? OPENCODE_EXPORT_SCHEMA_VERSION;
112
120
  if (schemaVersion && schemaVersion !== currentSchema) {
113
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenCode event schemas');
121
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenCode event schemas', schemaIssueDetail('schemaVersion'));
114
122
  }
115
123
  schemaVersion = currentSchema;
116
124
  assertSupportedSchemaMajor(schemaVersion);
@@ -135,7 +143,7 @@ export class OpenCodeSessionAdapter {
135
143
  messages.set(candidate.data.id, candidate.data);
136
144
  const session = sessions.get(candidate.data.sessionID);
137
145
  if (!session) {
138
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode message event precedes its session identity');
146
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode message event precedes its session identity', schemaIssueDetail('properties.info.sessionID'));
139
147
  }
140
148
  const common = sessionExtensions({
141
149
  info: session, messages: [], sanitized: event.sanitized !== false,
@@ -150,7 +158,7 @@ export class OpenCodeSessionAdapter {
150
158
  const message = messages.get(candidate.data.messageID);
151
159
  const session = sessions.get(candidate.data.sessionID);
152
160
  if (!message || !session) {
153
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode part event precedes its message or session identity');
161
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode part event precedes its message or session identity', schemaIssueDetail(!message ? 'properties.part.messageID' : 'properties.part.sessionID'));
154
162
  }
155
163
  const common = sessionExtensions({
156
164
  info: session, messages: [], sanitized: event.sanitized !== false,
@@ -161,7 +169,7 @@ export class OpenCodeSessionAdapter {
161
169
  }
162
170
  }
163
171
  if (sessions.size + messages.size > maxJoinStates) {
164
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'OpenCode SSE join state exceeds the bounded streaming limit');
172
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'OpenCode SSE join state exceeds the bounded streaming limit', { limit: 'maxJoinStates', configured: maxJoinStates, observed: sessions.size + messages.size });
165
173
  }
166
174
  for (const record of emitted) {
167
175
  if (outputIndex++ >= start)
@@ -169,14 +177,19 @@ export class OpenCodeSessionAdapter {
169
177
  }
170
178
  }
171
179
  if (!sawSession) {
172
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE stream lacks a session identity');
180
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE stream lacks a session identity', schemaIssueDetail('properties.info.id'));
173
181
  }
174
- return;
182
+ return {
183
+ sourceSchemaVersion: schemaVersion ?? OPENCODE_EXPORT_SCHEMA_VERSION,
184
+ consistency: 'provisional',
185
+ operationalState: 'available',
186
+ };
175
187
  }
176
188
  const parsed = await this.readSource(source);
177
189
  const start = parseCursor(cursor?.value);
178
190
  for (const record of parsed.records.slice(start))
179
191
  yield record;
192
+ return undefined;
180
193
  }
181
194
  async readSource(source) {
182
195
  if (source.locatorClass === 'opencode-sqlite') {
@@ -189,16 +202,6 @@ export class OpenCodeSessionAdapter {
189
202
  }, this.limits);
190
203
  return normalizeExport(value, source.locatorClass);
191
204
  }
192
- if (source.locatorClass === 'opencode-sse-jsonl') {
193
- const result = await readBoundedJsonLines({
194
- selectedPath: source.locator,
195
- allowedRoots: source.authorizedScope.allowedRoots,
196
- }, { consistency: 'provisional', limits: this.limits });
197
- if (result.records.length === 0) {
198
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE source is empty');
199
- }
200
- return normalizeEvents(result.records, source.locatorClass);
201
- }
202
205
  const kind = source.locatorClass === 'opencode-local-api'
203
206
  ? 'api'
204
207
  : source.locatorClass === 'opencode-live-sse' ? 'sse' : undefined;
@@ -222,7 +225,7 @@ export class OpenCodeSessionAdapter {
222
225
  function normalizeExport(input, locatorClass, provisional = false) {
223
226
  const parsed = ExportSchema.safeParse(input);
224
227
  if (!parsed.success)
225
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode export is malformed');
228
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode export is malformed', schemaRejectionDetail(parsed.error));
226
229
  const value = parsed.data;
227
230
  const schema = value.schemaVersion ?? OPENCODE_EXPORT_SCHEMA_VERSION;
228
231
  assertSupportedSchemaMajor(schema);
@@ -251,12 +254,12 @@ function normalizeEvents(input, locatorClass) {
251
254
  const parsed = input.map(({ value, ...line }) => {
252
255
  const result = EventSchema.safeParse(value);
253
256
  if (!result.success)
254
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE event is malformed');
257
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE event is malformed', schemaRejectionDetail(result.error));
255
258
  return { value: result.data, ...line };
256
259
  });
257
260
  const versions = new Set(parsed.map((item) => item.value.schemaVersion ?? OPENCODE_EXPORT_SCHEMA_VERSION));
258
261
  if (versions.size !== 1)
259
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenCode event schemas');
262
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenCode event schemas', schemaIssueDetail('schemaVersion'));
260
263
  assertSupportedSchemaMajor([...versions][0]);
261
264
  const sessions = new Map();
262
265
  const messages = new Map();
@@ -280,7 +283,7 @@ function normalizeEvents(input, locatorClass) {
280
283
  }
281
284
  }
282
285
  if (sessions.size === 0) {
283
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE stream lacks a session identity');
286
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE stream lacks a session identity', schemaIssueDetail('properties.info.id'));
284
287
  }
285
288
  const records = [];
286
289
  for (const session of sessions.values()) {
@@ -444,7 +447,7 @@ function parseCursor(value) {
444
447
  if (!value)
445
448
  return 0;
446
449
  if (!/^\d+$/.test(value))
447
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid OpenCode cursor');
450
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid OpenCode cursor', schemaIssueDetail('cursor'));
448
451
  return Number(value);
449
452
  }
450
453
  const SESSION_INFO_KEYS = new Set([
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
- import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
2
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
+ import { streamBoundedJsonLines, } from '../readers.js';
4
4
  export const OPENHUMAN_ADAPTER_VERSION = '1.0.0';
5
5
  export const OPENHUMAN_SOURCE_SCHEMA_VERSION = '1.0.0';
6
6
  const MetadataSchema = z.object({
@@ -76,12 +76,11 @@ export class OpenHumanSessionAdapter {
76
76
  // session_raw and enrichment bundles require explicit selection.
77
77
  }
78
78
  async inspect(source) {
79
- const parsed = await this.readSource(source);
80
- return {
81
- sourceSchemaVersion: parsed.schemaVersion,
82
- consistency: parsed.consistency,
83
- operationalState: 'available',
84
- };
79
+ const records = this.stream(source);
80
+ let result = await records.next();
81
+ while (!result.done)
82
+ result = await records.next();
83
+ return result.value;
85
84
  }
86
85
  async *stream(source, cursor) {
87
86
  if (source.locatorClass !== 'openhuman-session-raw-jsonl'
@@ -95,6 +94,7 @@ export class OpenHumanSessionAdapter {
95
94
  }, { consistency: 'provisional', limits: this.limits });
96
95
  const threads = new Map();
97
96
  const turns = new Map();
97
+ const sessions = new Map();
98
98
  const maxJoinStates = Math.min(this.limits?.maxRecords ?? 1_000_000, 10_000);
99
99
  let schemaVersion = null;
100
100
  let outputIndex = 0;
@@ -102,12 +102,12 @@ export class OpenHumanSessionAdapter {
102
102
  for await (const line of input) {
103
103
  const parsed = RecordSchema.safeParse(line.value);
104
104
  if (!parsed.success) {
105
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman transcript record is malformed');
105
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman transcript record is malformed', schemaRejectionDetail(parsed.error));
106
106
  }
107
107
  const value = parsed.data;
108
108
  const currentVersion = version(value.schemaVersion);
109
109
  if (schemaVersion && schemaVersion !== currentVersion) {
110
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenHuman schema versions');
110
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenHuman schema versions', schemaIssueDetail('schemaVersion'));
111
111
  }
112
112
  schemaVersion = currentVersion;
113
113
  assertSupportedSchemaMajor(schemaVersion);
@@ -119,64 +119,22 @@ export class OpenHumanSessionAdapter {
119
119
  }
120
120
  else {
121
121
  sawRaw = true;
122
+ sessions.set(value.session_id, value.active === false || value.type === 'session_end');
122
123
  const record = normalize(value, line, source.locatorClass, threads.get(value.thread_id), value.request_id ? turns.get(`${value.thread_id}\0${value.request_id}`) : undefined);
123
124
  if (outputIndex++ >= start)
124
125
  yield record;
125
126
  }
126
- if (threads.size + turns.size > maxJoinStates) {
127
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'OpenHuman enrichment join state exceeds the bounded streaming limit');
127
+ if (threads.size + turns.size + sessions.size > maxJoinStates) {
128
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'OpenHuman enrichment join state exceeds the bounded streaming limit', { limit: 'maxJoinStates', configured: maxJoinStates, observed: threads.size + turns.size + sessions.size });
128
129
  }
129
130
  }
130
131
  if (!sawRaw) {
131
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman source contains no session_raw records');
132
- }
133
- }
134
- async readSource(source) {
135
- if (source.locatorClass !== 'openhuman-session-raw-jsonl'
136
- && source.locatorClass !== 'openhuman-enriched-jsonl') {
137
- throw new SessionContractError('UNSUPPORTED_OPERATION', 'unsupported OpenHuman source class');
138
- }
139
- const result = await readBoundedJsonLines({
140
- selectedPath: source.locator,
141
- allowedRoots: source.authorizedScope.allowedRoots,
142
- }, { consistency: 'provisional', limits: this.limits });
143
- if (result.records.length === 0) {
144
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman transcript source is empty');
145
- }
146
- const parsed = result.records.map(({ value, ...line }) => {
147
- const record = RecordSchema.safeParse(value);
148
- if (!record.success)
149
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman transcript record is malformed');
150
- return { value: record.data, ...line };
151
- });
152
- const versions = new Set(parsed.map(({ value }) => version(value.schemaVersion)));
153
- if (versions.size !== 1)
154
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenHuman schema versions');
155
- assertSupportedSchemaMajor([...versions][0]);
156
- const raw = parsed.filter((item) => item.value.recordType === 'session_raw');
157
- if (raw.length === 0) {
158
- throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman source contains no session_raw records');
159
- }
160
- const threads = new Map();
161
- const turns = new Map();
162
- for (const item of parsed) {
163
- if (item.value.recordType === 'thread_state')
164
- threads.set(item.value.thread_id, item.value);
165
- if (item.value.recordType === 'turn_state') {
166
- turns.set(`${item.value.thread_id}\0${item.value.request_id}`, item.value);
167
- }
168
- }
169
- const sessions = new Map();
170
- for (const item of raw) {
171
- const values = sessions.get(item.value.session_id) ?? [];
172
- values.push(item.value);
173
- sessions.set(item.value.session_id, values);
132
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman source contains no session_raw records', schemaIssueDetail('<root>'));
174
133
  }
175
134
  return {
176
- schemaVersion: OPENHUMAN_SOURCE_SCHEMA_VERSION,
177
- consistency: result.incompleteTail || [...sessions.values()].some((events) => events.at(-1)?.active !== false && events.at(-1)?.type !== 'session_end')
178
- ? 'provisional' : 'complete',
179
- records: raw.map(({ value, ...line }) => normalize(value, line, source.locatorClass, threads.get(value.thread_id), value.request_id ? turns.get(`${value.thread_id}\0${value.request_id}`) : undefined)),
135
+ sourceSchemaVersion: OPENHUMAN_SOURCE_SCHEMA_VERSION,
136
+ consistency: !input.incompleteTail && [...sessions.values()].every(Boolean) ? 'complete' : 'provisional',
137
+ operationalState: 'available',
180
138
  };
181
139
  }
182
140
  }
@@ -296,7 +254,7 @@ function parseCursor(value) {
296
254
  if (!value)
297
255
  return 0;
298
256
  if (!/^\d+$/.test(value))
299
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid OpenHuman cursor');
257
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid OpenHuman cursor', schemaIssueDetail('cursor'));
300
258
  return Number(value);
301
259
  }
302
260
  const RAW_KEYS = new Set([
@@ -1,8 +1,8 @@
1
1
  import { opendir } from 'node:fs/promises';
2
- import { basename, resolve } from 'node:path';
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 PI_ADAPTER_VERSION = '1.0.0';
7
7
  export const PI_SOURCE_SCHEMA_VERSION = '3.0.0';
8
8
  const Header = z.object({ type: z.literal('session'), version: z.number().int(), id: z.string().min(1),
@@ -29,7 +29,7 @@ export class PiSessionAdapter {
29
29
  for (const root of [...scope.allowedRoots].sort()) {
30
30
  for await (const locator of jsonlFiles(resolve(root))) {
31
31
  if (++count > this.maxFiles)
32
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Pi discovery file limit exceeded');
32
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Pi discovery file limit exceeded', { limit: 'maxFiles', configured: this.maxFiles, observed: count });
33
33
  yield { provider: 'pi', locator, locatorClass: 'pi-session-v3-jsonl' };
34
34
  }
35
35
  }
@@ -37,13 +37,16 @@ export class PiSessionAdapter {
37
37
  async inspect(source) {
38
38
  let input;
39
39
  try {
40
- input = await readBoundedJsonLines({ selectedPath: source.locator,
40
+ input = await streamBoundedJsonLines({ selectedPath: source.locator,
41
41
  allowedRoots: source.authorizedScope.allowedRoots }, { consistency: 'provisional', limits: this.limits });
42
42
  }
43
43
  catch (error) {
44
44
  throw normalizeAuthorizationError(error);
45
45
  }
46
- const header = parseHeader(input.records[0]?.value);
46
+ let header;
47
+ for await (const line of input)
48
+ header ??= parseHeader(line.value);
49
+ header ??= parseHeader(undefined);
47
50
  if (input.incompleteTail)
48
51
  throw new SessionContractError('TRUNCATED_SOURCE', 'Pi session has a truncated JSONL tail');
49
52
  return { sourceSchemaVersion: `${header.version}.0.0`, consistency: 'complete', operationalState: 'available' };
@@ -52,28 +55,38 @@ export class PiSessionAdapter {
52
55
  let input;
53
56
  try {
54
57
  input = await streamBoundedJsonLines({ selectedPath: source.locator,
55
- allowedRoots: source.authorizedScope.allowedRoots }, { cursor: cursor?.value, consistency: 'provisional', limits: this.limits });
58
+ allowedRoots: source.authorizedScope.allowedRoots }, { consistency: 'provisional', limits: this.limits });
56
59
  }
57
60
  catch (error) {
58
61
  throw normalizeAuthorizationError(error);
59
62
  }
60
63
  let sessionId = '';
61
- let index = 0;
64
+ const value = cursor?.value || '0';
65
+ const byteCursor = value.startsWith('byte:');
66
+ const position = value.replace(/^byte:/, '');
67
+ if (!/^\d+$/.test(position))
68
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Pi cursor', schemaIssueDetail('cursor'));
69
+ const start = Number(position);
70
+ let end = 0;
62
71
  const ids = new Set();
63
72
  for await (const line of input) {
64
- if (index++ === 0 && !cursor) {
73
+ end = line.byteOffset + line.byteLength;
74
+ if (byteCursor && start > line.byteOffset && start < end) {
75
+ throw new SessionContractError('SCHEMA_DRIFT', 'Pi cursor is not a record boundary', schemaIssueDetail('cursor'));
76
+ }
77
+ if (line.sequence === 0) {
65
78
  sessionId = parseHeader(line.value).id;
66
79
  continue;
67
80
  }
68
- if (!sessionId)
69
- sessionId = basename(source.locator, '.jsonl');
70
81
  const parsed = Entry.safeParse(line.value);
71
82
  if (!parsed.success)
72
- throw new SessionContractError('MALFORMED_SOURCE', 'Pi session entry is malformed');
83
+ throw new SessionContractError('MALFORMED_SOURCE', 'Pi session entry is malformed', schemaRejectionDetail(parsed.error));
73
84
  const entry = parsed.data;
74
85
  if (ids.has(entry.id))
75
86
  throw new SessionContractError('DUPLICATE_NATIVE_ID', 'Pi session contains a duplicate entry id');
76
87
  ids.add(entry.id);
88
+ if (byteCursor ? end <= start : line.sequence <= start)
89
+ continue;
77
90
  const message = object(entry.message);
78
91
  const role = string(message.role) ?? roleFor(entry.type);
79
92
  const sensitive = role === 'toolResult' || entry.type === 'custom';
@@ -81,7 +94,7 @@ export class PiSessionAdapter {
81
94
  yield { nativeSessionId: sessionId, nativeEventId: entry.id, sequence: line.sequence,
82
95
  kind: kindFor(entry.type, role), role, toolName: sensitive ? string(message.toolName) : undefined,
83
96
  toolCallId: sensitive ? string(message.toolCallId) : undefined,
84
- occurredAt: entry.timestamp, text, sourceCursor: String(line.byteOffset + line.byteLength),
97
+ occurredAt: entry.timestamp, text, sourceCursor: `byte:${end}`,
85
98
  sourceBytes: line.byteLength, rawReference: { locatorClass: 'pi-session-v3-jsonl', offset: line.byteOffset },
86
99
  activityBoundary: entry.type === 'compaction' ? 'continuation' : undefined,
87
100
  activityBoundaryBasis: entry.type === 'compaction' ? 'pi-session:compaction' : undefined,
@@ -90,6 +103,8 @@ export class PiSessionAdapter {
90
103
  opaque: !KNOWN.has(entry.type), redacted: sensitive,
91
104
  nativeType: entry.type, parentId: entry.parentId } };
92
105
  }
106
+ if (byteCursor && start > end)
107
+ throw new SessionContractError('SCHEMA_DRIFT', 'Pi cursor exceeds source size', schemaIssueDetail('cursor'));
93
108
  if (input.incompleteTail)
94
109
  throw new SessionContractError('TRUNCATED_SOURCE', 'Pi session has a truncated JSONL tail');
95
110
  }
@@ -99,7 +114,7 @@ const KNOWN = new Set(['message', 'thinking_level_change', 'model_change', 'comp
99
114
  function parseHeader(value) {
100
115
  const parsed = Header.safeParse(value);
101
116
  if (!parsed.success)
102
- throw new SessionContractError('MALFORMED_SOURCE', 'Pi session header is malformed');
117
+ throw new SessionContractError('MALFORMED_SOURCE', 'Pi session header is malformed', schemaRejectionDetail(parsed.error));
103
118
  if (parsed.data.version !== 3)
104
119
  throw new SessionContractError('UNKNOWN_SCHEMA_MAJOR', `unsupported Pi session version: ${parsed.data.version}`);
105
120
  return parsed.data;
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { basename } from 'node:path';
3
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
+ import { SessionContractError, schemaIssueDetail, assertSupportedSchemaMajor, } from '../contracts.js';
4
4
  import { readBoundedText } from '../readers.js';
5
5
  export const WARP_ADAPTER_VERSION = '1.0.0';
6
6
  export const WARP_MARKDOWN_SCHEMA_VERSION = '1.0.0';
@@ -48,7 +48,7 @@ export class WarpSessionAdapter {
48
48
  assertSupportedSchemaMajor(schemaVersion);
49
49
  const blocks = parseBlocks(value);
50
50
  if (blocks.length === 0) {
51
- throw new SessionContractError('MALFORMED_SOURCE', 'Warp Markdown export contains no recognized user or assistant conversation blocks');
51
+ throw new SessionContractError('MALFORMED_SOURCE', 'Warp Markdown export contains no recognized user or assistant conversation blocks', schemaIssueDetail('headings'));
52
52
  }
53
53
  const stableSessionId = `manual:${createHash('sha256').update(value).digest('hex').slice(0, 32)}`;
54
54
  const lifecycleEvidence = /<!--\s*warp-lifecycle:\s*complete\s*-->/i.test(value)
@@ -154,7 +154,7 @@ function parseCursor(value) {
154
154
  if (!value)
155
155
  return 0;
156
156
  if (!/^\d+$/.test(value))
157
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Warp cursor');
157
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Warp cursor', schemaIssueDetail('cursor'));
158
158
  return Number(value);
159
159
  }
160
160
  //# sourceMappingURL=warp.js.map