@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,17 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { access, mkdir, readFile, realpath, rename, stat, writeFile, } from 'node:fs/promises';
|
|
1
|
+
import { access, mkdir, readFile, readdir, realpath, rename, stat, writeFile, } from 'node:fs/promises';
|
|
3
2
|
import { homedir, userInfo } from 'node:os';
|
|
4
3
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
5
|
-
import { createInterface } from 'node:readline';
|
|
6
4
|
import { inspectMuseNativeWorkspace } from './adapters/muse.js';
|
|
7
5
|
import { readOmpSessionHeader } from './adapters/omp.js';
|
|
8
6
|
import { grokbotConversationIdFromLocator } from './adapters/grokbot-locator.js';
|
|
9
7
|
import { resolveOmpPaths } from '../providers/omp-paths.mjs';
|
|
10
|
-
import {
|
|
8
|
+
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
9
|
+
import { SessionCoveragePreflightSchema, SessionContractError, sha256, } from './contracts.js';
|
|
11
10
|
import { redactSourceLocator } from './discovery.js';
|
|
12
11
|
import { createSessionAdapterRegistry } from './adapter-registry.js';
|
|
13
|
-
import { coveragePreflight,
|
|
14
|
-
import { fingerprintSourceFile } from './readers.js';
|
|
12
|
+
import { coveragePreflight, providerScopeWarnings, resolveSessionProviderScope } from './coverage-preflight.js';
|
|
13
|
+
import { fingerprintSourceFile, streamJsonLines } from './readers.js';
|
|
14
|
+
import { matchSessionWorkspace } from './workspace-matching.js';
|
|
15
|
+
import { matchImportExclusion, nativeSessionIdFromLocator, resolveImportExclusions } from './import-exclusions.js';
|
|
15
16
|
export const DISCOVERY_MANIFEST_VERSION = '1.0.0';
|
|
16
17
|
const MANUAL_EXPORT_PROVIDERS = new Set([
|
|
17
18
|
'copilot', 'hermes', 'opencode', 'openclaw', 'openhuman', 'grok-build',
|
|
@@ -19,13 +20,36 @@ const MANUAL_EXPORT_PROVIDERS = new Set([
|
|
|
19
20
|
]);
|
|
20
21
|
export async function discoverWorkspaceHistories(options) {
|
|
21
22
|
const registry = options.registry ?? createSessionAdapterRegistry();
|
|
22
|
-
const
|
|
23
|
+
const exclusions = resolveImportExclusions(undefined, options.exclude);
|
|
23
24
|
const supportedProviders = new Set(registry.list().map((adapter) => adapter.provider));
|
|
24
25
|
const workspacePath = await canonicalPath(options.workspace);
|
|
26
|
+
const config = await readAiwgConfig(workspacePath);
|
|
27
|
+
const configuredProviders = config?.providers ?? [];
|
|
28
|
+
const explicitlyRootedProviders = [
|
|
29
|
+
...(options.codexRoot || options.codexRoots?.length || config?.sessions?.codex_roots?.length ? ['codex'] : []),
|
|
30
|
+
...(options.ompRoot ? ['omp'] : []),
|
|
31
|
+
...(options.museRoot ? ['muse'] : []),
|
|
32
|
+
...(options.dshRoot ? ['deepseek-harness'] : []),
|
|
33
|
+
...(options.grokbotRoot ? ['grokbot'] : []),
|
|
34
|
+
];
|
|
35
|
+
const savedRoots = options.authorizedRoots ?? options.refreshFrom?.authorizedRoots;
|
|
36
|
+
const authorizedProviders = options.refreshFrom
|
|
37
|
+
? Object.keys(savedRoots ?? {}).filter(provider => savedRoots?.[provider]?.length)
|
|
38
|
+
: configuredProviders.length ? explicitlyRootedProviders : undefined;
|
|
39
|
+
const providerScope = resolveSessionProviderScope(configuredProviders, options.expectedProviders, authorizedProviders);
|
|
40
|
+
if (options.refreshFrom) {
|
|
41
|
+
providerScope.scopeWarnings = providerScopeWarnings(configuredProviders, options.refreshFrom.preflight?.expectedProviders ?? []);
|
|
42
|
+
}
|
|
43
|
+
const { expectedProviders } = providerScope;
|
|
25
44
|
const workspaceId = workspacePath;
|
|
45
|
+
const workspacePaths = [...new Set([workspacePath, resolve(options.workspace),
|
|
46
|
+
...(options.workspaceAliases ?? []).map((alias) => resolve(alias))])];
|
|
47
|
+
for (const alias of workspacePaths) {
|
|
48
|
+
if (await canonicalPath(alias) !== workspacePath) {
|
|
49
|
+
throw new SessionContractError('INVALID_ARGUMENT', 'workspace alias resolves to a different workspace');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
26
52
|
const providerHomes = providerHomeCandidates(options.providerHome, options.operatorHome);
|
|
27
|
-
const keyWithLeadingDash = workspaceKey(workspacePath, true);
|
|
28
|
-
const keyWithoutLeadingDash = workspaceKey(workspacePath, false);
|
|
29
53
|
const discoverable = [
|
|
30
54
|
{ provider: 'deepseek-harness', roots: options.dshRoot
|
|
31
55
|
? [resolve(options.dshRoot)] : options.providerHome
|
|
@@ -46,26 +70,30 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
46
70
|
? [resolve(options.grokbotRoot)] : [] },
|
|
47
71
|
{
|
|
48
72
|
provider: 'claude',
|
|
49
|
-
|
|
73
|
+
// Same scope rule as codex: configured providers keep their root grant
|
|
74
|
+
// even when a narrow discovery does not scan them (#3402).
|
|
75
|
+
roots: savedRoots || (!expectedProviders.includes('claude') && !providerScope.configuredProviders?.includes('claude'))
|
|
76
|
+
? [] : await claudeDiscoveryRoots(providerHomes, workspacePaths, workspacePath, registry, exclusions),
|
|
50
77
|
},
|
|
51
78
|
{
|
|
52
79
|
provider: 'codex',
|
|
53
|
-
roots:
|
|
54
|
-
|
|
55
|
-
: options
|
|
56
|
-
|
|
57
|
-
|
|
80
|
+
roots: savedRoots || (!expectedProviders.includes('codex') && !providerScope.configuredProviders?.includes('codex')
|
|
81
|
+
&& !explicitlyRootedProviders.includes('codex'))
|
|
82
|
+
? [] : await codexDiscoveryRoots({ ...options, codexRoots: [
|
|
83
|
+
...(config?.sessions?.codex_roots ?? []).map(root => resolve(workspacePath, root)),
|
|
84
|
+
...(options.codexRoots ?? []),
|
|
85
|
+
] }),
|
|
58
86
|
},
|
|
59
87
|
{
|
|
60
88
|
provider: 'cursor',
|
|
61
|
-
roots: providerHomes.
|
|
89
|
+
roots: providerHomes.flatMap((providerHome) => workspacePaths.map((workspace) => join(providerHome, '.cursor', 'projects', workspaceKey(workspace, false), 'agent-transcripts'))),
|
|
62
90
|
},
|
|
63
91
|
{
|
|
64
92
|
provider: 'factory',
|
|
65
|
-
roots: providerHomes.flatMap((providerHome) => [
|
|
66
|
-
join(providerHome, '.factory', 'projects',
|
|
67
|
-
join(providerHome, '.factory', 'sessions',
|
|
68
|
-
]),
|
|
93
|
+
roots: providerHomes.flatMap((providerHome) => workspacePaths.flatMap((workspace) => [
|
|
94
|
+
join(providerHome, '.factory', 'projects', workspaceKey(workspace, true)),
|
|
95
|
+
join(providerHome, '.factory', 'sessions', workspaceKey(workspace, true)),
|
|
96
|
+
])),
|
|
69
97
|
},
|
|
70
98
|
{
|
|
71
99
|
// Pi stores every working directory's sessions in its own
|
|
@@ -79,22 +107,52 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
79
107
|
provider: 'pi',
|
|
80
108
|
roots: (process.env.PI_CODING_AGENT_SESSION_DIR
|
|
81
109
|
? [resolve(process.env.PI_CODING_AGENT_SESSION_DIR)]
|
|
82
|
-
: providerHomes.map((providerHome) => join(providerHome, '.pi', 'agent', 'sessions'))).
|
|
110
|
+
: providerHomes.map((providerHome) => join(providerHome, '.pi', 'agent', 'sessions'))).flatMap((sessionsRoot) => workspacePaths.map((workspace) => join(sessionsRoot, `--${workspaceKey(workspace, false)}--`))),
|
|
83
111
|
},
|
|
84
112
|
];
|
|
113
|
+
const authorizedRoots = { ...savedRoots };
|
|
85
114
|
const reports = new Map();
|
|
86
115
|
const candidates = [];
|
|
87
116
|
const seen = new Set();
|
|
88
117
|
for (const entry of discoverable) {
|
|
118
|
+
// A hand-run narrow scope must not discard explicit root grants, including
|
|
119
|
+
// configured providers under an authorized home. Do not scan their histories.
|
|
120
|
+
if (!savedRoots && !expectedProviders.includes(entry.provider)
|
|
121
|
+
&& (explicitlyRootedProviders.includes(entry.provider)
|
|
122
|
+
|| (providerScope.configuredProviders?.includes(entry.provider) && options.providerHome))) {
|
|
123
|
+
const roots = [];
|
|
124
|
+
for (const root of entry.roots)
|
|
125
|
+
if (await pathExists(root))
|
|
126
|
+
roots.push(await canonicalPath(root));
|
|
127
|
+
authorizedRoots[entry.provider] = [...new Set(roots)];
|
|
128
|
+
}
|
|
89
129
|
if (!expectedProviders.includes(entry.provider) || !supportedProviders.has(entry.provider))
|
|
90
130
|
continue;
|
|
91
131
|
const adapter = registry.get(entry.provider);
|
|
92
132
|
if (adapter.disposition === 'unsupported' || !adapter.supportedOperations.includes('discover'))
|
|
93
133
|
continue;
|
|
94
134
|
const availableRoots = [];
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
135
|
+
const rootCounts = new Map();
|
|
136
|
+
for (const root of savedRoots ? savedRoots[entry.provider] ?? [] : entry.roots) {
|
|
137
|
+
if (await pathExists(root)) {
|
|
138
|
+
const canonical = await canonicalPath(root);
|
|
139
|
+
if (savedRoots && canonical !== root) {
|
|
140
|
+
throw new SessionContractError('INVALID_ARGUMENT', 'previously authorized provider root changed; rediscover with explicit authorization');
|
|
141
|
+
}
|
|
142
|
+
if (!availableRoots.includes(canonical)) {
|
|
143
|
+
availableRoots.push(canonical);
|
|
144
|
+
rootCounts.set(canonical, {
|
|
145
|
+
root: redactSourceLocator(canonical), rootId: sha256(canonical),
|
|
146
|
+
status: 'checked', sourceCount: 0, duplicateCount: 0,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
rootCounts.set(root, {
|
|
152
|
+
root: redactSourceLocator(root), rootId: sha256(root),
|
|
153
|
+
status: 'unavailable', sourceCount: 0, duplicateCount: 0,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
98
156
|
}
|
|
99
157
|
if (availableRoots.length === 0) {
|
|
100
158
|
const codexNeedsAuthorization = (entry.provider === 'codex' || entry.provider === 'omp'
|
|
@@ -106,15 +164,44 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
106
164
|
: 'PROVIDER_ROOT_UNAVAILABLE', codexNeedsAuthorization
|
|
107
165
|
? `Pass --${entry.provider === 'deepseek-harness' ? 'dsh' : entry.provider}-root with an explicitly authorized sessions root.`
|
|
108
166
|
: `No authorized ${entry.provider} workspace history root was found.`));
|
|
167
|
+
if (entry.provider === 'codex')
|
|
168
|
+
reports.get(entry.provider).rootCounts = [...rootCounts.values()];
|
|
109
169
|
continue;
|
|
110
170
|
}
|
|
171
|
+
authorizedRoots[entry.provider] = availableRoots;
|
|
111
172
|
const scope = {
|
|
112
173
|
workspaceId,
|
|
113
174
|
allowedRoots: availableRoots,
|
|
114
175
|
};
|
|
115
176
|
const providerSources = [];
|
|
177
|
+
let matchedBySubfolder = 0;
|
|
178
|
+
let matchedByWorktree = 0;
|
|
116
179
|
const museSourceInspections = [];
|
|
117
180
|
for await (const descriptor of adapter.discover(scope)) {
|
|
181
|
+
const known = options.knownSources?.find(source => source.provider === descriptor.provider && source.locator === descriptor.locator);
|
|
182
|
+
const nativeSessionId = known?.nativeSessionId ?? descriptor.nativeSessionId ?? nativeSessionIdFromLocator(descriptor);
|
|
183
|
+
const exclusion = matchImportExclusion({ ...descriptor, nativeSessionId }, exclusions);
|
|
184
|
+
if (exclusion) {
|
|
185
|
+
const locator = resolve(descriptor.locator);
|
|
186
|
+
const authorizedRoot = scope.allowedRoots.find(root => locator === root || locator.startsWith(`${root}/`));
|
|
187
|
+
if (!authorizedRoot)
|
|
188
|
+
continue;
|
|
189
|
+
const key = `excluded\0${entry.provider}\0${locator}`;
|
|
190
|
+
if (seen.has(key))
|
|
191
|
+
continue;
|
|
192
|
+
seen.add(key);
|
|
193
|
+
const source = {
|
|
194
|
+
sourceId: known?.sourceId ?? sha256(key), provider: entry.provider,
|
|
195
|
+
locator, redactedLocator: redactSourceLocator(locator), locatorClass: descriptor.locatorClass,
|
|
196
|
+
authorizedRoot, modifiedAt: '', sizeBytes: 0, digest: '', nativeSessionId,
|
|
197
|
+
status: 'excluded', exclusion,
|
|
198
|
+
};
|
|
199
|
+
rootCounts.get(authorizedRoot).sourceCount += 1;
|
|
200
|
+
providerSources.push(source);
|
|
201
|
+
candidates.push(source);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
let workspaceMatch = null;
|
|
118
205
|
if (entry.provider === 'muse') {
|
|
119
206
|
const inspection = await inspectMuseNativeWorkspace({
|
|
120
207
|
...descriptor,
|
|
@@ -124,31 +211,57 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
124
211
|
museSourceInspections.push({
|
|
125
212
|
sourceKey: sha256(['muse-workspace-inspection-v1', descriptor.locator].join('\0')),
|
|
126
213
|
redactedLocator: redactSourceLocator(descriptor.locator),
|
|
214
|
+
nativeSessionId: basename(dirname(descriptor.locator)),
|
|
127
215
|
...inspection,
|
|
128
216
|
});
|
|
129
217
|
if (inspection.outcome !== 'matched')
|
|
130
218
|
continue;
|
|
219
|
+
workspaceMatch = inspection.matchKind ?? null;
|
|
131
220
|
}
|
|
132
221
|
const locator = await canonicalPath(descriptor.locator);
|
|
133
|
-
if (entry.provider === 'codex'
|
|
134
|
-
|
|
135
|
-
|
|
222
|
+
if (entry.provider === 'codex') {
|
|
223
|
+
workspaceMatch = await codexSourceWorkspaceMatch(locator, workspacePath);
|
|
224
|
+
if (!workspaceMatch)
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
136
227
|
const ompHeader = entry.provider === 'omp' ? await readOmpSessionHeader({ ...descriptor, sourceId: 'discovery', authorizedScope: scope }) : undefined;
|
|
137
|
-
if (ompHeader
|
|
138
|
-
|
|
228
|
+
if (ompHeader) {
|
|
229
|
+
workspaceMatch = await matchSessionWorkspace(ompHeader.cwd, workspacePath);
|
|
230
|
+
if (!workspaceMatch)
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
139
233
|
const details = await stat(locator);
|
|
140
234
|
const authorizedRoot = scope.allowedRoots.find((root) => locator === root || locator.startsWith(`${root}/`));
|
|
141
235
|
if (!authorizedRoot)
|
|
142
236
|
continue;
|
|
237
|
+
if (entry.provider === 'claude') {
|
|
238
|
+
const exactProject = workspacePaths.some(path => basename(authorizedRoot) === claudeProjectKey(path));
|
|
239
|
+
workspaceMatch = await claudeSourceWorkspaceMatch(locator, workspacePath, exactProject);
|
|
240
|
+
if (!workspaceMatch)
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
143
243
|
const fingerprint = await fingerprintSourceFile({
|
|
144
244
|
selectedPath: locator,
|
|
145
245
|
allowedRoots: [authorizedRoot],
|
|
146
246
|
});
|
|
147
|
-
const
|
|
148
|
-
|
|
247
|
+
const rolloutId = entry.provider === 'codex' && descriptor.locatorClass === 'codex-rollout-jsonl'
|
|
248
|
+
? await codexRolloutId(locator) : undefined;
|
|
249
|
+
const dedupeKey = rolloutId
|
|
250
|
+
? `codex\0rollout\0${rolloutId}`
|
|
251
|
+
: `${entry.provider}\0${fingerprint.digest}\0${fingerprint.size}`;
|
|
252
|
+
const rootCount = rootCounts.get(authorizedRoot);
|
|
253
|
+
if (seen.has(dedupeKey)) {
|
|
254
|
+
rootCount.duplicateCount += 1;
|
|
149
255
|
continue;
|
|
256
|
+
}
|
|
150
257
|
seen.add(dedupeKey);
|
|
258
|
+
if (workspaceMatch === 'subfolder')
|
|
259
|
+
matchedBySubfolder += 1;
|
|
260
|
+
if (workspaceMatch === 'worktree')
|
|
261
|
+
matchedByWorktree += 1;
|
|
262
|
+
rootCount.sourceCount += 1;
|
|
151
263
|
const source = sourceFromDescriptor(descriptor, locator, authorizedRoot, details, fingerprint.digest);
|
|
264
|
+
source.nativeSessionId = rolloutId ?? ompHeader?.id ?? nativeSessionId;
|
|
152
265
|
if (ompHeader)
|
|
153
266
|
source.sourceId = sha256(['omp-native-source-v1', workspaceId, ompHeader.id].join('\0'));
|
|
154
267
|
if (entry.provider === 'grokbot') {
|
|
@@ -168,13 +281,17 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
168
281
|
const incompleteMuseInspection = museSourceInspections.some((inspection) => inspection.outcome === 'inconclusive');
|
|
169
282
|
const report = providerReport(entry.provider, incompleteMuseInspection ? 'not-checked' : 'checked', 'discoverable', providerSources, incompleteMuseInspection ? 'MUSE_SOURCE_INSPECTION_INCOMPLETE'
|
|
170
283
|
: providerSources.length === 0 ? 'NO_WORKSPACE_SOURCES' : null, incompleteMuseInspection
|
|
171
|
-
? 'Review museSourceInspections; authorize a supported Muse export for unresolved sources before claiming workspace coverage.'
|
|
284
|
+
? 'Review unattributed museSourceInspections and their limit name/value; authorize a supported Muse export for unresolved sources before claiming workspace coverage.'
|
|
172
285
|
: providerSources.length === 0
|
|
173
286
|
? `The ${entry.provider} root was checked, but no source matched the authorized workspace.`
|
|
174
287
|
: null);
|
|
175
288
|
if (entry.provider === 'muse') {
|
|
176
289
|
report.museSourceInspections = museSourceInspections.sort((a, b) => a.sourceKey.localeCompare(b.sourceKey));
|
|
177
290
|
}
|
|
291
|
+
report.matchedBySubfolder = matchedBySubfolder;
|
|
292
|
+
report.matchedByWorktree = matchedByWorktree;
|
|
293
|
+
if (entry.provider === 'codex')
|
|
294
|
+
report.rootCounts = [...rootCounts.values()];
|
|
178
295
|
reports.set(entry.provider, report);
|
|
179
296
|
}
|
|
180
297
|
for (const provider of expectedProviders) {
|
|
@@ -196,13 +313,14 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
196
313
|
const sources = candidates.sort(compareSources);
|
|
197
314
|
const providers = [...reports.values()].sort((a, b) => a.provider.localeCompare(b.provider));
|
|
198
315
|
const preflight = coveragePreflight({
|
|
199
|
-
|
|
316
|
+
...providerScope,
|
|
200
317
|
registry, runtime: options.runtime, reports: providers,
|
|
201
318
|
});
|
|
202
319
|
const identity = {
|
|
203
320
|
schemaVersion: DISCOVERY_MANIFEST_VERSION,
|
|
204
321
|
workspaceId,
|
|
205
322
|
preflight,
|
|
323
|
+
authorizedRoots,
|
|
206
324
|
sources: sources.map((source) => ({
|
|
207
325
|
sourceId: source.sourceId,
|
|
208
326
|
provider: source.provider,
|
|
@@ -211,11 +329,16 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
211
329
|
modifiedAt: source.modifiedAt,
|
|
212
330
|
sizeBytes: source.sizeBytes,
|
|
213
331
|
digest: source.digest,
|
|
332
|
+
...(source.nativeSessionId ? { nativeSessionId: source.nativeSessionId } : {}),
|
|
333
|
+
...(source.status ? { status: source.status, exclusion: source.exclusion } : {}),
|
|
214
334
|
})),
|
|
215
335
|
providers: providers.map((provider) => ({
|
|
216
336
|
provider: provider.provider,
|
|
217
337
|
status: provider.status,
|
|
218
338
|
reasonCode: provider.reasonCode,
|
|
339
|
+
...(provider.matchedBySubfolder !== undefined ? { matchedBySubfolder: provider.matchedBySubfolder } : {}),
|
|
340
|
+
...(provider.matchedByWorktree !== undefined ? { matchedByWorktree: provider.matchedByWorktree } : {}),
|
|
341
|
+
...(provider.rootCounts ? { rootCounts: provider.rootCounts } : {}),
|
|
219
342
|
...(provider.museSourceInspections ? { museSourceInspections: provider.museSourceInspections } : {}),
|
|
220
343
|
})),
|
|
221
344
|
};
|
|
@@ -226,6 +349,7 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
226
349
|
workspaceId,
|
|
227
350
|
workspacePath,
|
|
228
351
|
preflight,
|
|
352
|
+
authorizedRoots,
|
|
229
353
|
providers,
|
|
230
354
|
sources,
|
|
231
355
|
};
|
|
@@ -272,6 +396,9 @@ export function publicDiscoveryManifest(manifest) {
|
|
|
272
396
|
totals: {
|
|
273
397
|
providers: manifest.providers.length,
|
|
274
398
|
sources: manifest.sources.length,
|
|
399
|
+
excluded: manifest.sources.filter(source => source.status === 'excluded').length,
|
|
400
|
+
matchedBySubfolder: manifest.providers.reduce((sum, provider) => sum + (provider.matchedBySubfolder ?? 0), 0),
|
|
401
|
+
matchedByWorktree: manifest.providers.reduce((sum, provider) => sum + (provider.matchedByWorktree ?? 0), 0),
|
|
275
402
|
},
|
|
276
403
|
};
|
|
277
404
|
}
|
|
@@ -291,12 +418,14 @@ function sourceFromDescriptor(descriptor, locator, authorizedRoot, details, dige
|
|
|
291
418
|
};
|
|
292
419
|
}
|
|
293
420
|
function providerReport(provider, status, disposition, sources, reasonCode, remediation) {
|
|
294
|
-
const timestamps = sources.map((source) => source.modifiedAt).sort();
|
|
421
|
+
const timestamps = sources.map((source) => source.modifiedAt).filter(Boolean).sort();
|
|
295
422
|
return {
|
|
296
423
|
provider,
|
|
297
424
|
status,
|
|
298
425
|
disposition,
|
|
299
426
|
sourceCount: sources.length,
|
|
427
|
+
matchedBySubfolder: 0,
|
|
428
|
+
matchedByWorktree: 0,
|
|
300
429
|
dateRange: {
|
|
301
430
|
earliest: timestamps.at(0) ?? null,
|
|
302
431
|
latest: timestamps.at(-1) ?? null,
|
|
@@ -311,6 +440,7 @@ async function rediscoverManifestIdentity(manifest) {
|
|
|
311
440
|
schemaVersion: manifest.schemaVersion,
|
|
312
441
|
workspaceId: manifest.workspaceId,
|
|
313
442
|
...(manifest.preflight ? { preflight: manifest.preflight } : {}),
|
|
443
|
+
...(manifest.authorizedRoots ? { authorizedRoots: manifest.authorizedRoots } : {}),
|
|
314
444
|
sources: manifest.sources.map((source) => ({
|
|
315
445
|
sourceId: source.sourceId,
|
|
316
446
|
provider: source.provider,
|
|
@@ -319,47 +449,84 @@ async function rediscoverManifestIdentity(manifest) {
|
|
|
319
449
|
modifiedAt: source.modifiedAt,
|
|
320
450
|
sizeBytes: source.sizeBytes,
|
|
321
451
|
digest: source.digest,
|
|
452
|
+
...(source.nativeSessionId ? { nativeSessionId: source.nativeSessionId } : {}),
|
|
453
|
+
...(source.status ? { status: source.status, exclusion: source.exclusion } : {}),
|
|
322
454
|
})),
|
|
323
455
|
providers: manifest.providers.map((provider) => ({
|
|
324
456
|
provider: provider.provider,
|
|
325
457
|
status: provider.status,
|
|
326
458
|
reasonCode: provider.reasonCode,
|
|
459
|
+
...(provider.matchedBySubfolder !== undefined ? { matchedBySubfolder: provider.matchedBySubfolder } : {}),
|
|
460
|
+
...(provider.matchedByWorktree !== undefined ? { matchedByWorktree: provider.matchedByWorktree } : {}),
|
|
461
|
+
...(provider.rootCounts ? { rootCounts: provider.rootCounts } : {}),
|
|
327
462
|
...(provider.museSourceInspections ? { museSourceInspections: provider.museSourceInspections } : {}),
|
|
328
463
|
})),
|
|
329
464
|
};
|
|
330
465
|
return sha256(JSON.stringify(identity));
|
|
331
466
|
}
|
|
332
|
-
async function
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
467
|
+
async function codexDiscoveryRoots(options) {
|
|
468
|
+
const explicit = [...(options.codexRoots ?? []), ...(options.codexRoot ? [options.codexRoot] : [])];
|
|
469
|
+
if (explicit.length > 0)
|
|
470
|
+
return [...new Set(explicit.map((root) => resolve(root)))];
|
|
471
|
+
// Shared stores require the same explicit provider-home authorization as the default root.
|
|
472
|
+
if (!options.providerHome)
|
|
473
|
+
return [];
|
|
474
|
+
const codexHome = join(resolve(options.providerHome), '.codex');
|
|
475
|
+
const roots = [join(codexHome, 'sessions')];
|
|
476
|
+
if (process.env.CODEX_HOME)
|
|
477
|
+
roots.push(resolve(process.env.CODEX_HOME, 'sessions'));
|
|
336
478
|
try {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
value = JSON.parse(line);
|
|
343
|
-
}
|
|
344
|
-
catch {
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
const cwd = codexWorkspaceField(value);
|
|
348
|
-
if (!cwd)
|
|
349
|
-
continue;
|
|
350
|
-
try {
|
|
351
|
-
return await canonicalPath(cwd) === workspacePath;
|
|
352
|
-
}
|
|
353
|
-
catch {
|
|
354
|
-
return resolve(cwd) === workspacePath;
|
|
479
|
+
const roles = await readdir(join(codexHome, 'roles-runtime'), { withFileTypes: true });
|
|
480
|
+
for (const role of roles.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
481
|
+
if (role.isDirectory() || role.isSymbolicLink()) {
|
|
482
|
+
roots.push(join(codexHome, 'roles-runtime', role.name, '.codex', 'sessions'));
|
|
355
483
|
}
|
|
356
484
|
}
|
|
357
|
-
return false;
|
|
358
485
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
486
|
+
catch (error) {
|
|
487
|
+
if (!['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
|
|
488
|
+
throw error;
|
|
362
489
|
}
|
|
490
|
+
return [...new Set(roots)];
|
|
491
|
+
}
|
|
492
|
+
async function codexRolloutId(locator) {
|
|
493
|
+
let count = 0;
|
|
494
|
+
for await (const { line } of streamJsonLines(locator)) {
|
|
495
|
+
if (++count > 50)
|
|
496
|
+
break;
|
|
497
|
+
let value;
|
|
498
|
+
try {
|
|
499
|
+
value = JSON.parse(line);
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
const record = asObject(value);
|
|
505
|
+
const id = asObject(record.payload).id;
|
|
506
|
+
if (record.type === 'session_meta' && typeof id === 'string' && id.length > 0)
|
|
507
|
+
return id;
|
|
508
|
+
}
|
|
509
|
+
return /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i
|
|
510
|
+
.exec(basename(locator))?.[1];
|
|
511
|
+
}
|
|
512
|
+
async function codexSourceWorkspaceMatch(locator, workspacePath) {
|
|
513
|
+
let count = 0;
|
|
514
|
+
for await (const { line } of streamJsonLines(locator)) {
|
|
515
|
+
if (++count > 50)
|
|
516
|
+
break;
|
|
517
|
+
let value;
|
|
518
|
+
try {
|
|
519
|
+
value = JSON.parse(line);
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const cwd = codexWorkspaceField(value);
|
|
525
|
+
if (!cwd)
|
|
526
|
+
continue;
|
|
527
|
+
return await matchSessionWorkspace(cwd, workspacePath);
|
|
528
|
+
}
|
|
529
|
+
return null;
|
|
363
530
|
}
|
|
364
531
|
function codexWorkspaceField(value) {
|
|
365
532
|
const root = asObject(value);
|
|
@@ -384,6 +551,71 @@ function asObject(value) {
|
|
|
384
551
|
async function canonicalPath(path) {
|
|
385
552
|
return realpath(resolve(path));
|
|
386
553
|
}
|
|
554
|
+
/** Claude keys are lossy: use transcript cwd, never decode hyphens into paths. */
|
|
555
|
+
async function claudeDiscoveryRoots(homes, paths, workspace, registry, exclusions) {
|
|
556
|
+
const roots = homes.flatMap(home => paths.map(path => join(home, '.claude', 'projects', claudeProjectKey(path))));
|
|
557
|
+
const adapter = registry.get('claude');
|
|
558
|
+
for (const home of homes) {
|
|
559
|
+
const projects = join(home, '.claude', 'projects');
|
|
560
|
+
let entries;
|
|
561
|
+
try {
|
|
562
|
+
entries = await readdir(projects, { withFileTypes: true });
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
if (['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
|
|
566
|
+
continue;
|
|
567
|
+
throw error;
|
|
568
|
+
}
|
|
569
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
570
|
+
if (!entry.isDirectory())
|
|
571
|
+
continue;
|
|
572
|
+
const root = join(projects, entry.name);
|
|
573
|
+
if (roots.includes(root))
|
|
574
|
+
continue;
|
|
575
|
+
for await (const descriptor of adapter.discover({ workspaceId: workspace, allowedRoots: [root] })) {
|
|
576
|
+
if (matchImportExclusion(descriptor, exclusions))
|
|
577
|
+
continue;
|
|
578
|
+
if (await claudeSourceWorkspaceMatch(descriptor.locator, workspace, false)) {
|
|
579
|
+
roots.push(root);
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return [...new Set(roots)];
|
|
586
|
+
}
|
|
587
|
+
async function claudeSourceWorkspaceMatch(locator, workspacePath, exactProject) {
|
|
588
|
+
let count = 0;
|
|
589
|
+
for await (const { line } of streamJsonLines(locator, { end: 1024 * 1024 - 1 })) {
|
|
590
|
+
if (++count > 50)
|
|
591
|
+
break;
|
|
592
|
+
let value;
|
|
593
|
+
try {
|
|
594
|
+
value = JSON.parse(line);
|
|
595
|
+
}
|
|
596
|
+
catch {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const cwd = asObject(value).cwd;
|
|
600
|
+
if (typeof cwd !== 'string' || cwd.length === 0)
|
|
601
|
+
continue;
|
|
602
|
+
return matchSessionWorkspace(cwd, workspacePath);
|
|
603
|
+
}
|
|
604
|
+
// Only exact project keys can establish ownership without cwd.
|
|
605
|
+
return exactProject ? 'exact' : null;
|
|
606
|
+
}
|
|
607
|
+
export function claudeProjectKey(path) {
|
|
608
|
+
// Claude Code 2.1.302 replaces each character, then caps keys at 200
|
|
609
|
+
// characters with a base-36 suffix from the original path's 32-bit hash.
|
|
610
|
+
const key = path.replace(/[^A-Za-z0-9-]/g, '-');
|
|
611
|
+
if (key.length <= 200)
|
|
612
|
+
return key;
|
|
613
|
+
let hash = 0;
|
|
614
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
615
|
+
hash = ((hash << 5) - hash + path.charCodeAt(index)) | 0;
|
|
616
|
+
}
|
|
617
|
+
return `${key.slice(0, 200)}-${Math.abs(hash).toString(36)}`;
|
|
618
|
+
}
|
|
387
619
|
function workspaceKey(path, retainLeadingSeparator) {
|
|
388
620
|
const normalized = path.replace(/\\/g, '/');
|
|
389
621
|
const input = retainLeadingSeparator ? normalized : normalized.replace(/^\/+/, '');
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { lstat, readFile, realpath, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
export const WORKSPACE_MATCH_KINDS = ['exact', 'subfolder', 'worktree'];
|
|
4
|
+
/** A nested catalog owns its subtree, even when discovery runs one workspace at a time. */
|
|
5
|
+
export async function matchSessionWorkspace(cwd, workspace) {
|
|
6
|
+
try {
|
|
7
|
+
const { path: candidate, missing } = await resolveCandidate(resolve(cwd));
|
|
8
|
+
const workspacePath = await realpath(resolve(workspace));
|
|
9
|
+
const direct = encloses(workspacePath, candidate);
|
|
10
|
+
if (missing && !direct)
|
|
11
|
+
return null;
|
|
12
|
+
const physicalCatalog = await nearestCatalogBetween(candidate, direct ? workspacePath : undefined);
|
|
13
|
+
if (physicalCatalog && !encloses(physicalCatalog, workspacePath))
|
|
14
|
+
return null;
|
|
15
|
+
const mainRepository = await worktreeMainRepository(candidate).catch(() => null);
|
|
16
|
+
// A worktree also belongs to a deeper catalog around its main repository,
|
|
17
|
+
// including when both paths sit beneath the workspace being discovered.
|
|
18
|
+
if (mainRepository && encloses(workspacePath, mainRepository)
|
|
19
|
+
&& await nearestCatalogBetween(mainRepository, workspacePath))
|
|
20
|
+
return null;
|
|
21
|
+
if (direct)
|
|
22
|
+
return candidate === workspacePath ? 'exact' : 'subfolder';
|
|
23
|
+
if (!mainRepository || !encloses(workspacePath, mainRepository))
|
|
24
|
+
return null;
|
|
25
|
+
return 'worktree';
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Unresolved paths or Git metadata cannot establish workspace ownership.
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function resolveCandidate(path) {
|
|
33
|
+
let ancestor = path;
|
|
34
|
+
const suffix = [];
|
|
35
|
+
for (;;) {
|
|
36
|
+
try {
|
|
37
|
+
// Stop at symlinks too, so realpath rejects dangling links below.
|
|
38
|
+
await lstat(ancestor);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error.code !== 'ENOENT')
|
|
43
|
+
throw error;
|
|
44
|
+
const parent = dirname(ancestor);
|
|
45
|
+
if (parent === ancestor)
|
|
46
|
+
throw error;
|
|
47
|
+
suffix.unshift(basename(ancestor));
|
|
48
|
+
ancestor = parent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { path: join(await realpath(ancestor), ...suffix), missing: suffix.length > 0 };
|
|
52
|
+
}
|
|
53
|
+
function encloses(parent, child) {
|
|
54
|
+
const suffix = relative(parent, child);
|
|
55
|
+
return suffix === '' || (!isAbsolute(suffix) && suffix !== '..' && !suffix.startsWith(`..${sep}`));
|
|
56
|
+
}
|
|
57
|
+
async function nearestCatalogBetween(candidate, stop) {
|
|
58
|
+
for (let directory = candidate; directory !== stop; directory = dirname(directory)) {
|
|
59
|
+
const catalog = await statIfPresent(join(directory, '.aiwg', 'sessions'));
|
|
60
|
+
if (catalog?.isDirectory())
|
|
61
|
+
return directory;
|
|
62
|
+
if (directory === dirname(directory))
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
async function worktreeMainRepository(candidate) {
|
|
68
|
+
for (let directory = candidate;; directory = dirname(directory)) {
|
|
69
|
+
const marker = join(directory, '.git');
|
|
70
|
+
const details = await statIfPresent(marker);
|
|
71
|
+
if (details) {
|
|
72
|
+
// Stop at the nearest repository; a normal repository or submodule
|
|
73
|
+
// must not inherit an outer worktree's ownership.
|
|
74
|
+
if (!details.isFile())
|
|
75
|
+
return null;
|
|
76
|
+
const gitfile = /^gitdir: (.+)\s*$/.exec((await readFile(marker, 'utf8')).trim());
|
|
77
|
+
if (!gitfile)
|
|
78
|
+
return null;
|
|
79
|
+
const gitdir = await realpath(resolve(directory, gitfile[1].trim()));
|
|
80
|
+
const common = (await readFile(join(gitdir, 'commondir'), 'utf8')).trim();
|
|
81
|
+
if (!common)
|
|
82
|
+
return null;
|
|
83
|
+
const commonDir = await realpath(resolve(gitdir, common));
|
|
84
|
+
const backlink = (await readFile(join(gitdir, 'gitdir'), 'utf8')).trim();
|
|
85
|
+
if (!backlink || await realpath(resolve(gitdir, backlink)) !== await realpath(marker))
|
|
86
|
+
return null;
|
|
87
|
+
// Standard linked worktrees share the main repository's .git directory.
|
|
88
|
+
if (basename(commonDir) !== '.git' || commonDir === gitdir)
|
|
89
|
+
return null;
|
|
90
|
+
const mainRepository = dirname(commonDir);
|
|
91
|
+
return await realpath(join(mainRepository, '.git')) === commonDir ? mainRepository : null;
|
|
92
|
+
}
|
|
93
|
+
if (directory === dirname(directory))
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function statIfPresent(path) {
|
|
98
|
+
try {
|
|
99
|
+
return await stat(path);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
|
|
103
|
+
return null;
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=workspace-matching.js.map
|
|
@@ -178,6 +178,7 @@ function runGit(args, options = {}) {
|
|
|
178
178
|
maxBuffer: options.maxBuffer ?? 16 * 1024 * 1024,
|
|
179
179
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
180
180
|
timeout: 120_000,
|
|
181
|
+
killSignal: 'SIGKILL',
|
|
181
182
|
});
|
|
182
183
|
if (result.error) {
|
|
183
184
|
if (result.error.code === 'ETIMEDOUT') {
|