@aiwg/cli 2026.10.4 → 2026.10.6
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/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
- package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
- package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
- package/agentic/code/providers/model-capabilities.v1.json +74 -50
- package/bin/aiwg.mjs +3 -1
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +72 -9
- package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
- package/dist/src/artifacts/fortemi-core-sync.js +26 -8
- package/dist/src/artifacts/graph-query.js +18 -8
- package/dist/src/artifacts/index-adapters.js +339 -0
- package/dist/src/artifacts/index-builder.js +146 -16
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-preparation.js +91 -26
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +130 -56
- package/dist/src/artifacts/index-status.js +20 -5
- package/dist/src/artifacts/index-worker.mjs +9 -7
- package/dist/src/artifacts/memory-maintenance.js +503 -0
- package/dist/src/artifacts/prebuilt-relations.js +3 -1
- package/dist/src/artifacts/query-engine.js +263 -108
- package/dist/src/artifacts/relations-builder.js +2 -2
- package/dist/src/artifacts/source-graph.js +2 -2
- package/dist/src/artifacts/types.js +9 -1
- package/dist/src/cli/handlers/decision.js +19 -1
- package/dist/src/cli/handlers/guided-setup.js +470 -0
- package/dist/src/cli/handlers/help.js +2 -2
- package/dist/src/cli/handlers/housekeeper.js +1110 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/init.js +41 -6
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/mc.js +509 -53
- package/dist/src/cli/handlers/models.js +32 -2
- package/dist/src/cli/handlers/ralph-launcher.js +76 -4
- package/dist/src/cli/handlers/rules.js +1 -1
- package/dist/src/cli/handlers/script-runner.js +73 -2
- package/dist/src/cli/handlers/serve.js +5 -3
- package/dist/src/cli/handlers/sessions.js +40 -6
- package/dist/src/cli/handlers/setup.js +428 -1
- package/dist/src/cli/handlers/skill-lint.js +1 -1
- package/dist/src/cli/handlers/subcommands.js +118 -8
- package/dist/src/cli/handlers/use.js +279 -93
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +8 -0
- package/dist/src/cli/router.js +16 -1
- package/dist/src/config/aiwg-config.js +42 -0
- package/dist/src/config/cli.js +7 -3
- package/dist/src/config/decision-config.js +14 -1
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/benchmark.js +469 -0
- package/dist/src/decision/ledger.js +22 -3
- package/dist/src/decision/pricing.js +147 -0
- package/dist/src/decision/qualification/gate-evidence.js +18 -15
- package/dist/src/decision/qualification/load-staged.js +539 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +5 -2
- package/dist/src/decision/review/service.js +6 -4
- package/dist/src/decision/review/validate.js +23 -2
- package/dist/src/decision/savings.js +8 -3
- package/dist/src/discovery/index.js +492 -0
- package/dist/src/discovery/interview.js +233 -0
- package/dist/src/discovery/plan-apply.js +571 -0
- package/dist/src/discovery/plan-store.js +171 -0
- package/dist/src/discovery/plan-types.js +2 -0
- package/dist/src/discovery/plan-validation.js +139 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/extensions/commands/definitions.js +49 -9
- package/dist/src/extensions/manifest.js +71 -44
- package/dist/src/extensions/project-local-discovery.js +1 -1
- package/dist/src/extensions/project-quickref.js +18 -17
- package/dist/src/extensions/validation.js +32 -29
- package/dist/src/housekeeper/daemon-config.js +148 -0
- package/dist/src/housekeeper/ipc.js +84 -0
- package/dist/src/housekeeper/lifecycle.js +148 -0
- package/dist/src/housekeeper/platform.js +35 -0
- package/dist/src/housekeeper/status.js +70 -0
- package/dist/src/housekeeper/units.js +114 -0
- package/dist/src/jobs/flow.js +22 -21
- package/dist/src/lint/rule-activation.js +20 -5
- package/dist/src/lint/runner.js +1 -1
- package/dist/src/mcp/cli.mjs +5 -6
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/profiles.js +18 -31
- package/dist/src/mcp/profiles.mjs +18 -32
- package/dist/src/mcp/registry.js +7 -27
- package/dist/src/mcp/registry.mjs +7 -28
- package/dist/src/mcp/server.mjs +34 -1
- package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
- package/dist/src/mcp/tools/command-run.mjs +3 -2
- package/dist/src/mcp/tools/decision.mjs +49 -48
- package/dist/src/mcp/tools/discovery.mjs +46 -21
- package/dist/src/mcp/tools/orchestration.mjs +22 -22
- package/dist/src/mcp/tools/subsystems.mjs +9 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
- package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
- package/dist/src/memory/audit.js +719 -0
- package/dist/src/memory/canonical-context.js +392 -13
- package/dist/src/memory/canonical-maintenance.js +95 -0
- package/dist/src/memory/cli-result.js +22 -0
- package/dist/src/memory/cli.js +176 -0
- package/dist/src/memory/context-pack.js +57 -6
- package/dist/src/memory/cost.js +512 -0
- package/dist/src/memory/firewall-classification.d.mts +7 -0
- package/dist/src/memory/firewall-classification.mjs +32 -0
- package/dist/src/memory/index.js +8 -0
- package/dist/src/memory/line-maintenance.js +68 -0
- package/dist/src/memory/maintenance-cli.js +286 -0
- package/dist/src/memory/maintenance-contract.js +136 -0
- package/dist/src/memory/maintenance-index.js +18 -0
- package/dist/src/memory/maintenance-owner.js +2 -0
- package/dist/src/memory/maintenance-record.js +138 -0
- package/dist/src/memory/maintenance.js +254 -0
- package/dist/src/memory/provider-memory-adapter.js +154 -0
- package/dist/src/memory/recommend-cli.js +83 -0
- package/dist/src/memory/recommend.js +269 -0
- package/dist/src/memory/scheduled.js +81 -0
- package/dist/src/memory/taxonomy.js +44 -0
- package/dist/src/mission-protocol/codecs.js +19 -9
- package/dist/src/models/config-loader.js +11 -1
- package/dist/src/models/model-capabilities.v1.json +74 -50
- package/dist/src/models/provider-policy.js +125 -62
- package/dist/src/models/resolver.js +45 -8
- package/dist/src/models/worker-installation.d.mts +7 -0
- package/dist/src/models/worker-installation.mjs +169 -37
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/providers/deploy-version.d.mts +1 -0
- package/dist/src/providers/deploy-version.mjs +17 -0
- package/dist/src/providers/provider-definitions.js +19 -16
- package/dist/src/providers/skill-ownership.d.mts +11 -2
- package/dist/src/providers/skill-ownership.mjs +35 -17
- package/dist/src/rules/activation.d.mts +11 -2
- package/dist/src/rules/activation.mjs +82 -6
- package/dist/src/rules/inventory.js +1 -1
- package/dist/src/rules/rule-frontmatter.schema.json +2 -0
- package/dist/src/schema/zod-validation.d.mts +6 -0
- package/dist/src/schema/zod-validation.mjs +50 -0
- package/dist/src/serve/executor-registry.js +297 -20
- package/dist/src/serve/executor-token.d.mts +1 -0
- package/dist/src/serve/executor-token.mjs +8 -0
- package/dist/src/serve/shared-host-scheduler.js +1102 -31
- package/dist/src/serve/worker-invocation.js +114 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +33 -19
- package/dist/src/sessions/adapters/copilot.js +14 -13
- package/dist/src/sessions/adapters/cursor.js +19 -18
- package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
- package/dist/src/sessions/adapters/factory.js +11 -10
- package/dist/src/sessions/adapters/generic.js +32 -23
- package/dist/src/sessions/adapters/grokbot.js +35 -35
- package/dist/src/sessions/adapters/hermes.js +20 -19
- package/dist/src/sessions/adapters/muse.js +50 -49
- package/dist/src/sessions/adapters/omp.js +5 -4
- package/dist/src/sessions/adapters/openclaw.js +30 -29
- package/dist/src/sessions/adapters/opencode.js +26 -26
- package/dist/src/sessions/adapters/openhuman.js +24 -23
- package/dist/src/sessions/adapters/pi.js +5 -4
- package/dist/src/sessions/adapters/windsurf.js +8 -8
- package/dist/src/sessions/analytics.js +10 -9
- package/dist/src/sessions/batch-contracts.js +1 -1
- package/dist/src/sessions/batch-import.js +202 -2
- package/dist/src/sessions/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +73 -54
- package/dist/src/sessions/coverage-preflight.js +26 -1
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/policy.js +18 -7
- package/dist/src/sessions/refresh.js +2 -3
- package/dist/src/sessions/repository.js +4 -2
- package/dist/src/sessions/workspace-discovery.js +92 -26
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/writing/exemplar-selection.js +6 -6
- package/dist/src/writing/voice-evaluation.js +24 -23
- package/dist/src/writing/writer-migration.js +14 -13
- package/dist/src/writing/writer-profile.js +19 -18
- package/dist/src/writing/writing-brief.js +18 -17
- package/dist/src/writing/writing-receipt.js +47 -46
- package/package.json +5 -2
- package/schemas/decision/DecisionReview.schema.json +78 -1
- package/schemas/mission-v1.schema.json +3 -0
- package/tools/agents/deploy-agents.mjs +67 -39
- package/tools/agents/providers/antigravity.mjs +6 -1
- package/tools/agents/providers/base.mjs +19 -13
- package/tools/agents/providers/claude.mjs +1 -1
- package/tools/agents/providers/codex.mjs +50 -56
- package/tools/agents/providers/copilot.mjs +8 -2
- package/tools/agents/providers/cursor.mjs +1 -1
- package/tools/agents/providers/deepseek-harness.mjs +1 -1
- package/tools/agents/providers/factory.mjs +7 -1
- package/tools/agents/providers/grok-build.mjs +18 -2
- package/tools/agents/providers/grokbot.mjs +1 -1
- package/tools/agents/providers/hermes.mjs +7 -13
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +28 -4
- package/tools/agents/providers/opencode.mjs +14 -5
- package/tools/agents/providers/openhuman.mjs +1 -1
- package/tools/agents/providers/pi.mjs +1 -1
- package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
- package/tools/agents/providers/rule-delivery.mjs +263 -71
- package/tools/agents/providers/windsurf.mjs +7 -0
- package/tools/cli/workspace-status.mjs +5 -0
- package/tools/commands/deploy-prompts-codex.mjs +11 -8
- package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
- package/tools/ralph-external/process-monitor.mjs +7 -4
- package/tools/ralph-external/session-launcher.mjs +280 -32
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
|
@@ -5,10 +5,11 @@ import { inspectMuseNativeWorkspace } from './adapters/muse.js';
|
|
|
5
5
|
import { readOmpSessionHeader } from './adapters/omp.js';
|
|
6
6
|
import { grokbotConversationIdFromLocator } from './adapters/grokbot-locator.js';
|
|
7
7
|
import { resolveOmpPaths } from '../providers/omp-paths.mjs';
|
|
8
|
-
import {
|
|
8
|
+
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
9
|
+
import { SessionCoveragePreflightSchema, SessionContractError, sha256, } from './contracts.js';
|
|
9
10
|
import { redactSourceLocator } from './discovery.js';
|
|
10
11
|
import { createSessionAdapterRegistry } from './adapter-registry.js';
|
|
11
|
-
import { coveragePreflight,
|
|
12
|
+
import { coveragePreflight, providerScopeWarnings, resolveSessionProviderScope } from './coverage-preflight.js';
|
|
12
13
|
import { fingerprintSourceFile, streamJsonLines } from './readers.js';
|
|
13
14
|
import { matchSessionWorkspace } from './workspace-matching.js';
|
|
14
15
|
import { matchImportExclusion, nativeSessionIdFromLocator, resolveImportExclusions } from './import-exclusions.js';
|
|
@@ -20,9 +21,26 @@ const MANUAL_EXPORT_PROVIDERS = new Set([
|
|
|
20
21
|
export async function discoverWorkspaceHistories(options) {
|
|
21
22
|
const registry = options.registry ?? createSessionAdapterRegistry();
|
|
22
23
|
const exclusions = resolveImportExclusions(undefined, options.exclude);
|
|
23
|
-
const expectedProviders = expectedSessionProviders(options.expectedProviders ?? SESSION_PROVIDER_IDS);
|
|
24
24
|
const supportedProviders = new Set(registry.list().map((adapter) => adapter.provider));
|
|
25
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;
|
|
26
44
|
const workspaceId = workspacePath;
|
|
27
45
|
const workspacePaths = [...new Set([workspacePath, resolve(options.workspace),
|
|
28
46
|
...(options.workspaceAliases ?? []).map((alias) => resolve(alias))])];
|
|
@@ -52,12 +70,19 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
52
70
|
? [resolve(options.grokbotRoot)] : [] },
|
|
53
71
|
{
|
|
54
72
|
provider: 'claude',
|
|
55
|
-
|
|
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),
|
|
56
77
|
},
|
|
57
78
|
{
|
|
58
79
|
provider: 'codex',
|
|
59
|
-
roots:
|
|
60
|
-
|
|
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
|
+
] }),
|
|
61
86
|
},
|
|
62
87
|
{
|
|
63
88
|
provider: 'cursor',
|
|
@@ -85,11 +110,22 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
85
110
|
: providerHomes.map((providerHome) => join(providerHome, '.pi', 'agent', 'sessions'))).flatMap((sessionsRoot) => workspacePaths.map((workspace) => join(sessionsRoot, `--${workspaceKey(workspace, false)}--`))),
|
|
86
111
|
},
|
|
87
112
|
];
|
|
88
|
-
const authorizedRoots = { ...
|
|
113
|
+
const authorizedRoots = { ...savedRoots };
|
|
89
114
|
const reports = new Map();
|
|
90
115
|
const candidates = [];
|
|
91
116
|
const seen = new Set();
|
|
92
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
|
+
}
|
|
93
129
|
if (!expectedProviders.includes(entry.provider) || !supportedProviders.has(entry.provider))
|
|
94
130
|
continue;
|
|
95
131
|
const adapter = registry.get(entry.provider);
|
|
@@ -97,10 +133,10 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
97
133
|
continue;
|
|
98
134
|
const availableRoots = [];
|
|
99
135
|
const rootCounts = new Map();
|
|
100
|
-
for (const root of
|
|
136
|
+
for (const root of savedRoots ? savedRoots[entry.provider] ?? [] : entry.roots) {
|
|
101
137
|
if (await pathExists(root)) {
|
|
102
138
|
const canonical = await canonicalPath(root);
|
|
103
|
-
if (
|
|
139
|
+
if (savedRoots && canonical !== root) {
|
|
104
140
|
throw new SessionContractError('INVALID_ARGUMENT', 'previously authorized provider root changed; rediscover with explicit authorization');
|
|
105
141
|
}
|
|
106
142
|
if (!availableRoots.includes(canonical)) {
|
|
@@ -198,9 +234,12 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
198
234
|
const authorizedRoot = scope.allowedRoots.find((root) => locator === root || locator.startsWith(`${root}/`));
|
|
199
235
|
if (!authorizedRoot)
|
|
200
236
|
continue;
|
|
201
|
-
if (entry.provider === 'claude'
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
}
|
|
204
243
|
const fingerprint = await fingerprintSourceFile({
|
|
205
244
|
selectedPath: locator,
|
|
206
245
|
allowedRoots: [authorizedRoot],
|
|
@@ -274,7 +313,7 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
274
313
|
const sources = candidates.sort(compareSources);
|
|
275
314
|
const providers = [...reports.values()].sort((a, b) => a.provider.localeCompare(b.provider));
|
|
276
315
|
const preflight = coveragePreflight({
|
|
277
|
-
|
|
316
|
+
...providerScope,
|
|
278
317
|
registry, runtime: options.runtime, reports: providers,
|
|
279
318
|
});
|
|
280
319
|
const identity = {
|
|
@@ -427,13 +466,12 @@ async function rediscoverManifestIdentity(manifest) {
|
|
|
427
466
|
}
|
|
428
467
|
async function codexDiscoveryRoots(options) {
|
|
429
468
|
const explicit = [...(options.codexRoots ?? []), ...(options.codexRoot ? [options.codexRoot] : [])];
|
|
430
|
-
|
|
431
|
-
return [...new Set(explicit.map((root) => resolve(root)))];
|
|
469
|
+
const roots = [...new Set(explicit.map((root) => resolve(root)))];
|
|
432
470
|
// Shared stores require the same explicit provider-home authorization as the default root.
|
|
433
471
|
if (!options.providerHome)
|
|
434
|
-
return
|
|
472
|
+
return roots;
|
|
435
473
|
const codexHome = join(resolve(options.providerHome), '.codex');
|
|
436
|
-
|
|
474
|
+
roots.push(join(codexHome, 'sessions'));
|
|
437
475
|
if (process.env.CODEX_HOME)
|
|
438
476
|
roots.push(resolve(process.env.CODEX_HOME, 'sessions'));
|
|
439
477
|
try {
|
|
@@ -512,7 +550,40 @@ function asObject(value) {
|
|
|
512
550
|
async function canonicalPath(path) {
|
|
513
551
|
return realpath(resolve(path));
|
|
514
552
|
}
|
|
515
|
-
|
|
553
|
+
/** Claude keys are lossy: use transcript cwd, never decode hyphens into paths. */
|
|
554
|
+
async function claudeDiscoveryRoots(homes, paths, workspace, registry, exclusions) {
|
|
555
|
+
const roots = homes.flatMap(home => paths.map(path => join(home, '.claude', 'projects', claudeProjectKey(path))));
|
|
556
|
+
const adapter = registry.get('claude');
|
|
557
|
+
for (const home of homes) {
|
|
558
|
+
const projects = join(home, '.claude', 'projects');
|
|
559
|
+
let entries;
|
|
560
|
+
try {
|
|
561
|
+
entries = await readdir(projects, { withFileTypes: true });
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
if (['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
|
|
565
|
+
continue;
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
569
|
+
if (!entry.isDirectory())
|
|
570
|
+
continue;
|
|
571
|
+
const root = join(projects, entry.name);
|
|
572
|
+
if (roots.includes(root))
|
|
573
|
+
continue;
|
|
574
|
+
for await (const descriptor of adapter.discover({ workspaceId: workspace, allowedRoots: [root] })) {
|
|
575
|
+
if (matchImportExclusion(descriptor, exclusions))
|
|
576
|
+
continue;
|
|
577
|
+
if (await claudeSourceWorkspaceMatch(descriptor.locator, workspace, false)) {
|
|
578
|
+
roots.push(root);
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return [...new Set(roots)];
|
|
585
|
+
}
|
|
586
|
+
async function claudeSourceWorkspaceMatch(locator, workspacePath, exactProject) {
|
|
516
587
|
let count = 0;
|
|
517
588
|
for await (const { line } of streamJsonLines(locator, { end: 1024 * 1024 - 1 })) {
|
|
518
589
|
if (++count > 50)
|
|
@@ -527,15 +598,10 @@ async function claudeSourceMatchesWorkspace(locator, workspacePath) {
|
|
|
527
598
|
const cwd = asObject(value).cwd;
|
|
528
599
|
if (typeof cwd !== 'string' || cwd.length === 0)
|
|
529
600
|
continue;
|
|
530
|
-
|
|
531
|
-
return await canonicalPath(cwd) === workspacePath;
|
|
532
|
-
}
|
|
533
|
-
catch {
|
|
534
|
-
return resolve(cwd) === workspacePath;
|
|
535
|
-
}
|
|
601
|
+
return matchSessionWorkspace(cwd, workspacePath);
|
|
536
602
|
}
|
|
537
|
-
//
|
|
538
|
-
return
|
|
603
|
+
// Only exact project keys can establish ownership without cwd.
|
|
604
|
+
return exactProject ? 'exact' : null;
|
|
539
605
|
}
|
|
540
606
|
export function claudeProjectKey(path) {
|
|
541
607
|
// Claude Code 2.1.302 replaces each character, then caps keys at 200
|
|
@@ -18,6 +18,7 @@ import { promises as fs } from 'node:fs';
|
|
|
18
18
|
import * as path from 'node:path';
|
|
19
19
|
import { projectControlPath } from '../../config/project-artifacts.js';
|
|
20
20
|
import { buildClaudeHookBlock, CLAUDE_HOOK_START, CLAUDE_HOOK_END } from './claude-hook.js';
|
|
21
|
+
import { dedupeProviderBootstrap } from './workspace-context.js';
|
|
21
22
|
import { dominantLineEnding, withLineEnding } from './line-endings.js';
|
|
22
23
|
import { buildParallelismSection, replaceOrAppendParallelismBlock } from './parallelism-section.js';
|
|
23
24
|
import { buildContextFinalizationBlock, replaceOrAppendFinalizationBlock } from './finalization.js';
|
|
@@ -60,6 +61,7 @@ export async function buildAiwgMdContent(projectPath, stagedClaudeMdContent) {
|
|
|
60
61
|
// Regenerate may mirror CLAUDE.md before its hook updater runs. Normalize
|
|
61
62
|
// that managed block here too so legacy duplicate policy never reaches
|
|
62
63
|
// AIWG.md, even on the first refresh. Operator content stays untouched.
|
|
64
|
+
claudeMdContent = dedupeProviderBootstrap(claudeMdContent);
|
|
63
65
|
const start = claudeMdContent.indexOf(CLAUDE_HOOK_START);
|
|
64
66
|
const end = claudeMdContent.indexOf(CLAUDE_HOOK_END, start);
|
|
65
67
|
if (start >= 0 && end >= start) {
|
|
@@ -72,7 +74,22 @@ export async function buildAiwgMdContent(projectPath, stagedClaudeMdContent) {
|
|
|
72
74
|
// context, so the directive becomes a self-reference when copied through.
|
|
73
75
|
// Match the directive when it appears on its own line (possibly indented).
|
|
74
76
|
const SELF_INCLUDE_PATTERN = /^\s*@AIWG\.md\s*$/;
|
|
75
|
-
|
|
77
|
+
// #3451: CLAUDE.md already `@`-imports WORKSPACE.md; a second `@` path to
|
|
78
|
+
// the same file would load it twice, so AIWG.md refers to it in plain text.
|
|
79
|
+
const CONFIG_IMPORT_PATTERN = /^\s*@\S*aiwg\.config\s*$/;
|
|
80
|
+
const WORKSPACE_IMPORT_PATTERN = /^\s*@(?:\.\.\/)?WORKSPACE\.md\s*$/;
|
|
81
|
+
const filtered = lines
|
|
82
|
+
.filter((line) => !SELF_INCLUDE_PATTERN.test(line))
|
|
83
|
+
.map((line) => {
|
|
84
|
+
if (WORKSPACE_IMPORT_PATTERN.test(line)) {
|
|
85
|
+
return 'WORKSPACE.md is the canonical project context (loaded by CLAUDE.md; read it first if your harness did not).';
|
|
86
|
+
}
|
|
87
|
+
// Legacy CLAUDE.md files may still `@`-import the raw JSON config.
|
|
88
|
+
if (CONFIG_IMPORT_PATTERN.test(line)) {
|
|
89
|
+
return 'Project configuration: `.aiwg/aiwg.config` (not auto-loaded; run `aiwg config show --project` to view it).';
|
|
90
|
+
}
|
|
91
|
+
return line;
|
|
92
|
+
});
|
|
76
93
|
const firstLine = filtered[0] ?? '';
|
|
77
94
|
const rest = filtered.slice(1);
|
|
78
95
|
// If the file already carries the signature elsewhere, don't double-insert.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import * as fs from 'fs/promises';
|
|
23
23
|
import * as path from 'path';
|
|
24
|
-
import { buildProviderBootstrapBlock, PROVIDER_BOOTSTRAP_START, PROVIDER_BOOTSTRAP_END, } from './workspace-context.js';
|
|
24
|
+
import { buildProviderBootstrapBlock, PROVIDER_BOOTSTRAP_START, PROVIDER_BOOTSTRAP_END, dedupeProviderBootstrap, } from './workspace-context.js';
|
|
25
25
|
import { dominantLineEnding, withLineEnding } from './line-endings.js';
|
|
26
26
|
import { assertSafeContextFile } from './context-file-safety.js';
|
|
27
27
|
export const CLAUDE_HOOK_START = '<!-- AIWG:claude-md-hook:start -->';
|
|
@@ -31,7 +31,9 @@ export function buildClaudeHookBlock(_policy = {}) {
|
|
|
31
31
|
CLAUDE_HOOK_START,
|
|
32
32
|
'',
|
|
33
33
|
buildProviderBootstrapBlock('claude'),
|
|
34
|
-
|
|
34
|
+
// Plain-text pointer, deliberately not an `@` import: the raw JSON config
|
|
35
|
+
// is several KB and would be pulled into every system prompt (#3451).
|
|
36
|
+
'Project configuration: `.aiwg/aiwg.config` (not auto-loaded; run `aiwg config show --project` to view it).',
|
|
35
37
|
'',
|
|
36
38
|
'Artifact destination authority: `aiwg show rule artifact-output-policy` (kernel).',
|
|
37
39
|
'',
|
|
@@ -45,6 +47,30 @@ export function buildClaudeHookBlock(_policy = {}) {
|
|
|
45
47
|
CLAUDE_HOOK_END,
|
|
46
48
|
].join('\n');
|
|
47
49
|
}
|
|
50
|
+
/** Keep the first managed hook block and drop any later complete copies. */
|
|
51
|
+
function collapseExtraHookBlocks(content) {
|
|
52
|
+
const first = content.indexOf(CLAUDE_HOOK_START);
|
|
53
|
+
if (first === -1)
|
|
54
|
+
return content;
|
|
55
|
+
const firstEnd = content.indexOf(CLAUDE_HOOK_END, first);
|
|
56
|
+
if (firstEnd === -1)
|
|
57
|
+
return content;
|
|
58
|
+
const blockEnd = firstEnd + CLAUDE_HOOK_END.length;
|
|
59
|
+
let tail = content.slice(blockEnd);
|
|
60
|
+
const before = tail;
|
|
61
|
+
for (;;) {
|
|
62
|
+
const s = tail.indexOf(CLAUDE_HOOK_START);
|
|
63
|
+
if (s === -1)
|
|
64
|
+
break;
|
|
65
|
+
const e = tail.indexOf(CLAUDE_HOOK_END, s);
|
|
66
|
+
if (e === -1)
|
|
67
|
+
break;
|
|
68
|
+
tail = tail.slice(0, s) + tail.slice(e + CLAUDE_HOOK_END.length);
|
|
69
|
+
}
|
|
70
|
+
if (tail === before)
|
|
71
|
+
return content;
|
|
72
|
+
return content.slice(0, blockEnd) + tail.replace(/(?:\r?\n){3,}/g, '\n\n');
|
|
73
|
+
}
|
|
48
74
|
/**
|
|
49
75
|
* Ensure CLAUDE.md at projectPath contains the AIWG-managed marker block
|
|
50
76
|
* pointing at AIWG.md. Returns a result describing what was done.
|
|
@@ -72,6 +98,11 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
72
98
|
}
|
|
73
99
|
throw err;
|
|
74
100
|
}
|
|
101
|
+
// Repair duplicates left by earlier generations before locating markers, so
|
|
102
|
+
// regeneration converges on exactly one bootstrap (#3451).
|
|
103
|
+
const original = existing;
|
|
104
|
+
existing = collapseExtraHookBlocks(dedupeProviderBootstrap(existing));
|
|
105
|
+
const repaired = existing !== original;
|
|
75
106
|
const lineEnding = dominantLineEnding(existing);
|
|
76
107
|
block = withLineEnding(block, lineEnding);
|
|
77
108
|
const startIdx = existing.indexOf(CLAUDE_HOOK_START);
|
|
@@ -90,7 +121,7 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
90
121
|
const updated = existing.substring(0, bootstrapStartIdx)
|
|
91
122
|
+ block
|
|
92
123
|
+ existing.substring(bootstrapEndIdx);
|
|
93
|
-
await fs.writeFile(claudeMdPath, updated, 'utf8');
|
|
124
|
+
await fs.writeFile(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
|
|
94
125
|
result.action = 'updated';
|
|
95
126
|
return result;
|
|
96
127
|
}
|
|
@@ -101,7 +132,7 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
101
132
|
// Ensure the file ends with a single newline before appending.
|
|
102
133
|
const trimmed = existing.replace(/(?:\r?\n)+$/, lineEnding);
|
|
103
134
|
const updated = `${trimmed}${lineEnding}${block}${lineEnding}`;
|
|
104
|
-
await fs.writeFile(claudeMdPath, updated, 'utf8');
|
|
135
|
+
await fs.writeFile(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
|
|
105
136
|
result.action = 'inserted';
|
|
106
137
|
return result;
|
|
107
138
|
}
|
|
@@ -119,7 +150,7 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
119
150
|
}
|
|
120
151
|
const trimmed = existing.replace(/(?:\r?\n)+$/, lineEnding);
|
|
121
152
|
const updated = `${trimmed}${lineEnding}${block}${lineEnding}`;
|
|
122
|
-
await fs.writeFile(claudeMdPath, updated, 'utf8');
|
|
153
|
+
await fs.writeFile(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
|
|
123
154
|
result.action = 'inserted';
|
|
124
155
|
return result;
|
|
125
156
|
}
|
|
@@ -127,6 +158,11 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
127
158
|
const blockEndOffset = endIdx + CLAUDE_HOOK_END.length;
|
|
128
159
|
const currentBlock = existing.substring(startIdx, blockEndOffset);
|
|
129
160
|
if (currentBlock === block) {
|
|
161
|
+
if (repaired) {
|
|
162
|
+
await fs.writeFile(claudeMdPath, existing, 'utf8');
|
|
163
|
+
result.action = 'updated';
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
130
166
|
result.action = 'unchanged';
|
|
131
167
|
return result;
|
|
132
168
|
}
|
|
@@ -142,7 +178,7 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
142
178
|
result.warnings.push(`CLAUDE.md AIWG hook block has been modified inside the markers; updating to canonical content. Operator content outside the block is preserved.`);
|
|
143
179
|
}
|
|
144
180
|
const updated = existing.substring(0, startIdx) + block + existing.substring(blockEndOffset);
|
|
145
|
-
await fs.writeFile(claudeMdPath, updated, 'utf8');
|
|
181
|
+
await fs.writeFile(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
|
|
146
182
|
result.action = 'updated';
|
|
147
183
|
return result;
|
|
148
184
|
}
|
|
@@ -13,12 +13,18 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @implements #1362
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import { readFile } from 'node:fs/promises';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
import { dirname, join } from 'node:path';
|
|
17
19
|
import { fileURLToPath } from 'node:url';
|
|
18
20
|
import { findPackageRoot } from '../../cli/find-package-root.js';
|
|
19
21
|
import { readAiwgConfigCandidate, validateWorkerConfigCandidate, resolveParallelism, PROVIDER_PARALLELISM_DEFAULTS } from '../../config/aiwg-config.js';
|
|
22
|
+
import { ConfigLoader } from '../../models/config-loader.js';
|
|
23
|
+
import { validateWorkerCatalogDiscovery } from '../../models/provider-policy.js';
|
|
20
24
|
export const PARALLELISM_BLOCK_START = '<!-- AIWG-PARALLELISM-CAP:START -->';
|
|
21
25
|
export const PARALLELISM_BLOCK_END = '<!-- AIWG-PARALLELISM-CAP:END -->';
|
|
26
|
+
/** Plain-path pointer (not an `@` import) to the long health/supervision explanation. */
|
|
27
|
+
export const WORKER_HEALTH_DOC_POINTER = '`docs/getting-started/worker-dispatch.md` ("Worker health windows and supervision")';
|
|
22
28
|
/**
|
|
23
29
|
* One-line note rendered when the bundled model defaults cannot be loaded.
|
|
24
30
|
* The orchestration health block is omitted; the rest of the section renders
|
|
@@ -109,6 +115,58 @@ function groupSummaries(entries, parallelism) {
|
|
|
109
115
|
return parts.join('; ') + ')';
|
|
110
116
|
});
|
|
111
117
|
}
|
|
118
|
+
/** Same cache used by model discovery; absence/corruption cannot invent evidence. */
|
|
119
|
+
async function readCachedCatalog() {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(await readFile(join(homedir(), '.cache/aiwg/model-catalog.v1.json'), 'utf8'));
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function observationSummaries(entries, deps) {
|
|
128
|
+
const candidate = await (deps.readCatalog ?? readCachedCatalog)();
|
|
129
|
+
const catalog = candidate?.version && candidate.providers && !validateWorkerCatalogDiscovery(candidate.discovery).length
|
|
130
|
+
? candidate : null;
|
|
131
|
+
const readAt = (deps.now ?? (() => new Date()))();
|
|
132
|
+
const stamp = (value) => {
|
|
133
|
+
const time = value === undefined ? NaN : Date.parse(value);
|
|
134
|
+
if (!Number.isFinite(time))
|
|
135
|
+
return 'unknown';
|
|
136
|
+
const age = readAt.getTime() - time;
|
|
137
|
+
return `${new Date(time).toISOString()} (${age < 0 ? 'future timestamp; age unavailable' : `${Math.floor(age / 1000)}s old`})`;
|
|
138
|
+
};
|
|
139
|
+
const range = (values) => {
|
|
140
|
+
if (!values.length)
|
|
141
|
+
return 'unknown';
|
|
142
|
+
values.sort((a, b) => Date.parse(a) - Date.parse(b));
|
|
143
|
+
const oldest = stamp(values[0]);
|
|
144
|
+
return Date.parse(values[0]) === Date.parse(values[values.length - 1]) ? oldest : `${oldest} to ${stamp(values[values.length - 1])}`;
|
|
145
|
+
};
|
|
146
|
+
return [
|
|
147
|
+
`Cached model observations read at ${readAt.toISOString()}; catalog fetchedAt: ${stamp(catalog?.discovery?.fetchedAt)}. Ages are measured at this read and grow afterward. This cache may differ from the resolver's current installation observations; no eligibility read is performed by context generation. Missing or ambiguous observations have unknown age.`,
|
|
148
|
+
'',
|
|
149
|
+
...entries.map(([id, group]) => {
|
|
150
|
+
const observations = group.members.flatMap(member => {
|
|
151
|
+
// Mirror the resolver's exact, unambiguous id/API-id lookup per installation.
|
|
152
|
+
const rows = catalog?.discovery?.instances?.[member.cli]?.models
|
|
153
|
+
.filter(row => row.id === member.model || row.apiModelId === member.model) ?? [];
|
|
154
|
+
return rows.length === 1 && rows[0].worker ? [rows[0].worker] : [];
|
|
155
|
+
});
|
|
156
|
+
return `- ${oneLine(id, 64)} cached observations: ${observations.length}/${group.members.length} members; observedAt: ${range(observations.map(row => row.observedAt))}; verifiedAt: ${range(observations.map(row => row.verifiedAt))}.`;
|
|
157
|
+
}),
|
|
158
|
+
'',
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
function capacitySummary(parallelism) {
|
|
162
|
+
const caps = parallelism.admission;
|
|
163
|
+
const summaries = ['groupCaps', 'memberCaps', 'cliCaps', 'modelCaps', 'quotaCaps'].map(field => {
|
|
164
|
+
const entries = Object.entries(caps?.[field] ?? {});
|
|
165
|
+
const shown = entries.slice(0, MAX_SHOWN_GROUPS).map(([key, value]) => `${oneLine(key, 96)}=${value}`).join(', ');
|
|
166
|
+
return `${field}: ${shown || 'none configured'}${entries.length > MAX_SHOWN_GROUPS ? ` (+${entries.length - MAX_SHOWN_GROUPS} more)` : ''}`;
|
|
167
|
+
});
|
|
168
|
+
return `Configured concurrency ceilings (\`parallelism.admission\`): ${summaries.join('; ')}. Local/self-hosted models obey these same ceilings: CLI caps use installation IDs, model caps use canonical inference-provider/API-model keys, and quota caps use the installation's quotaScope. Unset caps add no extra ceiling; a current/request capacity ceiling of zero blocks selection. The resolver returns the minimum applicable ceiling as \`effectiveMaxParallel\`; Tier 0 conductors must self-count active workers, including workers sharing a local server.`;
|
|
169
|
+
}
|
|
112
170
|
/**
|
|
113
171
|
* One configured health window with its origin. The origin mirrors
|
|
114
172
|
* `resolveWorkerEffectiveHealth()` (`src/serve/worker-invocation.ts`): a
|
|
@@ -268,15 +326,24 @@ export async function buildParallelismSection(projectPath, deps = {}) {
|
|
|
268
326
|
lines.push('');
|
|
269
327
|
lines.push('A group above shown as configured, with excluded members or an uncovered required role listed, is a static config read — not a live eligibility verdict. Eligibility is resolved per (group, member, effort) tuple against the current catalog and request; see `aiwg models resolve` below to check a specific role/task/group.');
|
|
270
328
|
lines.push('');
|
|
271
|
-
lines.push(
|
|
329
|
+
lines.push(...await observationSummaries(entries.slice(0, MAX_SHOWN_GROUPS), deps));
|
|
330
|
+
const models = await new ConfigLoader(projectPath, { aiwgHome: resolveAiwgHome() }).load(cfg.parallelism);
|
|
331
|
+
const freshness = models.defaults.workerSelection.freshness;
|
|
332
|
+
lines.push(`Resolver freshness limits from merged model policy: observation warning after ${freshness.warnHours} hours; observation blocked after ${freshness.blockHours} hours; verification blocked after ${freshness.policyDays} days. Future-dated evidence is blocked. Re-resolve before launch; cached ages do not establish eligibility.`);
|
|
333
|
+
lines.push('');
|
|
334
|
+
lines.push('Cross-family review: when required, the reviewer must use a different observed model family from the writer. Different CLI names do not prove different families. The resolver requires unambiguous writer lineage in `reviewOf` with `requireDifferentFamily`; missing, conflicting, unknown, or same-family lineage blocks required review. The direct CLI does not accept review-lineage flags. Family diversity does not establish independent errors; same-vendor or cross-vendor agreement is not proof. Consequential claims require an executable check or human acceptance. Record observed evaluator model/vendor/family, criteria, disagreements and re-review links. Calibration guide: `$AIWG_ROOT/docs/decision/evaluator-calibration.md`.');
|
|
335
|
+
lines.push('');
|
|
336
|
+
lines.push(capacitySummary(cfg.parallelism));
|
|
337
|
+
lines.push('');
|
|
338
|
+
lines.push('Tier 0 direct handoff is the default: use your own shell or a qualified MCP worker tool. Run the rendered command from your own shell outside your own harness sandbox (Claude Code `sandbox.excludedCommands` or an unsandboxed Bash call). The worker CLI native sandbox, per-task worktree and resource scope provide isolation; never nest sandboxes: a CLI sandbox inside a conductor sandbox fails; do not route Tier 0 workers through `aiwg ralph-external --launch-worker`, whose systemd-unit isolation and namespace probe apply only to that launcher and managed launches. No Mission/MC session, scheduler, daemon, or activation marker is needed. Self-count active native + CLI + MCP workers, take the minimum of the project, group, CLI, model, and context caps, and launch each writing task once.');
|
|
272
339
|
lines.push('');
|
|
273
340
|
lines.push('Before resolving, qualify the installed CLIs and configured model roles with `aiwg doctor --probe-workers --qualify` (`--yes` for headless confirmation).');
|
|
274
341
|
lines.push('Agents pass `--authorize-premium` to `aiwg models resolve` only after the human explicitly confirmed the premium escalation (escalation-discipline rule); authorization applies to that resolution only.');
|
|
275
342
|
lines.push('Resolve each bounded brief with `aiwg models resolve --role <role> --task <class> --group <group> --brief <file> --workdir <tree> --final-output <file> --transport shell --format shell`. Copy the exact rendered command or the MCP server/tool/arguments byte-for-byte; never assemble vendor flags from memory. Re-resolve after any brief, profile, model, or workspace change. `--format shell` prints only the launch command: to see the resolved health windows for an attempt, use `--format json` (the default) and read `invocation.health` with its per-field `origins`.');
|
|
276
343
|
lines.push('');
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
lines.push(
|
|
344
|
+
// #3451: the long window-scaling and supervision explanation lives in the
|
|
345
|
+
// worker-dispatch doc (and the subagent-scoping rule); keep a summary here.
|
|
346
|
+
lines.push(`Worker health windows (origin in parentheses): check every ${check.value}s (${check.origin}); startup ${startup.value}s (${startup.origin}); quiet ${quiet.value}s (${quiet.origin}); stall ${stall.value}s (${stall.origin}); hard stop ${hard.value}s (${hard.origin}). Resolved values win. Supervise by observer state: only \`hung\` authorizes a no-progress stop, and only of a worker you own; read status with \`aiwg ralph-external --status --workers --runs-root <dir> --json\` (read-only). Details: ${WORKER_HEALTH_DOC_POINTER} and \`aiwg show rule subagent-scoping\`.`);
|
|
280
347
|
lines.push('');
|
|
281
348
|
}
|
|
282
349
|
lines.push(PARALLELISM_BLOCK_END);
|