@aiwg/cli 2026.10.3 → 2026.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +57 -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 +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -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 +98 -37
- 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 +82 -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 +51 -11
- package/dist/src/sessions/contracts.js +40 -2
- package/dist/src/sessions/coverage-preflight.js +1 -1
- 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 +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +147 -52
- package/dist/src/sessions/workspace-discovery.js +221 -56
- 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
|
@@ -27,7 +27,9 @@ export async function authorizeSourceFile(input) {
|
|
|
27
27
|
throw new SessionContractError('SOURCE_NOT_REGULAR_FILE', 'session source must be a regular file');
|
|
28
28
|
}
|
|
29
29
|
if (input.maxBytes !== undefined && stat.size > input.maxBytes) {
|
|
30
|
-
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'session source exceeds the authorized byte limit'
|
|
30
|
+
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'session source exceeds the authorized byte limit', {
|
|
31
|
+
limit: 'maxBytes', configured: input.maxBytes, observed: stat.size,
|
|
32
|
+
});
|
|
31
33
|
}
|
|
32
34
|
return {
|
|
33
35
|
canonicalPath,
|
|
@@ -54,21 +56,51 @@ async function safeRealpath(path, root = false) {
|
|
|
54
56
|
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', root ? 'an authorized source root is inaccessible' : 'selected session source is inaccessible');
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
// Ambiguous token components are bounded; whitespace and key blocks use disjoint scans.
|
|
57
60
|
const SECRET_PATTERNS = [
|
|
58
|
-
['
|
|
59
|
-
['
|
|
60
|
-
['
|
|
61
|
-
['
|
|
62
|
-
['credential', /\b(?:api[_-]?key|token|password|secret)\
|
|
63
|
-
['
|
|
61
|
+
['bearer-token', /\bBearer[ \t]+[A-Za-z0-9._~+/=-]{12,}/gi],
|
|
62
|
+
['authorization', /\bAuthorization[ \t]*:[ \t]*[^\r\n]+/gi],
|
|
63
|
+
['cookie', /\b(?:Cookie|Set-Cookie)[ \t]*:[ \t]*[^\r\n]+/gi],
|
|
64
|
+
['environment-assignment', /\b(?:AWS_SECRET_ACCESS_KEY|[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY))[ \t]*=[ \t]*(?:"[^"\r\n]+"|'[^'\r\n]+'|[^\s]+)/gi],
|
|
65
|
+
['credential', /\b(?:api[_-]?key|token|password|secret)[ \t]*[:=][ \t]*["']?[^\s"',;]{6,}/gi],
|
|
66
|
+
['credential', /^[ \t]*password[ \t]+[^\s]+[ \t]*$/gim],
|
|
67
|
+
// Space-separated values only when they look generated (20+ chars, letters and digits), so prose stays.
|
|
68
|
+
['credential', /\b(?:api[_-]?key|token|secret)[ \t]+(?=[A-Za-z0-9._~+/=-]*[0-9])(?=[A-Za-z0-9._~+/=-]*[A-Za-z])[A-Za-z0-9._~+/=-]{20,}/gi],
|
|
69
|
+
['provider-token', /\b(?:sk-(?:ant-|proj-)?|[sr]k_(?:live|test)_|gh[pousr]_|github_pat_|glpat-|xai-|npm_|hf_|xox[baprs]-|AKIA|AIza)[A-Za-z0-9._~+/=-]{8,}/g],
|
|
70
|
+
['provider-token', /https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+/g],
|
|
64
71
|
['connection-string', /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis):\/\/[^\s]+/gi],
|
|
65
72
|
['credential-url', /\bhttps?:\/\/[^/\s:@]+:[^/\s@]+@[^\s]+/gi],
|
|
66
|
-
['environment-assignment', /\b[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY)\s*=\s*(?:"[^"]+"|'[^']+'|[^\s]+)/g],
|
|
67
73
|
['fixture-credential', /\bredaction-canary-[A-Za-z0-9_-]+\b/gi],
|
|
68
|
-
['email', /\b[A-Z0-9._%+-]
|
|
74
|
+
['email', /\b[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\.[A-Z]{2,63}\b/gi],
|
|
69
75
|
];
|
|
76
|
+
function redactJwts(input, mark) {
|
|
77
|
+
// Consume even incomplete candidates. Retrying at each inner `eyJ` in an
|
|
78
|
+
// unterminated segment would repeatedly scan the remainder of a large record.
|
|
79
|
+
return input.replace(/\beyJ[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+){0,2}/g, (candidate) => {
|
|
80
|
+
const parts = candidate.split('.');
|
|
81
|
+
return parts.length === 3 && parts[0].length >= 8 && parts[1].length >= 2 && parts[2].length >= 8
|
|
82
|
+
? mark('provider-token') : candidate;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const PRIVATE_KEY_BEGIN = /-----BEGIN ((?:(?:RSA|EC|DSA|OPENSSH|ENCRYPTED) )?PRIVATE KEY|PGP PRIVATE KEY BLOCK)-----/g;
|
|
86
|
+
function redactPrivateKeys(input, mark) {
|
|
87
|
+
const parts = [];
|
|
88
|
+
let cursor = 0;
|
|
89
|
+
PRIVATE_KEY_BEGIN.lastIndex = 0;
|
|
90
|
+
let match;
|
|
91
|
+
while ((match = PRIVATE_KEY_BEGIN.exec(input))) {
|
|
92
|
+
parts.push(input.slice(cursor, match.index), mark('private-key'));
|
|
93
|
+
const endMarker = `-----END ${match[1]}-----`;
|
|
94
|
+
const end = input.indexOf(endMarker, PRIVATE_KEY_BEGIN.lastIndex);
|
|
95
|
+
// An unfinished key block is sensitive through the end of the record.
|
|
96
|
+
cursor = end < 0 ? input.length : end + endMarker.length;
|
|
97
|
+
PRIVATE_KEY_BEGIN.lastIndex = cursor;
|
|
98
|
+
}
|
|
99
|
+
parts.push(input.slice(cursor));
|
|
100
|
+
return parts.join('');
|
|
101
|
+
}
|
|
70
102
|
const SENSITIVE_KEY = /(?:authorization|auth[_-]?header|cookie|credential|password|passwd|secret|token|api[_-]?key|private[_-]?key|connection[_-]?string)/i;
|
|
71
|
-
const CONTENT_KEY = /(?:^|[_-])(?:text|content|prompt|command|arguments?|args|result|output|body|request|response|source|code|query)(?:$|[_-])/i;
|
|
103
|
+
const CONTENT_KEY = /(?:^|[_-])(?:data|text|content|prompt|command|arguments?|args|result|output|body|request|response|source|code|query)(?:$|[_-])/i;
|
|
72
104
|
const PATH_KEY = /(?:^|[_-])(?:path|cwd|directory|filename|file)(?:$|[_-])/i;
|
|
73
105
|
const SAFE_STRING_KEY = /(?:^|[_-])(?:id|kind|type|role|status|state|lifecycle|reason|name|product|provider|version|schema|model|format|class|mode|event|operation|tool|method|language|scope|consistency|disposition|phase|visibility|protocol|category|classification|decision|capability|permission|transition|hash|digest)(?:$|[_-])/i;
|
|
74
106
|
const CONTROL_CHARACTERS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g;
|
|
@@ -103,23 +135,28 @@ export function sanitizeNativeExtensions(input, overrides = {}) {
|
|
|
103
135
|
return marker('depth-limit');
|
|
104
136
|
if (value === null || typeof value === 'boolean' || typeof value === 'number')
|
|
105
137
|
return value;
|
|
138
|
+
if (typeof value === 'string' && /^\[REDACTED:[a-z-]+\]$/.test(value))
|
|
139
|
+
return value;
|
|
140
|
+
if (SENSITIVE_KEY.test(classifiedKey))
|
|
141
|
+
return marker('sensitive-field');
|
|
142
|
+
if (CONTENT_KEY.test(classifiedKey))
|
|
143
|
+
return marker('content');
|
|
144
|
+
if (PATH_KEY.test(classifiedKey))
|
|
145
|
+
return marker('path');
|
|
106
146
|
if (typeof value === 'bigint')
|
|
107
147
|
return String(value);
|
|
108
148
|
if (typeof value !== 'string' && typeof value !== 'object') {
|
|
109
149
|
return marker('unsupported-value');
|
|
110
150
|
}
|
|
111
151
|
if (typeof value === 'string') {
|
|
112
|
-
if (SENSITIVE_KEY.test(classifiedKey))
|
|
113
|
-
return marker('sensitive-field');
|
|
114
|
-
if (CONTENT_KEY.test(classifiedKey))
|
|
115
|
-
return marker('content');
|
|
116
|
-
if (PATH_KEY.test(classifiedKey))
|
|
117
|
-
return marker('path');
|
|
118
152
|
if (Buffer.byteLength(value) > limits.maxStringBytes)
|
|
119
153
|
return marker('string-limit');
|
|
120
154
|
const redacted = redactSessionText(value);
|
|
121
155
|
for (const classification of redacted.classes)
|
|
122
156
|
classes.add(classification);
|
|
157
|
+
for (const [classification, count] of Object.entries(redacted.decisions)) {
|
|
158
|
+
decisions[classification] = (decisions[classification] ?? 0) + count;
|
|
159
|
+
}
|
|
123
160
|
if (redacted.classes.length) {
|
|
124
161
|
decisions['classified-value'] = (decisions['classified-value'] ?? 0) + 1;
|
|
125
162
|
return redacted.text.replace(CONTROL_CHARACTERS, ' ');
|
|
@@ -142,8 +179,15 @@ export function sanitizeNativeExtensions(input, overrides = {}) {
|
|
|
142
179
|
const output = {};
|
|
143
180
|
const entries = Object.entries(value);
|
|
144
181
|
for (const [childKey, childValue] of entries.slice(0, limits.maxObjectKeys)) {
|
|
145
|
-
|
|
146
|
-
|
|
182
|
+
const safeKey = redactSessionText(childKey);
|
|
183
|
+
for (const classification of safeKey.classes)
|
|
184
|
+
classes.add(classification);
|
|
185
|
+
for (const [classification, count] of Object.entries(safeKey.decisions)) {
|
|
186
|
+
decisions[classification] = (decisions[classification] ?? 0) + count;
|
|
187
|
+
}
|
|
188
|
+
Object.defineProperty(output, safeKey.text.replace(/\r?\n/g, ' ').replace(CONTROL_CHARACTERS, ''), {
|
|
189
|
+
value: visit(childValue, childKey, depth + 1), enumerable: true, configurable: true, writable: true,
|
|
190
|
+
});
|
|
147
191
|
}
|
|
148
192
|
if (entries.length > limits.maxObjectKeys)
|
|
149
193
|
output.__truncated__ = marker('object-key-limit');
|
|
@@ -165,15 +209,79 @@ export function sanitizeNativeExtensions(input, overrides = {}) {
|
|
|
165
209
|
};
|
|
166
210
|
}
|
|
167
211
|
export function redactSessionText(input) {
|
|
168
|
-
const
|
|
169
|
-
|
|
212
|
+
const decisions = {};
|
|
213
|
+
const mark = (classification) => {
|
|
214
|
+
decisions[classification] = (decisions[classification] ?? 0) + 1;
|
|
215
|
+
return `[REDACTED:${classification}]`;
|
|
216
|
+
};
|
|
217
|
+
let text = redactJwts(redactPrivateKeys(input, mark), mark);
|
|
170
218
|
for (const [classification, pattern] of SECRET_PATTERNS) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
219
|
+
// Every email match requires an at-sign. Avoid costly bounded local-part
|
|
220
|
+
// backtracking on large records containing only email-shaped near matches.
|
|
221
|
+
if (classification === 'email' && !text.includes('@'))
|
|
222
|
+
continue;
|
|
223
|
+
text = text.replace(pattern, () => mark(classification));
|
|
175
224
|
}
|
|
176
|
-
|
|
225
|
+
const classes = Object.keys(decisions).sort();
|
|
226
|
+
return { text, sensitivity: classes.length ? 'sensitive' : 'none', classes, decisions };
|
|
227
|
+
}
|
|
228
|
+
/** Redact every string, including object keys, without changing canonical field shapes. */
|
|
229
|
+
export function redactSessionValue(input) {
|
|
230
|
+
const decisions = {};
|
|
231
|
+
const active = new WeakSet();
|
|
232
|
+
const text = (value) => {
|
|
233
|
+
const result = redactSessionText(value);
|
|
234
|
+
for (const [key, count] of Object.entries(result.decisions)) {
|
|
235
|
+
decisions[key] = (decisions[key] ?? 0) + count;
|
|
236
|
+
}
|
|
237
|
+
return result.text;
|
|
238
|
+
};
|
|
239
|
+
const visit = (value, depth) => {
|
|
240
|
+
if (typeof value === 'string')
|
|
241
|
+
return text(value);
|
|
242
|
+
if (!value || typeof value !== 'object')
|
|
243
|
+
return value;
|
|
244
|
+
if (depth > 64 || active.has(value))
|
|
245
|
+
return '[REDACTED:structure-limit]';
|
|
246
|
+
active.add(value);
|
|
247
|
+
try {
|
|
248
|
+
return Array.isArray(value)
|
|
249
|
+
? value.map((item) => visit(item, depth + 1))
|
|
250
|
+
: Object.fromEntries(Object.entries(value).map(([key, item]) => [text(key), visit(item, depth + 1)]));
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
active.delete(value);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const value = visit(input, 0);
|
|
257
|
+
return { value, classes: Object.keys(decisions).sort(), decisions };
|
|
258
|
+
}
|
|
259
|
+
/** Shared import boundary for canonical text, metadata and bounded native attributes. */
|
|
260
|
+
export function sanitizeSessionEvent(input) {
|
|
261
|
+
const native = sanitizeNativeExtensions(input.extensions);
|
|
262
|
+
const result = redactSessionValue({ ...input, extensions: native.value });
|
|
263
|
+
const event = result.value;
|
|
264
|
+
const classes = [...new Set([...event.sensitivity.classes, ...native.classes, ...result.classes])].sort();
|
|
265
|
+
event.sensitivity = {
|
|
266
|
+
classification: classes.length ? 'sensitive' : 'none', classes,
|
|
267
|
+
};
|
|
268
|
+
// Counts contain classifications only, never matched input or excerpts.
|
|
269
|
+
const prior = event.extensions['native.redaction'];
|
|
270
|
+
const counts = {};
|
|
271
|
+
for (const item of Array.isArray(prior?.decisions) ? prior.decisions : []) {
|
|
272
|
+
if (item && typeof item.classification === 'string' && Number.isSafeInteger(item.count) && item.count >= 0) {
|
|
273
|
+
counts[item.classification] = item.count;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
for (const decisions of [native.decisions, result.decisions]) {
|
|
277
|
+
for (const [key, count] of Object.entries(decisions)) {
|
|
278
|
+
counts[key] = (counts[key] ?? 0) + count;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
event.extensions['native.redaction'] = {
|
|
282
|
+
decisions: Object.entries(counts).map(([classification, count]) => ({ classification, count })),
|
|
283
|
+
};
|
|
284
|
+
return event;
|
|
177
285
|
}
|
|
178
286
|
export function contentFreeAuditEvent(event) {
|
|
179
287
|
return Object.freeze({ ...event, counts: { ...event.counts } });
|
|
@@ -1,15 +1,82 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { createReadStream } from 'node:fs';
|
|
3
2
|
import { open, readFile } from 'node:fs/promises';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { assertSessionResourceLimit, schemaIssueDetail, SessionContractError } from './contracts.js';
|
|
4
|
+
import { assertImportByteLimit, DEFAULT_IMPORT_BYTE_LIMITS } from './import-limits.js';
|
|
6
5
|
import { authorizeSourceFile } from './policy.js';
|
|
7
6
|
export const DEFAULT_READER_LIMITS = Object.freeze({
|
|
8
7
|
maxRecords: 1_000_000,
|
|
9
|
-
|
|
10
|
-
maxTotalBytes: 1024 * 1024 * 1024,
|
|
8
|
+
...DEFAULT_IMPORT_BYTE_LIMITS,
|
|
11
9
|
maxNestingDepth: 64,
|
|
12
10
|
});
|
|
11
|
+
const JSONL_READ_CHUNK_BYTES = 1024 * 1024;
|
|
12
|
+
export async function* streamJsonLines(path, options = {}) {
|
|
13
|
+
const startOffset = options.start ?? 0;
|
|
14
|
+
if (!Number.isSafeInteger(startOffset) || startOffset < 0)
|
|
15
|
+
throw new RangeError('start must be a nonnegative safe integer');
|
|
16
|
+
if (options.end !== undefined && options.end !== Infinity
|
|
17
|
+
&& (!Number.isSafeInteger(options.end) || options.end < 0))
|
|
18
|
+
throw new RangeError('end must be a nonnegative safe integer or Infinity');
|
|
19
|
+
if (options.end !== undefined && options.end < startOffset)
|
|
20
|
+
throw new RangeError('end must not precede start');
|
|
21
|
+
// Reuse raw storage across the full source instead of allocating a new MiB
|
|
22
|
+
// buffer per read. Only incomplete line fragments outlive the current read.
|
|
23
|
+
const storage = Buffer.allocUnsafe(JSONL_READ_CHUNK_BYTES);
|
|
24
|
+
const input = await open(path, 'r');
|
|
25
|
+
let position = startOffset;
|
|
26
|
+
let parts = [];
|
|
27
|
+
let length = 0;
|
|
28
|
+
let total = 0;
|
|
29
|
+
try {
|
|
30
|
+
while (options.end === undefined || position <= options.end) {
|
|
31
|
+
const wanted = options.end === undefined ? storage.length
|
|
32
|
+
: Math.min(storage.length, options.end - position + 1);
|
|
33
|
+
const { bytesRead } = await input.read(storage, 0, wanted, position);
|
|
34
|
+
if (bytesRead === 0)
|
|
35
|
+
break;
|
|
36
|
+
position += bytesRead;
|
|
37
|
+
const buffer = storage.subarray(0, bytesRead);
|
|
38
|
+
let start = 0;
|
|
39
|
+
while (start < buffer.length) {
|
|
40
|
+
const newline = buffer.indexOf(10, start);
|
|
41
|
+
const end = newline === -1 ? buffer.length : newline + 1;
|
|
42
|
+
const segment = buffer.subarray(start, end);
|
|
43
|
+
if (options.limits) {
|
|
44
|
+
const recordBytes = length + segment.length;
|
|
45
|
+
const totalBytes = (options.start ?? 0) + total + segment.length;
|
|
46
|
+
// For an incomplete segment, report the first byte known to exceed
|
|
47
|
+
// the limit instead of making diagnostics depend on read-buffer size.
|
|
48
|
+
assertImportByteLimit('maxRecordBytes', options.limits, newline === -1 && recordBytes > options.limits.maxRecordBytes
|
|
49
|
+
? options.limits.maxRecordBytes + 1 : recordBytes);
|
|
50
|
+
assertImportByteLimit('maxTotalBytes', options.limits, newline === -1 && options.limits.maxTotalBytes !== 0 && totalBytes > options.limits.maxTotalBytes
|
|
51
|
+
? options.limits.maxTotalBytes + 1 : totalBytes);
|
|
52
|
+
}
|
|
53
|
+
// Bound the raw bytes before copying or decoding a record. Only LF
|
|
54
|
+
// separates records; CRLF and Unicode separators remain JSON input.
|
|
55
|
+
parts.push(segment);
|
|
56
|
+
length += segment.length;
|
|
57
|
+
total += segment.length;
|
|
58
|
+
start = end;
|
|
59
|
+
if (newline !== -1) {
|
|
60
|
+
const line = (parts.length === 1 ? parts[0] : Buffer.concat(parts, length)).toString('utf8');
|
|
61
|
+
const byteLength = length;
|
|
62
|
+
parts = [];
|
|
63
|
+
length = 0;
|
|
64
|
+
yield { line, byteLength };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Earlier fragments already own their bytes. The last fragment is the
|
|
68
|
+
// only view into storage, which the next read will overwrite.
|
|
69
|
+
if (parts.length > 0)
|
|
70
|
+
parts[parts.length - 1] = Buffer.from(parts[parts.length - 1]);
|
|
71
|
+
}
|
|
72
|
+
if (length > 0) {
|
|
73
|
+
yield { line: (parts.length === 1 ? parts[0] : Buffer.concat(parts, length)).toString('utf8'), byteLength: length };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await input.close();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
13
80
|
export async function readBoundedJsonLines(authorization, options) {
|
|
14
81
|
const stream = await streamBoundedJsonLines(authorization, options);
|
|
15
82
|
const records = [];
|
|
@@ -28,50 +95,36 @@ export async function streamBoundedJsonLines(authorization, options) {
|
|
|
28
95
|
const limits = { ...DEFAULT_READER_LIMITS, ...options.limits };
|
|
29
96
|
const start = parseCursor(options.cursor);
|
|
30
97
|
if (start > allowed.size)
|
|
31
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'reader cursor is beyond source size');
|
|
98
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'reader cursor is beyond source size', schemaIssueDetail('cursor'));
|
|
32
99
|
let offset = start;
|
|
33
100
|
let total = 0;
|
|
34
101
|
let count = 0;
|
|
35
102
|
let incompleteTail = false;
|
|
36
103
|
const iterate = async function* () {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for await (const line of lines) {
|
|
41
|
-
const bytes = Buffer.byteLength(line) + 1;
|
|
42
|
-
if (bytes > limits.maxRecordBytes || total + bytes > limits.maxTotalBytes
|
|
43
|
-
|| count + 1 > limits.maxRecords) {
|
|
44
|
-
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'bounded session reader limit exceeded');
|
|
45
|
-
}
|
|
46
|
-
if (line.trim() === '') {
|
|
47
|
-
offset += bytes;
|
|
48
|
-
total += bytes;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
let value;
|
|
52
|
-
try {
|
|
53
|
-
value = JSON.parse(line);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
if (options.consistency === 'provisional') {
|
|
57
|
-
incompleteTail = true;
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'malformed JSONL record in consistent source');
|
|
61
|
-
}
|
|
62
|
-
if (jsonDepth(value) > limits.maxNestingDepth) {
|
|
63
|
-
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'JSON nesting depth exceeds reader limit');
|
|
64
|
-
}
|
|
65
|
-
const record = { value, sequence: count, byteOffset: offset, byteLength: bytes };
|
|
66
|
-
count += 1;
|
|
104
|
+
for await (const { line, byteLength: bytes } of streamJsonLines(allowed.canonicalPath, { start, limits })) {
|
|
105
|
+
assertSessionResourceLimit('maxRecords', limits.maxRecords, count + 1);
|
|
106
|
+
if (line.trim() === '') {
|
|
67
107
|
offset += bytes;
|
|
68
108
|
total += bytes;
|
|
69
|
-
|
|
109
|
+
continue;
|
|
70
110
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
111
|
+
let value;
|
|
112
|
+
try {
|
|
113
|
+
value = JSON.parse(line);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
if (options.consistency === 'provisional') {
|
|
117
|
+
incompleteTail = true;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'malformed JSONL record in consistent source', schemaIssueDetail('<root>', 'invalid_json'));
|
|
121
|
+
}
|
|
122
|
+
assertSessionResourceLimit('maxNestingDepth', limits.maxNestingDepth, jsonDepth(value));
|
|
123
|
+
const record = { value, sequence: count, byteOffset: offset, byteLength: bytes };
|
|
124
|
+
count += 1;
|
|
125
|
+
offset += bytes;
|
|
126
|
+
total += bytes;
|
|
127
|
+
yield record;
|
|
75
128
|
}
|
|
76
129
|
};
|
|
77
130
|
return {
|
|
@@ -83,30 +136,30 @@ export async function streamBoundedJsonLines(authorization, options) {
|
|
|
83
136
|
[Symbol.asyncIterator]: iterate,
|
|
84
137
|
};
|
|
85
138
|
}
|
|
139
|
+
// Single-document JSON and text exports cannot be incrementally normalized.
|
|
140
|
+
export const MAX_BUFFERED_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
86
141
|
export async function readBoundedJson(authorization, limitsInput) {
|
|
87
142
|
const allowed = await authorizeSourceFile(authorization);
|
|
88
143
|
const limits = { ...DEFAULT_READER_LIMITS, ...limitsInput };
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
144
|
+
assertImportByteLimit('maxTotalBytes', limits, allowed.size);
|
|
145
|
+
assertImportByteLimit('maxRecordBytes', limits, allowed.size);
|
|
146
|
+
assertSessionResourceLimit('maxBufferedSourceBytes', MAX_BUFFERED_SOURCE_BYTES, allowed.size);
|
|
92
147
|
let value;
|
|
93
148
|
try {
|
|
94
149
|
value = JSON.parse(await readFile(allowed.canonicalPath, 'utf8'));
|
|
95
150
|
}
|
|
96
151
|
catch {
|
|
97
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'malformed JSON source');
|
|
98
|
-
}
|
|
99
|
-
if (jsonDepth(value) > limits.maxNestingDepth) {
|
|
100
|
-
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'JSON nesting depth exceeds reader limit');
|
|
152
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'malformed JSON source', schemaIssueDetail('<root>', 'invalid_json'));
|
|
101
153
|
}
|
|
154
|
+
assertSessionResourceLimit('maxNestingDepth', limits.maxNestingDepth, jsonDepth(value));
|
|
102
155
|
return { value, bytesRead: allowed.size };
|
|
103
156
|
}
|
|
104
157
|
export async function readBoundedText(authorization, limitsInput) {
|
|
105
158
|
const allowed = await authorizeSourceFile(authorization);
|
|
106
159
|
const limits = { ...DEFAULT_READER_LIMITS, ...limitsInput };
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
160
|
+
assertImportByteLimit('maxTotalBytes', limits, allowed.size);
|
|
161
|
+
assertImportByteLimit('maxRecordBytes', limits, allowed.size);
|
|
162
|
+
assertSessionResourceLimit('maxBufferedSourceBytes', MAX_BUFFERED_SOURCE_BYTES, allowed.size);
|
|
110
163
|
return {
|
|
111
164
|
value: await readFile(allowed.canonicalPath, 'utf8'),
|
|
112
165
|
bytesRead: allowed.size,
|
|
@@ -136,7 +189,7 @@ export async function fingerprintSourcePrefix(authorization, length, skipPrefixB
|
|
|
136
189
|
const allowed = await authorizeSourceFile(authorization);
|
|
137
190
|
if (!Number.isSafeInteger(length) || length < 0 || length > allowed.size
|
|
138
191
|
|| !Number.isSafeInteger(skipPrefixBytes) || skipPrefixBytes < 0 || skipPrefixBytes > allowed.size) {
|
|
139
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'checkpoint source position is beyond the current source size');
|
|
192
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'checkpoint source position is beyond the current source size', schemaIssueDetail('cursor'));
|
|
140
193
|
}
|
|
141
194
|
const handle = await open(allowed.canonicalPath, 'r');
|
|
142
195
|
const hash = createHash('sha256');
|
|
@@ -165,7 +218,7 @@ function parseCursor(cursor) {
|
|
|
165
218
|
if (cursor === undefined || cursor === '')
|
|
166
219
|
return 0;
|
|
167
220
|
if (!/^\d+$/.test(cursor))
|
|
168
|
-
throw new SessionContractError('SCHEMA_DRIFT', 'invalid reader cursor');
|
|
221
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid reader cursor', schemaIssueDetail('cursor'));
|
|
169
222
|
return Number(cursor);
|
|
170
223
|
}
|
|
171
224
|
function jsonDepth(value, depth = 0) {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { requireFeaturePackage } from '../features/runtime.js';
|
|
4
|
+
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
5
|
+
import { createSessionAdapterRegistry } from './adapter-registry.js';
|
|
6
|
+
import { resolveImportExclusions } from './import-exclusions.js';
|
|
7
|
+
import { resolveImportLimits } from './import-limits.js';
|
|
8
|
+
import { acquireImportLease } from './import-lease.js';
|
|
9
|
+
import { SessionRepository } from './repository.js';
|
|
10
|
+
import { importDiscoveryManifest } from './batch-import.js';
|
|
11
|
+
import { defaultDiscoveryManifestPath, discoverWorkspaceHistories, readDiscoveryManifest, writeDiscoveryManifest, } from './workspace-discovery.js';
|
|
12
|
+
export function sessionRefreshAvailable() {
|
|
13
|
+
try {
|
|
14
|
+
const Database = requireFeaturePackage('better-sqlite3');
|
|
15
|
+
const probe = new Database(':memory:');
|
|
16
|
+
probe.close();
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Refresh an existing catalog without inferring or granting provider access. */
|
|
24
|
+
export async function refreshSessionCatalog(workspace, full, waitMs, signal, workspaceAliases = [], flags = {}) {
|
|
25
|
+
const canonicalWorkspace = realpathSync(workspace);
|
|
26
|
+
const config = await readAiwgConfig(canonicalWorkspace);
|
|
27
|
+
const limits = resolveImportLimits(config?.sessions?.import, flags);
|
|
28
|
+
const exclude = resolveImportExclusions(config?.sessions?.import, flags.exclude);
|
|
29
|
+
const database = path.join(workspace, '.aiwg/sessions/catalog.sqlite');
|
|
30
|
+
const manifests = [];
|
|
31
|
+
if (!full) {
|
|
32
|
+
const paths = new Set([
|
|
33
|
+
defaultDiscoveryManifestPath(canonicalWorkspace),
|
|
34
|
+
...(config?.sessions?.discovery_manifests ?? []).map(file => path.resolve(canonicalWorkspace, file)),
|
|
35
|
+
]);
|
|
36
|
+
let found = false;
|
|
37
|
+
for (const manifestPath of paths) {
|
|
38
|
+
if (!existsSync(manifestPath))
|
|
39
|
+
continue;
|
|
40
|
+
const previous = await readDiscoveryManifest(manifestPath);
|
|
41
|
+
if (previous.workspaceId !== canonicalWorkspace)
|
|
42
|
+
throw new Error('discovery manifest workspace mismatch');
|
|
43
|
+
found = true;
|
|
44
|
+
const authorizedRoots = previous.authorizedRoots ?? {};
|
|
45
|
+
if (!previous.authorizedRoots) {
|
|
46
|
+
for (const source of previous.sources) {
|
|
47
|
+
const roots = authorizedRoots[source.provider] ??= [];
|
|
48
|
+
if (!roots.includes(source.authorizedRoot))
|
|
49
|
+
roots.push(source.authorizedRoot);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const providers = Object.keys(authorizedRoots)
|
|
53
|
+
.filter(provider => authorizedRoots[provider]?.length);
|
|
54
|
+
if (!providers.length)
|
|
55
|
+
continue;
|
|
56
|
+
const manifest = await discoverWorkspaceHistories({
|
|
57
|
+
workspace: canonicalWorkspace,
|
|
58
|
+
workspaceAliases: [workspace, ...workspaceAliases],
|
|
59
|
+
expectedProviders: providers,
|
|
60
|
+
authorizedRoots,
|
|
61
|
+
registry: createSessionAdapterRegistry(limits),
|
|
62
|
+
exclude,
|
|
63
|
+
knownSources: previous.sources,
|
|
64
|
+
});
|
|
65
|
+
manifests.push({ path: manifestPath, manifest });
|
|
66
|
+
}
|
|
67
|
+
if (!found)
|
|
68
|
+
return 'no authorized discovery manifest';
|
|
69
|
+
if (!manifests.length)
|
|
70
|
+
return 'no authorized provider roots';
|
|
71
|
+
}
|
|
72
|
+
const lease = await acquireImportLease(database, `index-refresh-${process.pid}`, { waitMs });
|
|
73
|
+
try {
|
|
74
|
+
const repository = new SessionRepository(database);
|
|
75
|
+
try {
|
|
76
|
+
if (full)
|
|
77
|
+
repository.reindex();
|
|
78
|
+
else {
|
|
79
|
+
const receipts = [];
|
|
80
|
+
for (const entry of manifests) {
|
|
81
|
+
const prior = repository.getBatchImportRunForManifest(entry.manifest.manifestId, canonicalWorkspace);
|
|
82
|
+
if (prior?.status === 'complete')
|
|
83
|
+
continue;
|
|
84
|
+
// Persist the exact discovered batch before its confirmed import.
|
|
85
|
+
await writeDiscoveryManifest(entry.path, entry.manifest);
|
|
86
|
+
const receipt = await importDiscoveryManifest({ manifest: entry.manifest, repository, signal, limits, exclude });
|
|
87
|
+
if (receipt.totals.pending) {
|
|
88
|
+
throw new Error(`session import incomplete: ${receipt.totals.rejected} rejected, ${receipt.totals.pending} pending`);
|
|
89
|
+
}
|
|
90
|
+
receipts.push(receipt);
|
|
91
|
+
}
|
|
92
|
+
if (!receipts.length)
|
|
93
|
+
return 'unchanged';
|
|
94
|
+
return receipts.length === 1 ? receipts[0] : receipts;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
repository.close();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
await lease.release();
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=refresh.js.map
|