substrate-ai 0.2.11 → 0.2.13
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/dist/cli/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createLogger, deepMask } from "../logger-C6n1g8uP.js";
|
|
3
3
|
import { AdapterRegistry, createEventBus } from "../event-bus-J-bw-pkp.js";
|
|
4
4
|
import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator } from "../version-manager-impl-BpVx2DkY.js";
|
|
5
|
-
import { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-
|
|
5
|
+
import { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-CoP8UQU3.js";
|
|
6
6
|
import { ConfigError, ConfigIncompatibleFormatError } from "../errors-BPqtzQ4U.js";
|
|
7
7
|
import { addTokenUsage, createDecision, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-DNYByk0U.js";
|
|
8
8
|
import { aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../metrics-BSg8VIHd.js";
|
|
@@ -2414,16 +2414,24 @@ const DEFAULT_STALL_THRESHOLD_SECONDS = 600;
|
|
|
2414
2414
|
* - `node dist/cli/index.js run` (npm run substrate:dev)
|
|
2415
2415
|
* - `npx substrate run`
|
|
2416
2416
|
* - any node process whose command contains `run` with `--events` or `--stories`
|
|
2417
|
+
*
|
|
2418
|
+
* When `projectRoot` is provided, additionally checks that the command line
|
|
2419
|
+
* contains that path (via `--project-root` flag or as part of the binary/CWD path).
|
|
2420
|
+
* This ensures multi-project environments match the correct orchestrator.
|
|
2417
2421
|
*/
|
|
2418
|
-
function isOrchestratorProcessLine(line) {
|
|
2422
|
+
function isOrchestratorProcessLine(line, projectRoot) {
|
|
2419
2423
|
if (line.includes("grep")) return false;
|
|
2420
|
-
|
|
2421
|
-
if (line.includes("substrate
|
|
2422
|
-
if (line.includes("
|
|
2423
|
-
if (line.includes("
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2424
|
+
let isOrchestrator = false;
|
|
2425
|
+
if (line.includes("substrate run")) isOrchestrator = true;
|
|
2426
|
+
else if (line.includes("substrate-ai run")) isOrchestrator = true;
|
|
2427
|
+
else if (line.includes("index.js run")) isOrchestrator = true;
|
|
2428
|
+
else if (line.includes("node") && /\srun(\s|$)/.test(line) && (line.includes("--events") || line.includes("--stories"))) isOrchestrator = true;
|
|
2429
|
+
if (!isOrchestrator) return false;
|
|
2430
|
+
if (projectRoot !== void 0) return line.includes(projectRoot);
|
|
2431
|
+
return true;
|
|
2432
|
+
}
|
|
2433
|
+
function inspectProcessTree(opts) {
|
|
2434
|
+
const { projectRoot, execFileSync: execFileSyncOverride } = opts ?? {};
|
|
2427
2435
|
const result = {
|
|
2428
2436
|
orchestrator_pid: null,
|
|
2429
2437
|
child_pids: [],
|
|
@@ -2443,7 +2451,7 @@ function inspectProcessTree(execFileSyncOverride) {
|
|
|
2443
2451
|
});
|
|
2444
2452
|
}
|
|
2445
2453
|
const lines = psOutput.split("\n");
|
|
2446
|
-
for (const line of lines) if (isOrchestratorProcessLine(line)) {
|
|
2454
|
+
for (const line of lines) if (isOrchestratorProcessLine(line, projectRoot)) {
|
|
2447
2455
|
const match = line.trim().match(/^(\d+)/);
|
|
2448
2456
|
if (match) {
|
|
2449
2457
|
result.orchestrator_pid = parseInt(match[1], 10);
|
|
@@ -2466,6 +2474,58 @@ function inspectProcessTree(execFileSyncOverride) {
|
|
|
2466
2474
|
return result;
|
|
2467
2475
|
}
|
|
2468
2476
|
/**
|
|
2477
|
+
* Collect all descendant PIDs of the given root PIDs by walking the process
|
|
2478
|
+
* tree recursively. This ensures that grandchildren of the orchestrator
|
|
2479
|
+
* (e.g. node subprocesses spawned by `claude -p`) are also killed during
|
|
2480
|
+
* stall recovery, leaving no orphan processes.
|
|
2481
|
+
*
|
|
2482
|
+
* Returns only the descendants — the root PIDs themselves are NOT included.
|
|
2483
|
+
*/
|
|
2484
|
+
function getAllDescendantPids(rootPids, execFileSyncOverride) {
|
|
2485
|
+
if (rootPids.length === 0) return [];
|
|
2486
|
+
try {
|
|
2487
|
+
let psOutput;
|
|
2488
|
+
if (execFileSyncOverride !== void 0) psOutput = execFileSyncOverride("ps", ["-eo", "pid,ppid"], {
|
|
2489
|
+
encoding: "utf-8",
|
|
2490
|
+
timeout: 5e3
|
|
2491
|
+
});
|
|
2492
|
+
else {
|
|
2493
|
+
const { execFileSync } = __require("node:child_process");
|
|
2494
|
+
psOutput = execFileSync("ps", ["-eo", "pid,ppid"], {
|
|
2495
|
+
encoding: "utf-8",
|
|
2496
|
+
timeout: 5e3
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
const childrenOf = new Map();
|
|
2500
|
+
for (const line of psOutput.split("\n")) {
|
|
2501
|
+
const parts = line.trim().split(/\s+/);
|
|
2502
|
+
if (parts.length >= 2) {
|
|
2503
|
+
const pid = parseInt(parts[0], 10);
|
|
2504
|
+
const ppid = parseInt(parts[1], 10);
|
|
2505
|
+
if (!isNaN(pid) && !isNaN(ppid) && pid > 0) {
|
|
2506
|
+
if (!childrenOf.has(ppid)) childrenOf.set(ppid, []);
|
|
2507
|
+
childrenOf.get(ppid).push(pid);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
const descendants = [];
|
|
2512
|
+
const seen = new Set(rootPids);
|
|
2513
|
+
const queue = [...rootPids];
|
|
2514
|
+
while (queue.length > 0) {
|
|
2515
|
+
const current = queue.shift();
|
|
2516
|
+
const children = childrenOf.get(current) ?? [];
|
|
2517
|
+
for (const child of children) if (!seen.has(child)) {
|
|
2518
|
+
seen.add(child);
|
|
2519
|
+
descendants.push(child);
|
|
2520
|
+
queue.push(child);
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
return descendants;
|
|
2524
|
+
} catch {
|
|
2525
|
+
return [];
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
/**
|
|
2469
2529
|
* Fetch pipeline health data as a structured object without any stdout side-effects.
|
|
2470
2530
|
* Used by runSupervisorAction to poll health without formatting overhead.
|
|
2471
2531
|
*
|
|
@@ -2524,10 +2584,11 @@ async function getAutoHealthData(options) {
|
|
|
2524
2584
|
}
|
|
2525
2585
|
}
|
|
2526
2586
|
} catch {}
|
|
2527
|
-
const processInfo = inspectProcessTree();
|
|
2587
|
+
const processInfo = inspectProcessTree({ projectRoot });
|
|
2528
2588
|
let verdict = "NO_PIPELINE_RUNNING";
|
|
2529
2589
|
if (run.status === "running") if (processInfo.orchestrator_pid === null && active === 0 && completed > 0) verdict = "NO_PIPELINE_RUNNING";
|
|
2530
2590
|
else if (processInfo.zombies.length > 0) verdict = "STALLED";
|
|
2591
|
+
else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length > 0 && stalenessSeconds > DEFAULT_STALL_THRESHOLD_SECONDS) verdict = "HEALTHY";
|
|
2531
2592
|
else if (stalenessSeconds > DEFAULT_STALL_THRESHOLD_SECONDS) verdict = "STALLED";
|
|
2532
2593
|
else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length === 0 && active > 0) verdict = "STALLED";
|
|
2533
2594
|
else verdict = "HEALTHY";
|
|
@@ -2660,6 +2721,7 @@ function defaultSupervisorDeps() {
|
|
|
2660
2721
|
};
|
|
2661
2722
|
}
|
|
2662
2723
|
},
|
|
2724
|
+
getAllDescendants: (rootPids) => getAllDescendantPids(rootPids),
|
|
2663
2725
|
runAnalysis: async (runId, projectRoot) => {
|
|
2664
2726
|
const dbPath = join(projectRoot, ".substrate", "substrate.db");
|
|
2665
2727
|
if (!existsSync(dbPath)) return;
|
|
@@ -2701,7 +2763,7 @@ function defaultSupervisorDeps() {
|
|
|
2701
2763
|
*/
|
|
2702
2764
|
async function runSupervisorAction(options, deps = {}) {
|
|
2703
2765
|
const { pollInterval, stallThreshold, maxRestarts, outputFormat, projectRoot, runId, pack, experiment, maxExperiments } = options;
|
|
2704
|
-
const { getHealth, killPid, resumePipeline, sleep, incrementRestarts, runAnalysis, getTokenSnapshot } = {
|
|
2766
|
+
const { getHealth, killPid, resumePipeline, sleep, incrementRestarts, runAnalysis, getTokenSnapshot, getAllDescendants } = {
|
|
2705
2767
|
...defaultSupervisorDeps(),
|
|
2706
2768
|
...deps
|
|
2707
2769
|
};
|
|
@@ -2833,7 +2895,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2833
2895
|
const expDb = expDbWrapper.db;
|
|
2834
2896
|
const { runRunAction: runPipeline } = await import(
|
|
2835
2897
|
/* @vite-ignore */
|
|
2836
|
-
"../run-
|
|
2898
|
+
"../run-B9IglY4m.js"
|
|
2837
2899
|
);
|
|
2838
2900
|
const runStoryFn = async (opts) => {
|
|
2839
2901
|
const exitCode = await runPipeline({
|
|
@@ -2899,7 +2961,10 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2899
2961
|
return failed.length > 0 || escalated.length > 0 ? 1 : 0;
|
|
2900
2962
|
}
|
|
2901
2963
|
if (health.staleness_seconds >= stallThreshold) {
|
|
2902
|
-
const
|
|
2964
|
+
const directPids = [...health.process.orchestrator_pid !== null ? [health.process.orchestrator_pid] : [], ...health.process.child_pids];
|
|
2965
|
+
const descendantPids = getAllDescendants(directPids);
|
|
2966
|
+
const directPidSet = new Set(directPids);
|
|
2967
|
+
const pids = [...directPids, ...descendantPids.filter((p) => !directPidSet.has(p))];
|
|
2903
2968
|
emitEvent({
|
|
2904
2969
|
type: "supervisor:kill",
|
|
2905
2970
|
run_id: health.run_id,
|
package/dist/index.d.ts
CHANGED
|
@@ -1025,6 +1025,8 @@ interface OrchestratorEvents {
|
|
|
1025
1025
|
storyKey: string;
|
|
1026
1026
|
phase: string;
|
|
1027
1027
|
elapsedMs: number;
|
|
1028
|
+
/** PID of the stalled child process, or null if not tracked */
|
|
1029
|
+
childPid: number | null;
|
|
1028
1030
|
};
|
|
1029
1031
|
/** Readiness check has completed — emitted for all verdicts (READY, NEEDS_WORK, NOT_READY) */
|
|
1030
1032
|
'solutioning:readiness-check': {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./logger-C6n1g8uP.js";
|
|
2
2
|
import "./event-bus-J-bw-pkp.js";
|
|
3
|
-
import { registerRunCommand, runRunAction } from "./run-
|
|
3
|
+
import { registerRunCommand, runRunAction } from "./run-CoP8UQU3.js";
|
|
4
4
|
import "./decisions-DNYByk0U.js";
|
|
5
5
|
import "./metrics-BSg8VIHd.js";
|
|
6
6
|
|
|
@@ -1202,6 +1202,15 @@ function buildPipelineStatusOutput(run, tokenSummary, decisionsCount, storiesCou
|
|
|
1202
1202
|
totalOutput += row.total_output_tokens;
|
|
1203
1203
|
totalCost += row.total_cost_usd;
|
|
1204
1204
|
}
|
|
1205
|
+
let activeDispatches = 0;
|
|
1206
|
+
try {
|
|
1207
|
+
if (run.token_usage_json) {
|
|
1208
|
+
const state = JSON.parse(run.token_usage_json);
|
|
1209
|
+
if (state.stories) {
|
|
1210
|
+
for (const s of Object.values(state.stories)) if (s.phase !== "PENDING" && s.phase !== "COMPLETE" && s.phase !== "ESCALATED") activeDispatches++;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
} catch {}
|
|
1205
1214
|
return {
|
|
1206
1215
|
run_id: run.id,
|
|
1207
1216
|
current_phase: currentPhase,
|
|
@@ -1214,7 +1223,9 @@ function buildPipelineStatusOutput(run, tokenSummary, decisionsCount, storiesCou
|
|
|
1214
1223
|
decisions_count: decisionsCount,
|
|
1215
1224
|
stories_count: storiesCount,
|
|
1216
1225
|
last_activity: run.updated_at,
|
|
1217
|
-
staleness_seconds: Math.round((Date.now() - parseDbTimestampAsUtc(run.updated_at).getTime()) / 1e3)
|
|
1226
|
+
staleness_seconds: Math.round((Date.now() - parseDbTimestampAsUtc(run.updated_at).getTime()) / 1e3),
|
|
1227
|
+
last_event_ts: run.updated_at,
|
|
1228
|
+
active_dispatches: activeDispatches
|
|
1218
1229
|
};
|
|
1219
1230
|
}
|
|
1220
1231
|
/**
|
|
@@ -5359,6 +5370,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5359
5370
|
let _heartbeatTimer = null;
|
|
5360
5371
|
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
5361
5372
|
const WATCHDOG_TIMEOUT_MS = 6e5;
|
|
5373
|
+
const _stalledStories = new Set();
|
|
5362
5374
|
const _phaseStartMs = new Map();
|
|
5363
5375
|
const _phaseEndMs = new Map();
|
|
5364
5376
|
const _storyDispatches = new Map();
|
|
@@ -5454,6 +5466,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
5454
5466
|
}
|
|
5455
5467
|
function recordProgress() {
|
|
5456
5468
|
_lastProgressTs = Date.now();
|
|
5469
|
+
_stalledStories.clear();
|
|
5457
5470
|
}
|
|
5458
5471
|
function startHeartbeat() {
|
|
5459
5472
|
if (_heartbeatTimer !== null) return;
|
|
@@ -5465,7 +5478,8 @@ function createImplementationOrchestrator(deps) {
|
|
|
5465
5478
|
for (const s of _stories.values()) if (s.phase === "COMPLETE" || s.phase === "ESCALATED") completed++;
|
|
5466
5479
|
else if (s.phase === "PENDING") queued++;
|
|
5467
5480
|
else active++;
|
|
5468
|
-
|
|
5481
|
+
const timeSinceProgress = Date.now() - _lastProgressTs;
|
|
5482
|
+
if (timeSinceProgress >= HEARTBEAT_INTERVAL_MS) eventBus.emit("orchestrator:heartbeat", {
|
|
5469
5483
|
runId: config.pipelineRunId ?? "",
|
|
5470
5484
|
activeDispatches: active,
|
|
5471
5485
|
completedDispatches: completed,
|
|
@@ -5474,6 +5488,8 @@ function createImplementationOrchestrator(deps) {
|
|
|
5474
5488
|
const elapsed = Date.now() - _lastProgressTs;
|
|
5475
5489
|
if (elapsed >= WATCHDOG_TIMEOUT_MS) {
|
|
5476
5490
|
for (const [key, s] of _stories) if (s.phase !== "PENDING" && s.phase !== "COMPLETE" && s.phase !== "ESCALATED") {
|
|
5491
|
+
if (_stalledStories.has(key)) continue;
|
|
5492
|
+
_stalledStories.add(key);
|
|
5477
5493
|
logger$16.warn({
|
|
5478
5494
|
storyKey: key,
|
|
5479
5495
|
phase: s.phase,
|
|
@@ -5483,7 +5499,8 @@ function createImplementationOrchestrator(deps) {
|
|
|
5483
5499
|
runId: config.pipelineRunId ?? "",
|
|
5484
5500
|
storyKey: key,
|
|
5485
5501
|
phase: s.phase,
|
|
5486
|
-
elapsedMs: elapsed
|
|
5502
|
+
elapsedMs: elapsed,
|
|
5503
|
+
childPid: null
|
|
5487
5504
|
});
|
|
5488
5505
|
}
|
|
5489
5506
|
}
|
|
@@ -6244,7 +6261,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
6244
6261
|
});
|
|
6245
6262
|
persistState();
|
|
6246
6263
|
recordProgress();
|
|
6247
|
-
startHeartbeat();
|
|
6264
|
+
if (config.enableHeartbeat) startHeartbeat();
|
|
6248
6265
|
if (projectRoot !== void 0) {
|
|
6249
6266
|
const seedResult = seedMethodologyContext(db, projectRoot);
|
|
6250
6267
|
if (seedResult.decisionsCreated > 0) logger$16.info({
|
|
@@ -10697,7 +10714,8 @@ async function runRunAction(options) {
|
|
|
10697
10714
|
run_id: payload.runId,
|
|
10698
10715
|
story_key: payload.storyKey,
|
|
10699
10716
|
phase: payload.phase,
|
|
10700
|
-
elapsed_ms: payload.elapsedMs
|
|
10717
|
+
elapsed_ms: payload.elapsedMs,
|
|
10718
|
+
child_pid: payload.childPid
|
|
10701
10719
|
});
|
|
10702
10720
|
});
|
|
10703
10721
|
}
|
|
@@ -10710,7 +10728,8 @@ async function runRunAction(options) {
|
|
|
10710
10728
|
config: {
|
|
10711
10729
|
maxConcurrency: concurrency,
|
|
10712
10730
|
maxReviewCycles: 2,
|
|
10713
|
-
pipelineRunId: pipelineRun.id
|
|
10731
|
+
pipelineRunId: pipelineRun.id,
|
|
10732
|
+
enableHeartbeat: eventsFlag === true
|
|
10714
10733
|
},
|
|
10715
10734
|
projectRoot
|
|
10716
10735
|
});
|
|
@@ -11153,4 +11172,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
11153
11172
|
|
|
11154
11173
|
//#endregion
|
|
11155
11174
|
export { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
|
|
11156
|
-
//# sourceMappingURL=run-
|
|
11175
|
+
//# sourceMappingURL=run-CoP8UQU3.js.map
|