@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.
- package/README.md +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +69 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +286 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +130 -0
- package/dist/src/artifacts/index-refresh.js +591 -0
- package/dist/src/artifacts/index-worker.mjs +11 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +137 -43
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +95 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +252 -12
- package/dist/src/sessions/contracts.js +50 -3
- package/dist/src/sessions/coverage-preflight.js +27 -2
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +145 -26
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +105 -0
- package/dist/src/sessions/repository.js +151 -54
- package/dist/src/sessions/workspace-discovery.js +292 -60
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- 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, extname, resolve } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
5
|
-
import {
|
|
4
|
+
import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
5
|
+
import { streamBoundedJsonLines, } from '../readers.js';
|
|
6
6
|
export const FACTORY_ADAPTER_VERSION = '1.0.0';
|
|
7
7
|
export const FACTORY_SOURCE_SCHEMA_VERSION = '1.0.0';
|
|
8
8
|
const FactoryRecordSchema = z.object({
|
|
@@ -51,13 +51,21 @@ export class FactorySessionAdapter {
|
|
|
51
51
|
for (const root of [...scope.allowedRoots].sort()) {
|
|
52
52
|
for await (const locator of discoverJsonl(resolve(root), this.discoveryLimits.maxDepth)) {
|
|
53
53
|
if (++emitted > this.discoveryLimits.maxFiles) {
|
|
54
|
-
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Factory source discovery exceeded the authorized file limit');
|
|
54
|
+
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Factory source discovery exceeded the authorized file limit', { limit: 'maxFiles', configured: this.discoveryLimits.maxFiles, observed: emitted });
|
|
55
55
|
}
|
|
56
56
|
yield { provider: 'factory', locator, locatorClass: 'factory-droid-jsonl' };
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
async inspect(source) {
|
|
61
|
+
if (source.locatorClass === 'factory-droid-jsonl') {
|
|
62
|
+
const records = this.stream(source);
|
|
63
|
+
let result = await records.next();
|
|
64
|
+
while (!result.done)
|
|
65
|
+
result = await records.next();
|
|
66
|
+
if (result.value)
|
|
67
|
+
return result.value;
|
|
68
|
+
}
|
|
61
69
|
const parsed = await this.readSource(source);
|
|
62
70
|
return {
|
|
63
71
|
sourceSchemaVersion: parsed.schemaVersion,
|
|
@@ -76,6 +84,7 @@ export class FactorySessionAdapter {
|
|
|
76
84
|
let establishedId;
|
|
77
85
|
let schemaVersion = null;
|
|
78
86
|
let sawRecord = false;
|
|
87
|
+
let complete = false;
|
|
79
88
|
for await (const line of input) {
|
|
80
89
|
sawRecord = true;
|
|
81
90
|
const parsed = FactoryRecordSchema.safeParse(line.value);
|
|
@@ -85,7 +94,7 @@ export class FactorySessionAdapter {
|
|
|
85
94
|
const value = parsed.data;
|
|
86
95
|
const currentSchema = value.schemaVersion ?? FACTORY_SOURCE_SCHEMA_VERSION;
|
|
87
96
|
if (schemaVersion && schemaVersion !== currentSchema) {
|
|
88
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Factory source schemas');
|
|
97
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Factory source schemas', schemaIssueDetail('schemaVersion'));
|
|
89
98
|
}
|
|
90
99
|
schemaVersion = currentSchema;
|
|
91
100
|
assertSupportedSchemaMajor(schemaVersion);
|
|
@@ -95,44 +104,39 @@ export class FactorySessionAdapter {
|
|
|
95
104
|
throw new SessionContractError('DUPLICATE_NATIVE_ID', 'Factory source changes session identity');
|
|
96
105
|
}
|
|
97
106
|
establishedId = currentId;
|
|
98
|
-
|
|
107
|
+
const normalized = normalize([line], source, establishedId);
|
|
108
|
+
complete ||= normalized.complete;
|
|
109
|
+
for (const record of normalized.records) {
|
|
99
110
|
if (outputIndex++ >= start)
|
|
100
111
|
yield record;
|
|
101
112
|
}
|
|
102
113
|
}
|
|
103
114
|
if (!sawRecord && !input.incompleteTail) {
|
|
104
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'Factory session source is empty');
|
|
115
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Factory session source is empty', schemaIssueDetail('<root>'));
|
|
105
116
|
}
|
|
106
|
-
return
|
|
117
|
+
return {
|
|
118
|
+
sourceSchemaVersion: schemaVersion ?? FACTORY_SOURCE_SCHEMA_VERSION,
|
|
119
|
+
consistency: complete && !input.incompleteTail ? 'complete' : 'provisional',
|
|
120
|
+
operationalState: 'available',
|
|
121
|
+
};
|
|
107
122
|
}
|
|
108
123
|
const parsed = await this.readSource(source);
|
|
109
124
|
const start = parseCursor(cursor?.value);
|
|
110
125
|
for (const record of parsed.records.slice(start))
|
|
111
126
|
yield record;
|
|
127
|
+
return undefined;
|
|
112
128
|
}
|
|
113
129
|
async readSource(source) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const result = await readBoundedJsonLines({
|
|
118
|
-
selectedPath: source.locator,
|
|
119
|
-
allowedRoots: source.authorizedScope.allowedRoots,
|
|
120
|
-
}, { consistency: 'provisional', limits: this.limits });
|
|
121
|
-
input = result.records;
|
|
122
|
-
incompleteTail = result.incompleteTail;
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
input = await this.remoteRecords(source);
|
|
126
|
-
}
|
|
127
|
-
if (input.length === 0 && !incompleteTail) {
|
|
128
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'Factory session source is empty');
|
|
130
|
+
const input = await this.remoteRecords(source);
|
|
131
|
+
if (input.length === 0) {
|
|
132
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Factory session source is empty', schemaIssueDetail('<root>'));
|
|
129
133
|
}
|
|
130
134
|
const schemaVersion = declaredVersion(input);
|
|
131
135
|
assertSupportedSchemaMajor(schemaVersion);
|
|
132
136
|
const normalized = normalize(input, source);
|
|
133
137
|
return {
|
|
134
138
|
schemaVersion,
|
|
135
|
-
consistency: normalized.complete
|
|
139
|
+
consistency: normalized.complete ? 'complete' : 'provisional',
|
|
136
140
|
records: normalized.records,
|
|
137
141
|
};
|
|
138
142
|
}
|
|
@@ -155,7 +159,7 @@ export class FactorySessionAdapter {
|
|
|
155
159
|
}
|
|
156
160
|
const values = await transport.snapshot(source);
|
|
157
161
|
if (values.length > (this.limits?.maxRecords ?? 1_000_000)) {
|
|
158
|
-
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Factory remote record limit exceeded');
|
|
162
|
+
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Factory remote record limit exceeded', { limit: 'maxRecords', configured: this.limits?.maxRecords ?? 1_000_000, observed: values.length });
|
|
159
163
|
}
|
|
160
164
|
return values.map((value, sequence) => ({
|
|
161
165
|
value, sequence, byteOffset: sequence, byteLength: Buffer.byteLength(JSON.stringify(value)),
|
|
@@ -330,7 +334,7 @@ function factoryMalformedRecord(source, record, error) {
|
|
|
330
334
|
? issue.path.map(String).join('.')
|
|
331
335
|
: 'record';
|
|
332
336
|
return new SessionContractError('MALFORMED_SOURCE', `Factory source ${basename(source.locator)} record ${record.sequence + 1} `
|
|
333
|
-
+ `type ${type} failed requirement ${requirement} (${issue?.code ?? 'invalid_type'})
|
|
337
|
+
+ `type ${type} failed requirement ${requirement} (${issue?.code ?? 'invalid_type'})`, schemaRejectionDetail(error));
|
|
334
338
|
}
|
|
335
339
|
function declaredVersion(records) {
|
|
336
340
|
const versions = new Set(records.flatMap((record) => {
|
|
@@ -338,7 +342,7 @@ function declaredVersion(records) {
|
|
|
338
342
|
return typeof value.schemaVersion === 'string' ? [value.schemaVersion] : [];
|
|
339
343
|
}));
|
|
340
344
|
if (versions.size > 1)
|
|
341
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Factory schema versions');
|
|
345
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Factory schema versions', schemaIssueDetail('schemaVersion'));
|
|
342
346
|
return [...versions][0] ?? FACTORY_SOURCE_SCHEMA_VERSION;
|
|
343
347
|
}
|
|
344
348
|
function timestamp(value) {
|
|
@@ -375,7 +379,7 @@ function parseCursor(value) {
|
|
|
375
379
|
if (!value)
|
|
376
380
|
return 0;
|
|
377
381
|
if (!/^\d+$/.test(value))
|
|
378
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Factory cursor');
|
|
382
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Factory cursor', schemaIssueDetail('cursor'));
|
|
379
383
|
return Number(value);
|
|
380
384
|
}
|
|
381
385
|
const RECORD_KEYS = new Set([
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
2
|
+
import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
3
3
|
import { streamBoundedJsonLines } from '../readers.js';
|
|
4
4
|
export const GENERIC_INTERCHANGE_KIND = 'aiwg.session-interchange';
|
|
5
5
|
export const GENERIC_ADAPTER_VERSION = '1.0.0';
|
|
@@ -80,7 +80,7 @@ export class GenericSessionInterchangeAdapter {
|
|
|
80
80
|
sessionSequences.add(sequenceKey);
|
|
81
81
|
}
|
|
82
82
|
if (!header)
|
|
83
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty');
|
|
83
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty', schemaIssueDetail('<root>'));
|
|
84
84
|
if (input.incompleteTail) {
|
|
85
85
|
throw new SessionContractError('TRUNCATED_SOURCE', 'generic interchange contains a truncated record');
|
|
86
86
|
}
|
|
@@ -100,7 +100,7 @@ export class GenericSessionInterchangeAdapter {
|
|
|
100
100
|
const first = await iterator.next();
|
|
101
101
|
await iterator.return?.();
|
|
102
102
|
if (first.done)
|
|
103
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty');
|
|
103
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty', schemaIssueDetail('<root>'));
|
|
104
104
|
header = this.validateHeader(first.value.value, source);
|
|
105
105
|
}
|
|
106
106
|
const input = await this.openStream(source, byteCursor ?? undefined);
|
|
@@ -160,7 +160,7 @@ export class GenericSessionInterchangeAdapter {
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
if (!header) {
|
|
163
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty');
|
|
163
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty', schemaIssueDetail('<root>'));
|
|
164
164
|
}
|
|
165
165
|
if (input.incompleteTail) {
|
|
166
166
|
throw new SessionContractError('TRUNCATED_SOURCE', 'generic interchange contains a truncated record');
|
|
@@ -183,7 +183,7 @@ export class GenericSessionInterchangeAdapter {
|
|
|
183
183
|
// SCHEMA_DRIFT: the selected identity no longer describes this file's
|
|
184
184
|
// declared source, so importing it would attach events to the wrong source.
|
|
185
185
|
throw new SessionContractError('SCHEMA_DRIFT', `selected source identity does not match interchange header: --source-id ${quoteValue(source.sourceId)} `
|
|
186
|
-
+ `but header sourceId is ${quoteValue(header.sourceId)}; pass --source-id ${quoteValue(header.sourceId)}
|
|
186
|
+
+ `but header sourceId is ${quoteValue(header.sourceId)}; pass --source-id ${quoteValue(header.sourceId)}`, schemaIssueDetail('sourceId'));
|
|
187
187
|
}
|
|
188
188
|
return header;
|
|
189
189
|
}
|
|
@@ -193,7 +193,7 @@ function parseByteCursor(value) {
|
|
|
193
193
|
return null;
|
|
194
194
|
const position = value.slice(5);
|
|
195
195
|
if (!/^\d+$/.test(position)) {
|
|
196
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'invalid generic byte cursor');
|
|
196
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid generic byte cursor', schemaIssueDetail('cursor'));
|
|
197
197
|
}
|
|
198
198
|
return Number(position);
|
|
199
199
|
}
|
|
@@ -205,9 +205,9 @@ function parseHeader(value) {
|
|
|
205
205
|
throw new SessionContractError('AMBIGUOUS_TIMESTAMP', 'generic interchange header has an ambiguous timestamp');
|
|
206
206
|
}
|
|
207
207
|
if (!isDeclaredInterchange(value)) {
|
|
208
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'input is not a declared AIWG generic session interchange');
|
|
208
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'input is not a declared AIWG generic session interchange', schemaRejectionDetail(result.error));
|
|
209
209
|
}
|
|
210
|
-
throw new SessionContractError('MALFORMED_SOURCE', `generic interchange header ${describeIssue(result.error, true)}
|
|
210
|
+
throw new SessionContractError('MALFORMED_SOURCE', `generic interchange header ${describeIssue(result.error, true)}`, schemaRejectionDetail(result.error));
|
|
211
211
|
}
|
|
212
212
|
function parseEvent(value) {
|
|
213
213
|
const result = GenericInterchangeEventSchema.safeParse(value);
|
|
@@ -218,7 +218,7 @@ function parseEvent(value) {
|
|
|
218
218
|
}
|
|
219
219
|
// Event values can carry transcript content, so name the field and the
|
|
220
220
|
// expected shape without echoing the received value.
|
|
221
|
-
throw new SessionContractError('MALFORMED_SOURCE', `generic interchange event is malformed: ${describeIssue(result.error, false)}
|
|
221
|
+
throw new SessionContractError('MALFORMED_SOURCE', `generic interchange event is malformed: ${describeIssue(result.error, false)}`, schemaRejectionDetail(result.error));
|
|
222
222
|
}
|
|
223
223
|
function isDeclaredInterchange(value) {
|
|
224
224
|
return typeof value === 'object' && value !== null
|
|
@@ -253,7 +253,7 @@ function parseEventCursor(value) {
|
|
|
253
253
|
if (value === undefined || value === '')
|
|
254
254
|
return 0;
|
|
255
255
|
if (!/^\d+$/.test(value))
|
|
256
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'generic event cursor is invalid');
|
|
256
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'generic event cursor is invalid', schemaIssueDetail('cursor'));
|
|
257
257
|
return Number(value);
|
|
258
258
|
}
|
|
259
259
|
//# sourceMappingURL=generic.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { basename, extname } from 'node:path';
|
|
2
|
-
import { SessionContractError, } from '../contracts.js';
|
|
2
|
+
import { SessionContractError, schemaIssueDetail, } from '../contracts.js';
|
|
3
3
|
import { readBoundedText } from '../readers.js';
|
|
4
4
|
export const GROK_BUILD_ADAPTER_VERSION = '1.0.0';
|
|
5
5
|
export const GROK_BUILD_CLI_EXPORT_SCHEMA_VERSION = '1.0.0';
|
|
@@ -75,7 +75,7 @@ export class GrokBuildSessionAdapter {
|
|
|
75
75
|
}
|
|
76
76
|
const filename = basename(source.locator, extname(source.locator));
|
|
77
77
|
if (extname(source.locator).toLowerCase() !== '.md' || !UUID.test(filename)) {
|
|
78
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'Grok Build export filename must be <session-id>.md so the documented CLI session ID is preserved.');
|
|
78
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Grok Build export filename must be <session-id>.md so the documented CLI session ID is preserved.', schemaIssueDetail('filename'));
|
|
79
79
|
}
|
|
80
80
|
const { value } = await readBoundedText({
|
|
81
81
|
selectedPath: source.locator,
|
|
@@ -93,7 +93,7 @@ export function validateGrokBuildMarkdown(value) {
|
|
|
93
93
|
parseTools(block.text, sequence);
|
|
94
94
|
}
|
|
95
95
|
if (blocks.length === 0 || !blocks.some((block) => block.heading === 'User')) {
|
|
96
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'Unrecognized Grok Build CLI export; expected exact `## User`, `## Assistant`, or `## Tools` headings.');
|
|
96
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Unrecognized Grok Build CLI export; expected exact `## User`, `## Assistant`, or `## Tools` headings.', schemaIssueDetail('headings'));
|
|
97
97
|
}
|
|
98
98
|
return blocks;
|
|
99
99
|
}
|
|
@@ -105,7 +105,7 @@ function parseBlocks(value) {
|
|
|
105
105
|
return;
|
|
106
106
|
const text = current.lines.join('\n').trim();
|
|
107
107
|
if (!text && current.heading === 'Tools') {
|
|
108
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build tools block is empty');
|
|
108
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build tools block is empty', schemaIssueDetail('tools'));
|
|
109
109
|
}
|
|
110
110
|
if (text)
|
|
111
111
|
blocks.push({ heading: current.heading, text });
|
|
@@ -117,13 +117,13 @@ function parseBlocks(value) {
|
|
|
117
117
|
current = { heading: match[1], lines: [] };
|
|
118
118
|
}
|
|
119
119
|
else if (/^##\s+/.test(line)) {
|
|
120
|
-
throw new SessionContractError('SCHEMA_DRIFT', `unknown Grok Build CLI export heading: ${line}
|
|
120
|
+
throw new SessionContractError('SCHEMA_DRIFT', `unknown Grok Build CLI export heading: ${line}`, schemaIssueDetail('headings'));
|
|
121
121
|
}
|
|
122
122
|
else if (current) {
|
|
123
123
|
current.lines.push(line);
|
|
124
124
|
}
|
|
125
125
|
else if (line.trim()) {
|
|
126
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'unexpected content before the first Grok Build CLI export heading');
|
|
126
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'unexpected content before the first Grok Build CLI export heading', schemaIssueDetail('headings'));
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
flush();
|
|
@@ -132,17 +132,17 @@ function parseBlocks(value) {
|
|
|
132
132
|
function parseTools(text, blockSequence) {
|
|
133
133
|
const lines = text.split('\n').filter((line) => line.trim());
|
|
134
134
|
if (lines.length === 0 || lines.length > 1_000) {
|
|
135
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build tools block has no summaries or exceeds 1,000 summaries');
|
|
135
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build tools block has no summaries or exceeds 1,000 summaries', schemaIssueDetail('tools'));
|
|
136
136
|
}
|
|
137
137
|
return lines.map((line, index) => {
|
|
138
138
|
const match = /^- (.+)$/.exec(line);
|
|
139
139
|
if (!match) {
|
|
140
|
-
throw new SessionContractError('SCHEMA_DRIFT', `unrecognized Grok Build tool summary: ${line}
|
|
140
|
+
throw new SessionContractError('SCHEMA_DRIFT', `unrecognized Grok Build tool summary: ${line}`, schemaIssueDetail('tools'));
|
|
141
141
|
}
|
|
142
142
|
const summary = match[1];
|
|
143
143
|
const name = summary.split(/:| \(/, 1)[0]?.trim();
|
|
144
144
|
if (!name) {
|
|
145
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'empty Grok Build tool summary');
|
|
145
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'empty Grok Build tool summary', schemaIssueDetail('tools'));
|
|
146
146
|
}
|
|
147
147
|
return { sequence: blockSequence * 1_000 + index, name, text: summary };
|
|
148
148
|
});
|
|
@@ -176,11 +176,11 @@ function parseCursor(value) {
|
|
|
176
176
|
if (!value)
|
|
177
177
|
return 0;
|
|
178
178
|
if (!/^\d+$/.test(value)) {
|
|
179
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Build export cursor');
|
|
179
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Build export cursor', schemaIssueDetail('cursor'));
|
|
180
180
|
}
|
|
181
181
|
const cursor = Number(value);
|
|
182
182
|
if (!Number.isSafeInteger(cursor)) {
|
|
183
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Build export cursor');
|
|
183
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Build export cursor', schemaIssueDetail('cursor'));
|
|
184
184
|
}
|
|
185
185
|
return cursor;
|
|
186
186
|
}
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
import { opendir } from 'node:fs/promises';
|
|
56
56
|
import { resolve } from 'node:path';
|
|
57
57
|
import { z } from 'zod';
|
|
58
|
-
import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
58
|
+
import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
59
59
|
import { readBoundedJson } from '../readers.js';
|
|
60
60
|
import { GenericSessionInterchangeAdapter, } from './generic.js';
|
|
61
61
|
import { classifyGrokbotBlobName, grokbotConversationIdFromLocator, } from './grokbot-locator.js';
|
|
@@ -130,10 +130,10 @@ const NoticeEntrySchema = GrokbotEntryBaseSchema.extend({
|
|
|
130
130
|
});
|
|
131
131
|
const GrokbotReplicaValueSchema = z.object({
|
|
132
132
|
entries: z.array(GrokbotEntryBaseSchema),
|
|
133
|
-
epochHint: z.string().
|
|
134
|
-
acceptedSequenceHint: z.number().int().nonnegative().
|
|
135
|
-
persistedAt: z.number().int().nonnegative().
|
|
136
|
-
precedingTimestampMs: z.number().int().nonnegative().
|
|
133
|
+
epochHint: z.string().nullish(),
|
|
134
|
+
acceptedSequenceHint: z.number().int().nonnegative().nullish(),
|
|
135
|
+
persistedAt: z.number().int().nonnegative().nullish(),
|
|
136
|
+
precedingTimestampMs: z.number().int().nonnegative().nullish(),
|
|
137
137
|
}).passthrough();
|
|
138
138
|
const GrokbotReplicaDocumentSchema = z.object({
|
|
139
139
|
schemaVersion: z.number().int().nonnegative(),
|
|
@@ -181,7 +181,7 @@ export class GrokbotSessionAdapter {
|
|
|
181
181
|
if (classification?.kind !== 'transcript-replica')
|
|
182
182
|
continue;
|
|
183
183
|
if (++count > this.maxFiles) {
|
|
184
|
-
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Grok Bot native discovery exceeded the authorized file limit');
|
|
184
|
+
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Grok Bot native discovery exceeded the authorized file limit', { limit: 'maxFiles', configured: this.maxFiles, observed: count });
|
|
185
185
|
}
|
|
186
186
|
yield {
|
|
187
187
|
provider: 'grokbot',
|
|
@@ -268,7 +268,7 @@ export class GrokbotSessionAdapter {
|
|
|
268
268
|
}, this.limits);
|
|
269
269
|
const parsed = GrokbotReplicaDocumentSchema.safeParse(value);
|
|
270
270
|
if (!parsed.success) {
|
|
271
|
-
throw
|
|
271
|
+
throw malformedGrokbotSource('transcript replica', parsed.error);
|
|
272
272
|
}
|
|
273
273
|
assertSupportedSchemaMajor(`${parsed.data.schemaVersion}.0.0`, GROKBOT_NATIVE_SCHEMA_MAJOR);
|
|
274
274
|
const { entries, precedingTimestampMs } = parsed.data.value;
|
|
@@ -287,7 +287,7 @@ function parseNativeCursor(value) {
|
|
|
287
287
|
return 0;
|
|
288
288
|
const raw = value.startsWith('native:') ? value.slice('native:'.length) : value;
|
|
289
289
|
if (!/^\d+$/.test(raw))
|
|
290
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Bot native cursor');
|
|
290
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Bot native cursor', schemaIssueDetail('cursor'));
|
|
291
291
|
return Number(raw);
|
|
292
292
|
}
|
|
293
293
|
function mapGrokbotEntry(entry) {
|
|
@@ -308,10 +308,21 @@ function mapGrokbotEntry(entry) {
|
|
|
308
308
|
return null;
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
|
+
function malformedGrokbotSource(subject, error) {
|
|
312
|
+
// Include only schema paths and codes, never Zod messages or input values.
|
|
313
|
+
const issues = error.issues.map((issue) => `${issue.path.join('.') || '<root>'} (${issue.code})`);
|
|
314
|
+
return new SessionContractError('MALFORMED_SOURCE', `Grok Bot ${subject} is malformed at ${issues.join(', ')}`, schemaRejectionDetail(error));
|
|
315
|
+
}
|
|
311
316
|
function mapSendMessage(raw, occurredAt) {
|
|
317
|
+
const messageType = z.object({ type: z.string().min(1) }).safeParse(raw.message);
|
|
318
|
+
if (messageType.success && !SendMessageEntrySchema.shape.message.options.some((option) => option.shape.type.value === messageType.data.type)) {
|
|
319
|
+
// Like unknown entry kinds, unknown message types are skipped without inventing content.
|
|
320
|
+
// Known types still go through their full schema validation below.
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
312
323
|
const parsed = SendMessageEntrySchema.safeParse(raw);
|
|
313
324
|
if (!parsed.success) {
|
|
314
|
-
throw
|
|
325
|
+
throw malformedGrokbotSource('send-message entry', parsed.error);
|
|
315
326
|
}
|
|
316
327
|
const entry = parsed.data;
|
|
317
328
|
const { message } = entry;
|
|
@@ -359,7 +370,7 @@ function mapSendMessage(raw, occurredAt) {
|
|
|
359
370
|
function mapMessage(raw, occurredAt) {
|
|
360
371
|
const parsed = MessageEntrySchema.safeParse(raw);
|
|
361
372
|
if (!parsed.success) {
|
|
362
|
-
throw
|
|
373
|
+
throw malformedGrokbotSource('message entry', parsed.error);
|
|
363
374
|
}
|
|
364
375
|
const entry = parsed.data;
|
|
365
376
|
return {
|
|
@@ -380,7 +391,7 @@ function mapMessage(raw, occurredAt) {
|
|
|
380
391
|
function mapUserAttachment(raw, occurredAt) {
|
|
381
392
|
const parsed = UserAttachmentEntrySchema.safeParse(raw);
|
|
382
393
|
if (!parsed.success) {
|
|
383
|
-
throw
|
|
394
|
+
throw malformedGrokbotSource('user-attachment entry', parsed.error);
|
|
384
395
|
}
|
|
385
396
|
const entry = parsed.data;
|
|
386
397
|
return {
|
|
@@ -405,7 +416,7 @@ function mapUserAttachment(raw, occurredAt) {
|
|
|
405
416
|
function mapEvent(raw, occurredAt) {
|
|
406
417
|
const parsed = EventEntrySchema.safeParse(raw);
|
|
407
418
|
if (!parsed.success) {
|
|
408
|
-
throw
|
|
419
|
+
throw malformedGrokbotSource('event entry', parsed.error);
|
|
409
420
|
}
|
|
410
421
|
const entry = parsed.data;
|
|
411
422
|
const label = stringOrEmpty(entry.event.kind) || stringOrEmpty(entry.event.type) || 'event';
|
|
@@ -426,7 +437,7 @@ function mapEvent(raw, occurredAt) {
|
|
|
426
437
|
function mapNotice(raw, occurredAt) {
|
|
427
438
|
const parsed = NoticeEntrySchema.safeParse(raw);
|
|
428
439
|
if (!parsed.success) {
|
|
429
|
-
throw
|
|
440
|
+
throw malformedGrokbotSource('notice entry', parsed.error);
|
|
430
441
|
}
|
|
431
442
|
const entry = parsed.data;
|
|
432
443
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
3
|
-
import {
|
|
2
|
+
import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
3
|
+
import { streamBoundedJsonLines, } from '../readers.js';
|
|
4
4
|
export const HERMES_ADAPTER_VERSION = '1.0.0';
|
|
5
5
|
export const HERMES_EXPORT_SCHEMA_VERSION = '1.0.0';
|
|
6
6
|
export const HERMES_NATIVE_SCHEMA_VERSION = '23.0.0';
|
|
@@ -73,6 +73,15 @@ export class HermesSessionAdapter {
|
|
|
73
73
|
// Native exports and consistent snapshots require explicit selection.
|
|
74
74
|
}
|
|
75
75
|
async inspect(source) {
|
|
76
|
+
if (source.locatorClass === 'hermes-export-jsonl'
|
|
77
|
+
|| source.locatorClass === 'hermes-consistent-snapshot-jsonl') {
|
|
78
|
+
const records = this.stream(source);
|
|
79
|
+
let result = await records.next();
|
|
80
|
+
while (!result.done)
|
|
81
|
+
result = await records.next();
|
|
82
|
+
if (result.value)
|
|
83
|
+
return result.value;
|
|
84
|
+
}
|
|
76
85
|
const parsed = await this.readSource(source);
|
|
77
86
|
return {
|
|
78
87
|
sourceSchemaVersion: parsed.schemaVersion,
|
|
@@ -94,21 +103,23 @@ export class HermesSessionAdapter {
|
|
|
94
103
|
let outputIndex = 0;
|
|
95
104
|
let schema = null;
|
|
96
105
|
let sawRecord = false;
|
|
106
|
+
let complete = true;
|
|
97
107
|
for await (const line of input) {
|
|
98
108
|
sawRecord = true;
|
|
99
109
|
const parsed = ExportSchema.safeParse(line.value);
|
|
100
110
|
if (!parsed.success) {
|
|
101
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'Hermes export is malformed');
|
|
111
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Hermes export is malformed', schemaRejectionDetail(parsed.error));
|
|
102
112
|
}
|
|
113
|
+
complete &&= ended(parsed.data);
|
|
103
114
|
const currentSchema = declaredSchema(parsed.data);
|
|
104
115
|
if (schema && schema !== currentSchema) {
|
|
105
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Hermes export schemas');
|
|
116
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Hermes export schemas', schemaIssueDetail('schemaVersion'));
|
|
106
117
|
}
|
|
107
118
|
schema = currentSchema;
|
|
108
119
|
assertSupportedSchemaMajor(schema, 23);
|
|
109
120
|
if (source.locatorClass === 'hermes-consistent-snapshot-jsonl'
|
|
110
121
|
&& parsed.data.snapshotConsistency !== 'sqlite-backup') {
|
|
111
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'Hermes snapshot lacks sqlite3.backup() consistency evidence');
|
|
122
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'Hermes snapshot lacks sqlite3.backup() consistency evidence', schemaIssueDetail('snapshotConsistency'));
|
|
112
123
|
}
|
|
113
124
|
for (const record of normalizeSession(parsed.data, line, source.locatorClass)) {
|
|
114
125
|
if (outputIndex++ >= start)
|
|
@@ -116,16 +127,21 @@ export class HermesSessionAdapter {
|
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
129
|
if (!sawRecord)
|
|
119
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'Hermes source is empty');
|
|
130
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Hermes source is empty', schemaIssueDetail('<root>'));
|
|
120
131
|
if (input.incompleteTail && source.locatorClass !== 'hermes-export-jsonl') {
|
|
121
132
|
throw new SessionContractError('TRUNCATED_SOURCE', 'Hermes consistent snapshot is truncated');
|
|
122
133
|
}
|
|
123
|
-
return
|
|
134
|
+
return {
|
|
135
|
+
sourceSchemaVersion: HERMES_EXPORT_SCHEMA_VERSION,
|
|
136
|
+
consistency: complete ? 'complete' : 'provisional',
|
|
137
|
+
operationalState: 'available',
|
|
138
|
+
};
|
|
124
139
|
}
|
|
125
140
|
const parsed = await this.readSource(source);
|
|
126
141
|
const start = parseCursor(cursor?.value);
|
|
127
142
|
for (const record of parsed.records.slice(start))
|
|
128
143
|
yield record;
|
|
144
|
+
return undefined;
|
|
129
145
|
}
|
|
130
146
|
async readSource(source) {
|
|
131
147
|
if (source.locatorClass === 'hermes-state-db') {
|
|
@@ -143,37 +159,19 @@ export class HermesSessionAdapter {
|
|
|
143
159
|
value, sequence, byteOffset: sequence, byteLength: Buffer.byteLength(JSON.stringify(value)),
|
|
144
160
|
}));
|
|
145
161
|
}
|
|
146
|
-
else if (source.locatorClass === 'hermes-export-jsonl'
|
|
147
|
-
|| source.locatorClass === 'hermes-consistent-snapshot-jsonl') {
|
|
148
|
-
const result = await readBoundedJsonLines({
|
|
149
|
-
selectedPath: source.locator,
|
|
150
|
-
allowedRoots: source.authorizedScope.allowedRoots,
|
|
151
|
-
}, {
|
|
152
|
-
consistency: source.locatorClass === 'hermes-export-jsonl' ? 'provisional' : 'complete',
|
|
153
|
-
limits: this.limits,
|
|
154
|
-
});
|
|
155
|
-
input = result.records;
|
|
156
|
-
if (result.incompleteTail && source.locatorClass !== 'hermes-export-jsonl') {
|
|
157
|
-
throw new SessionContractError('TRUNCATED_SOURCE', 'Hermes consistent snapshot is truncated');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
162
|
else {
|
|
161
163
|
throw new SessionContractError('UNSUPPORTED_OPERATION', 'unsupported Hermes source class');
|
|
162
164
|
}
|
|
163
165
|
if (input.length === 0)
|
|
164
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'Hermes source is empty');
|
|
166
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Hermes source is empty', schemaIssueDetail('<root>'));
|
|
165
167
|
const parsed = input.map((line) => {
|
|
166
168
|
const result = ExportSchema.safeParse(line.value);
|
|
167
169
|
if (!result.success)
|
|
168
|
-
throw new SessionContractError('MALFORMED_SOURCE', 'Hermes export is malformed');
|
|
170
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Hermes export is malformed', schemaRejectionDetail(result.error));
|
|
169
171
|
return { line, value: result.data };
|
|
170
172
|
});
|
|
171
173
|
const schemaVersion = declaredVersion(parsed.map(({ value }) => value));
|
|
172
174
|
assertSupportedSchemaMajor(schemaVersion, 23);
|
|
173
|
-
if (source.locatorClass === 'hermes-consistent-snapshot-jsonl'
|
|
174
|
-
&& parsed.some(({ value }) => value.snapshotConsistency !== 'sqlite-backup')) {
|
|
175
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'Hermes snapshot lacks sqlite3.backup() consistency evidence');
|
|
176
|
-
}
|
|
177
175
|
return {
|
|
178
176
|
schemaVersion: HERMES_EXPORT_SCHEMA_VERSION,
|
|
179
177
|
consistency: parsed.every(({ value }) => ended(value)) ? 'complete' : 'provisional',
|
|
@@ -291,7 +289,7 @@ function ended(value) {
|
|
|
291
289
|
function declaredVersion(values) {
|
|
292
290
|
const versions = new Set(values.map(declaredSchema));
|
|
293
291
|
if (versions.size !== 1)
|
|
294
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Hermes export schemas');
|
|
292
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Hermes export schemas', schemaIssueDetail('schemaVersion'));
|
|
295
293
|
return [...versions][0];
|
|
296
294
|
}
|
|
297
295
|
function declaredSchema(value) {
|
|
@@ -330,7 +328,7 @@ function parseCursor(value) {
|
|
|
330
328
|
if (!value)
|
|
331
329
|
return 0;
|
|
332
330
|
if (!/^\d+$/.test(value))
|
|
333
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Hermes cursor');
|
|
331
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Hermes cursor', schemaIssueDetail('cursor'));
|
|
334
332
|
return Number(value);
|
|
335
333
|
}
|
|
336
334
|
const SESSION_KEYS = new Set([
|