substrate-ai 0.13.1 → 0.14.0
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
|
@@ -4,7 +4,7 @@ import { createLogger } from "../logger-KeHncl-f.js";
|
|
|
4
4
|
import { createEventBus } from "../helpers-CElYrONe.js";
|
|
5
5
|
import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, EXPERIMENT_RESULT, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRequirements, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-CLvAwmT7.js";
|
|
6
6
|
import "../adapter-registry-DXLMTmfD.js";
|
|
7
|
-
import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-
|
|
7
|
+
import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-CUMPhuVq.js";
|
|
8
8
|
import "../errors-D1LU8CZ9.js";
|
|
9
9
|
import "../routing-CcBOCuC9.js";
|
|
10
10
|
import "../decisions-C0pz9Clx.js";
|
|
@@ -4359,7 +4359,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
4359
4359
|
await initSchema(expAdapter);
|
|
4360
4360
|
const { runRunAction: runPipeline } = await import(
|
|
4361
4361
|
/* @vite-ignore */
|
|
4362
|
-
"../run-
|
|
4362
|
+
"../run-CZo7hpsh.js"
|
|
4363
4363
|
);
|
|
4364
4364
|
const runStoryFn = async (opts) => {
|
|
4365
4365
|
const exitCode = await runPipeline({
|
|
@@ -22162,6 +22162,10 @@ var RunStateManager = class {
|
|
|
22162
22162
|
*/
|
|
22163
22163
|
function createConvergenceController() {
|
|
22164
22164
|
const outcomes = new Map();
|
|
22165
|
+
/** Returns true only when id is non-empty AND exists in graph.nodes. */
|
|
22166
|
+
function isValidTarget(id, graph) {
|
|
22167
|
+
return id !== "" && graph.nodes.has(id);
|
|
22168
|
+
}
|
|
22165
22169
|
return {
|
|
22166
22170
|
recordOutcome(nodeId, status) {
|
|
22167
22171
|
outcomes.set(nodeId, status);
|
|
@@ -22177,10 +22181,387 @@ function createConvergenceController() {
|
|
|
22177
22181
|
satisfied: failingNodes.length === 0,
|
|
22178
22182
|
failingNodes
|
|
22179
22183
|
};
|
|
22184
|
+
},
|
|
22185
|
+
checkGoalGates(graph, runId, eventBus) {
|
|
22186
|
+
const failedGates = [];
|
|
22187
|
+
for (const [id, node] of graph.nodes) {
|
|
22188
|
+
if (!node.goalGate) continue;
|
|
22189
|
+
const status = outcomes.get(id);
|
|
22190
|
+
const satisfied = status === "SUCCESS" || status === "PARTIAL_SUCCESS";
|
|
22191
|
+
eventBus?.emit("graph:goal-gate-checked", {
|
|
22192
|
+
runId,
|
|
22193
|
+
nodeId: id,
|
|
22194
|
+
satisfied
|
|
22195
|
+
});
|
|
22196
|
+
if (!satisfied) failedGates.push(id);
|
|
22197
|
+
}
|
|
22198
|
+
return {
|
|
22199
|
+
satisfied: failedGates.length === 0,
|
|
22200
|
+
failedGates
|
|
22201
|
+
};
|
|
22202
|
+
},
|
|
22203
|
+
resolveRetryTarget(failedNode, graph) {
|
|
22204
|
+
const candidates = [
|
|
22205
|
+
failedNode.retryTarget,
|
|
22206
|
+
failedNode.fallbackRetryTarget,
|
|
22207
|
+
graph.retryTarget,
|
|
22208
|
+
graph.fallbackRetryTarget
|
|
22209
|
+
];
|
|
22210
|
+
for (const candidate of candidates) if (isValidTarget(candidate, graph)) return candidate;
|
|
22211
|
+
return null;
|
|
22180
22212
|
}
|
|
22181
22213
|
};
|
|
22182
22214
|
}
|
|
22183
22215
|
|
|
22216
|
+
//#endregion
|
|
22217
|
+
//#region packages/factory/dist/convergence/budget.js
|
|
22218
|
+
const DEFAULT_BACKOFF = {
|
|
22219
|
+
initialDelay: 200,
|
|
22220
|
+
factor: 2,
|
|
22221
|
+
maxDelay: 6e4,
|
|
22222
|
+
jitterFactor: .5
|
|
22223
|
+
};
|
|
22224
|
+
/**
|
|
22225
|
+
* Compute the delay (in milliseconds) before the next retry attempt.
|
|
22226
|
+
*
|
|
22227
|
+
* Formula (before jitter):
|
|
22228
|
+
* `baseDelay = initialDelay * factor^attemptIndex`
|
|
22229
|
+
* `cappedDelay = Math.min(baseDelay, maxDelay)`
|
|
22230
|
+
*
|
|
22231
|
+
* Jitter:
|
|
22232
|
+
* `jitter = (Math.random() * 2 - 1) * jitterFactor * cappedDelay`
|
|
22233
|
+
* `delay = Math.max(0, Math.round(cappedDelay + jitter))`
|
|
22234
|
+
*
|
|
22235
|
+
* Passing `{ jitterFactor: 0 }` disables jitter for deterministic tests.
|
|
22236
|
+
*
|
|
22237
|
+
* @param attemptIndex - Zero-based index of the current attempt (0 = first retry).
|
|
22238
|
+
* @param options - Optional overrides for the backoff parameters.
|
|
22239
|
+
*/
|
|
22240
|
+
function computeBackoffDelay(attemptIndex, options) {
|
|
22241
|
+
const { initialDelay, factor, maxDelay, jitterFactor } = {
|
|
22242
|
+
...DEFAULT_BACKOFF,
|
|
22243
|
+
...options
|
|
22244
|
+
};
|
|
22245
|
+
const baseDelay = initialDelay * Math.pow(factor, attemptIndex);
|
|
22246
|
+
const cappedDelay = Math.min(baseDelay, maxDelay);
|
|
22247
|
+
const jitter = (Math.random() * 2 - 1) * jitterFactor * cappedDelay;
|
|
22248
|
+
return Math.max(0, Math.round(cappedDelay + jitter));
|
|
22249
|
+
}
|
|
22250
|
+
/**
|
|
22251
|
+
* Determine whether a pipeline is permitted to dispatch the next node.
|
|
22252
|
+
*
|
|
22253
|
+
* **Unlimited mode:** When `cap === 0` the function returns `{ allowed: true }`
|
|
22254
|
+
* immediately, regardless of `accumulatedCost`. This matches the
|
|
22255
|
+
* `FactoryConfigSchema` default of `budget_cap_usd: 0` which means "no limit".
|
|
22256
|
+
*
|
|
22257
|
+
* **Strict greater-than boundary:** Enforcement triggers only when
|
|
22258
|
+
* `accumulatedCost > cap`. A cost that exactly equals the cap is still allowed,
|
|
22259
|
+
* consistent with the PRD wording "halts *before* dispatching further nodes
|
|
22260
|
+
* when accumulated cost **exceeds** the cap."
|
|
22261
|
+
*
|
|
22262
|
+
* @param accumulatedCost - Total cost (USD) spent so far during this pipeline run.
|
|
22263
|
+
* @param cap - Maximum allowed cost (USD). `0` disables enforcement.
|
|
22264
|
+
* @returns `{ allowed: true }` when the pipeline may continue, or
|
|
22265
|
+
* `{ allowed: false, reason: '...' }` when the budget is exhausted.
|
|
22266
|
+
*/
|
|
22267
|
+
function checkPipelineBudget(accumulatedCost, cap) {
|
|
22268
|
+
if (cap === 0) return { allowed: true };
|
|
22269
|
+
if (accumulatedCost > cap) return {
|
|
22270
|
+
allowed: false,
|
|
22271
|
+
reason: `pipeline budget exhausted: $${accumulatedCost.toFixed(2)} > $${cap.toFixed(2)}`
|
|
22272
|
+
};
|
|
22273
|
+
return { allowed: true };
|
|
22274
|
+
}
|
|
22275
|
+
/**
|
|
22276
|
+
* Tracks accumulated cost for a single pipeline run and enforces a configurable
|
|
22277
|
+
* spending cap via `checkPipelineBudget`.
|
|
22278
|
+
*
|
|
22279
|
+
* **Lifecycle:** Create one instance per pipeline run. Story 45-8 will call
|
|
22280
|
+
* `addCost()` after each node dispatch completes and `checkBudget()` before
|
|
22281
|
+
* the next dispatch. Call `reset()` between pipeline runs or in tests to clear
|
|
22282
|
+
* accumulated state.
|
|
22283
|
+
*/
|
|
22284
|
+
var PipelineBudgetManager = class {
|
|
22285
|
+
totalCost = 0;
|
|
22286
|
+
/**
|
|
22287
|
+
* Add `amount` USD to the running total for this pipeline run.
|
|
22288
|
+
*
|
|
22289
|
+
* @param amount - Cost (USD) for the just-completed node dispatch.
|
|
22290
|
+
*/
|
|
22291
|
+
addCost(amount) {
|
|
22292
|
+
this.totalCost += amount;
|
|
22293
|
+
}
|
|
22294
|
+
/**
|
|
22295
|
+
* Return the total cost (USD) accumulated so far during this pipeline run.
|
|
22296
|
+
*/
|
|
22297
|
+
getTotalCost() {
|
|
22298
|
+
return this.totalCost;
|
|
22299
|
+
}
|
|
22300
|
+
/**
|
|
22301
|
+
* Reset the accumulated cost to zero.
|
|
22302
|
+
* Useful for test isolation and future pipeline reuse scenarios.
|
|
22303
|
+
*/
|
|
22304
|
+
reset() {
|
|
22305
|
+
this.totalCost = 0;
|
|
22306
|
+
}
|
|
22307
|
+
/**
|
|
22308
|
+
* Determine whether the pipeline may dispatch the next node, delegating to
|
|
22309
|
+
* `checkPipelineBudget` with the current accumulated cost.
|
|
22310
|
+
*
|
|
22311
|
+
* @param cap - Maximum allowed cost (USD). `0` disables enforcement.
|
|
22312
|
+
*/
|
|
22313
|
+
checkBudget(cap) {
|
|
22314
|
+
return checkPipelineBudget(this.totalCost, cap);
|
|
22315
|
+
}
|
|
22316
|
+
};
|
|
22317
|
+
/**
|
|
22318
|
+
* Determine whether a pipeline session is permitted to dispatch the next node
|
|
22319
|
+
* based on wall-clock elapsed time.
|
|
22320
|
+
*
|
|
22321
|
+
* **Unlimited mode:** When `capMs === 0` the function returns `{ allowed: true }`
|
|
22322
|
+
* immediately, regardless of `elapsedMs`. This matches the `FactoryConfigSchema`
|
|
22323
|
+
* default of `wall_clock_cap_seconds: 0` which means "no limit".
|
|
22324
|
+
*
|
|
22325
|
+
* **Strict greater-than boundary:** Enforcement triggers only when
|
|
22326
|
+
* `elapsedMs > capMs`. An elapsed time that exactly equals the cap is still
|
|
22327
|
+
* allowed, consistent with the PRD wording "halts *before* dispatching further
|
|
22328
|
+
* nodes when elapsed time **exceeds** the cap."
|
|
22329
|
+
*
|
|
22330
|
+
* @param elapsedMs - Milliseconds elapsed since the pipeline session started.
|
|
22331
|
+
* @param capMs - Maximum allowed elapsed time in milliseconds. `0` disables
|
|
22332
|
+
* enforcement (unlimited mode).
|
|
22333
|
+
* @returns `{ allowed: true }` when the session may continue, or
|
|
22334
|
+
* `{ allowed: false, reason: 'wall clock budget exhausted' }` when the
|
|
22335
|
+
* cap has been exceeded.
|
|
22336
|
+
*/
|
|
22337
|
+
function checkSessionBudget(elapsedMs, capMs) {
|
|
22338
|
+
if (capMs === 0) return { allowed: true };
|
|
22339
|
+
if (elapsedMs > capMs) return {
|
|
22340
|
+
allowed: false,
|
|
22341
|
+
reason: "wall clock budget exhausted"
|
|
22342
|
+
};
|
|
22343
|
+
return { allowed: true };
|
|
22344
|
+
}
|
|
22345
|
+
/**
|
|
22346
|
+
* Tracks wall-clock elapsed time for a single pipeline session and enforces a
|
|
22347
|
+
* configurable time cap via `checkSessionBudget`.
|
|
22348
|
+
*
|
|
22349
|
+
* **Lifecycle:** Create one instance per pipeline run, constructed at pipeline
|
|
22350
|
+
* launch. Story 45-8 will call `checkBudget()` before each node dispatch as
|
|
22351
|
+
* the highest-priority budget check (before `PipelineBudgetManager`).
|
|
22352
|
+
* Call `reset()` between pipeline runs or in tests for
|
|
22353
|
+
* isolation.
|
|
22354
|
+
*
|
|
22355
|
+
* **Cap 0 means unlimited:** A `capSeconds` value of `0` passed to `checkBudget`
|
|
22356
|
+
* disables all wall-clock enforcement and always returns `{ allowed: true }`.
|
|
22357
|
+
*/
|
|
22358
|
+
var SessionBudgetManager = class {
|
|
22359
|
+
startTime;
|
|
22360
|
+
constructor() {
|
|
22361
|
+
this.startTime = Date.now();
|
|
22362
|
+
}
|
|
22363
|
+
/**
|
|
22364
|
+
* Return the number of milliseconds elapsed since this manager was constructed
|
|
22365
|
+
* (or since the last `reset()` call). Always returns a non-negative number.
|
|
22366
|
+
*/
|
|
22367
|
+
getElapsedMs() {
|
|
22368
|
+
return Date.now() - this.startTime;
|
|
22369
|
+
}
|
|
22370
|
+
/**
|
|
22371
|
+
* Reset the session start timestamp to the current time. Subsequent calls to
|
|
22372
|
+
* `getElapsedMs()` will measure from this new baseline. Useful for test
|
|
22373
|
+
* isolation and future pipeline reuse scenarios.
|
|
22374
|
+
*/
|
|
22375
|
+
reset() {
|
|
22376
|
+
this.startTime = Date.now();
|
|
22377
|
+
}
|
|
22378
|
+
/**
|
|
22379
|
+
* Determine whether the pipeline session may dispatch the next node, delegating
|
|
22380
|
+
* to `checkSessionBudget` with the current elapsed time converted from seconds
|
|
22381
|
+
* to milliseconds.
|
|
22382
|
+
*
|
|
22383
|
+
* @param capSeconds - Maximum allowed elapsed time in **seconds** (as stored in
|
|
22384
|
+
* `FactoryConfig.wall_clock_cap_seconds`). A value of `0`
|
|
22385
|
+
* disables enforcement.
|
|
22386
|
+
*/
|
|
22387
|
+
checkBudget(capSeconds) {
|
|
22388
|
+
return checkSessionBudget(this.getElapsedMs(), capSeconds * 1e3);
|
|
22389
|
+
}
|
|
22390
|
+
};
|
|
22391
|
+
|
|
22392
|
+
//#endregion
|
|
22393
|
+
//#region packages/factory/dist/convergence/plateau.js
|
|
22394
|
+
/**
|
|
22395
|
+
* Plateau detection for the convergence loop.
|
|
22396
|
+
* Story 45-6: provides pure plateau detection primitives — no I/O, no side effects.
|
|
22397
|
+
*
|
|
22398
|
+
* Algorithm: Track the last N satisfaction scores (N = `window`, default 3).
|
|
22399
|
+
* If max−min of the window falls strictly below threshold, declare plateau.
|
|
22400
|
+
*
|
|
22401
|
+
* Consumed by:
|
|
22402
|
+
* - Story 45-8 (convergence controller integration)
|
|
22403
|
+
*/
|
|
22404
|
+
const DEFAULT_WINDOW = 3;
|
|
22405
|
+
const DEFAULT_THRESHOLD = .05;
|
|
22406
|
+
/**
|
|
22407
|
+
* Create a new PlateauDetector with the given options.
|
|
22408
|
+
*
|
|
22409
|
+
* **Defaults:** `window=3`, `threshold=0.05` — matching `FactoryConfigSchema.plateau_window`
|
|
22410
|
+
* and `FactoryConfigSchema.plateau_threshold`. Story 45-8 will read these values from
|
|
22411
|
+
* `FactoryConfig` and pass them in.
|
|
22412
|
+
*
|
|
22413
|
+
* **Insufficient-data guard:** `isPlateaued()` always returns `false` when fewer than
|
|
22414
|
+
* `window` scores have been recorded. A plateau can only be declared once the window is full.
|
|
22415
|
+
*
|
|
22416
|
+
* @param options - Optional configuration for window size and threshold.
|
|
22417
|
+
*/
|
|
22418
|
+
function createPlateauDetector(options) {
|
|
22419
|
+
const window = options?.window ?? DEFAULT_WINDOW;
|
|
22420
|
+
const threshold = options?.threshold ?? DEFAULT_THRESHOLD;
|
|
22421
|
+
let scores = [];
|
|
22422
|
+
return {
|
|
22423
|
+
recordScore(_iteration, score) {
|
|
22424
|
+
scores.push(score);
|
|
22425
|
+
scores = scores.slice(-window);
|
|
22426
|
+
},
|
|
22427
|
+
isPlateaued() {
|
|
22428
|
+
if (scores.length < window) return false;
|
|
22429
|
+
const delta = Math.max(...scores) - Math.min(...scores);
|
|
22430
|
+
return delta < threshold;
|
|
22431
|
+
},
|
|
22432
|
+
getWindow() {
|
|
22433
|
+
return window;
|
|
22434
|
+
},
|
|
22435
|
+
getScores() {
|
|
22436
|
+
return [...scores];
|
|
22437
|
+
}
|
|
22438
|
+
};
|
|
22439
|
+
}
|
|
22440
|
+
/**
|
|
22441
|
+
* Check whether the detector has reached a plateau and, if so, emit the
|
|
22442
|
+
* `convergence:plateau-detected` event on the provided event bus.
|
|
22443
|
+
*
|
|
22444
|
+
* This mirrors the `checkGoalGates()` pattern:
|
|
22445
|
+
* - Pure detection is isolated in `PlateauDetector` (no side effects).
|
|
22446
|
+
* - Event emission is isolated here in this wrapper.
|
|
22447
|
+
* - Callers may omit `eventBus` for pure check behavior (no event is emitted).
|
|
22448
|
+
*
|
|
22449
|
+
* @param detector - A `PlateauDetector` instance.
|
|
22450
|
+
* @param context - Run/node identifiers and an optional event bus.
|
|
22451
|
+
* @returns `{ plateaued: true, scores }` with event emitted when plateaued;
|
|
22452
|
+
* `{ plateaued: false, scores }` with no event emitted otherwise.
|
|
22453
|
+
*/
|
|
22454
|
+
function checkPlateauAndEmit(detector, context) {
|
|
22455
|
+
const { runId, nodeId, eventBus } = context;
|
|
22456
|
+
const scores = detector.getScores();
|
|
22457
|
+
if (detector.isPlateaued()) {
|
|
22458
|
+
eventBus?.emit("convergence:plateau-detected", {
|
|
22459
|
+
runId,
|
|
22460
|
+
nodeId,
|
|
22461
|
+
scores,
|
|
22462
|
+
window: detector.getWindow()
|
|
22463
|
+
});
|
|
22464
|
+
return {
|
|
22465
|
+
plateaued: true,
|
|
22466
|
+
scores
|
|
22467
|
+
};
|
|
22468
|
+
}
|
|
22469
|
+
return {
|
|
22470
|
+
plateaued: false,
|
|
22471
|
+
scores
|
|
22472
|
+
};
|
|
22473
|
+
}
|
|
22474
|
+
|
|
22475
|
+
//#endregion
|
|
22476
|
+
//#region packages/factory/dist/convergence/remediation.js
|
|
22477
|
+
/**
|
|
22478
|
+
* Remediation context injection for the convergence loop.
|
|
22479
|
+
* Story 45-7: builds structured remediation context from failure data and
|
|
22480
|
+
* injects it into a retried node's IGraphContext.
|
|
22481
|
+
*
|
|
22482
|
+
* Architecture reference: Section 6.5 — Remediation Context fields
|
|
22483
|
+
*
|
|
22484
|
+
* Pure functions (`formatScenarioDiff`, `deriveFixScope`, `buildRemediationContext`)
|
|
22485
|
+
* have no I/O and no side effects.
|
|
22486
|
+
* Only `injectRemediationContext` mutates state (the IGraphContext).
|
|
22487
|
+
*
|
|
22488
|
+
* Consumed by:
|
|
22489
|
+
* - Story 45-8 (convergence controller integration with executor)
|
|
22490
|
+
* - CodergenBackend handlers (via `getRemediationContext`)
|
|
22491
|
+
*/
|
|
22492
|
+
/**
|
|
22493
|
+
* The agreed key under which remediation context is stored in `IGraphContext`.
|
|
22494
|
+
* Namespaced under `convergence.` to avoid collision with user-defined context keys.
|
|
22495
|
+
* Story 45-8 writes this key; CodergenBackend handlers read it via `getRemediationContext()`.
|
|
22496
|
+
*/
|
|
22497
|
+
const REMEDIATION_CONTEXT_KEY = "convergence.remediation";
|
|
22498
|
+
/**
|
|
22499
|
+
* Formats a human-readable diff of failed scenarios from a ScenarioRunResult.
|
|
22500
|
+
*
|
|
22501
|
+
* This is a pure formatting function with no side effects. For each failed
|
|
22502
|
+
* scenario it produces a line `"- {name}: {stderr || stdout || '(no output)'}"`,
|
|
22503
|
+
* preferring stderr (most useful for debugging), falling back to stdout
|
|
22504
|
+
* (some tools write errors to stdout), then to the literal `'(no output)'`.
|
|
22505
|
+
*
|
|
22506
|
+
* Returns `"All scenarios passed"` when there are no failures.
|
|
22507
|
+
*/
|
|
22508
|
+
function formatScenarioDiff(results) {
|
|
22509
|
+
const failed = results.scenarios.filter((s$1) => s$1.status === "fail");
|
|
22510
|
+
if (failed.length === 0) return "All scenarios passed";
|
|
22511
|
+
const lines = failed.map((s$1) => {
|
|
22512
|
+
const output = s$1.stderr || s$1.stdout || "(no output)";
|
|
22513
|
+
return `- ${s$1.name}: ${output}`;
|
|
22514
|
+
});
|
|
22515
|
+
return lines.join("\n");
|
|
22516
|
+
}
|
|
22517
|
+
/**
|
|
22518
|
+
* Derives a focused fix instruction string from failed scenarios.
|
|
22519
|
+
*
|
|
22520
|
+
* This function produces human-readable fix instructions for the retried agent.
|
|
22521
|
+
* Returns `"Fix {n} failing scenario{s}: {name1}, {name2}, ..."` when there are
|
|
22522
|
+
* failures, or `""` when all scenarios pass.
|
|
22523
|
+
*
|
|
22524
|
+
* Pluralization: singular "scenario" when n === 1, plural "scenarios" otherwise.
|
|
22525
|
+
*/
|
|
22526
|
+
function deriveFixScope(results) {
|
|
22527
|
+
const failed = results.scenarios.filter((s$1) => s$1.status === "fail");
|
|
22528
|
+
if (failed.length === 0) return "";
|
|
22529
|
+
const n$1 = failed.length;
|
|
22530
|
+
const plural = n$1 === 1 ? "scenario" : "scenarios";
|
|
22531
|
+
const names = failed.map((s$1) => s$1.name).join(", ");
|
|
22532
|
+
return `Fix ${n$1} failing ${plural}: ${names}`;
|
|
22533
|
+
}
|
|
22534
|
+
/**
|
|
22535
|
+
* Builds a complete `RemediationContext` from the provided parameters.
|
|
22536
|
+
*
|
|
22537
|
+
* `scenarioResults` is optional — first-iteration retries may not have scenario
|
|
22538
|
+
* data yet. When omitted, `scenarioDiff` defaults to
|
|
22539
|
+
* `"No scenario results available"` and `fixScope` defaults to `""`.
|
|
22540
|
+
*
|
|
22541
|
+
* Stores `satisfactionScoreHistory` as a defensive copy (`[...params.satisfactionScoreHistory]`)
|
|
22542
|
+
* so external mutation of the caller's array does not corrupt the stored history.
|
|
22543
|
+
*/
|
|
22544
|
+
function buildRemediationContext(params) {
|
|
22545
|
+
const scenarioDiff = params.scenarioResults ? formatScenarioDiff(params.scenarioResults) : "No scenario results available";
|
|
22546
|
+
const fixScope = params.scenarioResults ? deriveFixScope(params.scenarioResults) : "";
|
|
22547
|
+
return {
|
|
22548
|
+
previousFailureReason: params.previousFailureReason,
|
|
22549
|
+
scenarioDiff,
|
|
22550
|
+
iterationCount: params.iterationCount,
|
|
22551
|
+
satisfactionScoreHistory: [...params.satisfactionScoreHistory],
|
|
22552
|
+
fixScope
|
|
22553
|
+
};
|
|
22554
|
+
}
|
|
22555
|
+
/**
|
|
22556
|
+
* Injects a `RemediationContext` into an `IGraphContext` under `REMEDIATION_CONTEXT_KEY`.
|
|
22557
|
+
*
|
|
22558
|
+
* Called by the executor's retry loop before dispatching to the retried node —
|
|
22559
|
+
* story 45-8 wires this call into the graph executor.
|
|
22560
|
+
*/
|
|
22561
|
+
function injectRemediationContext(context, remediation) {
|
|
22562
|
+
context.set(REMEDIATION_CONTEXT_KEY, remediation);
|
|
22563
|
+
}
|
|
22564
|
+
|
|
22184
22565
|
//#endregion
|
|
22185
22566
|
//#region packages/factory/dist/graph/executor.js
|
|
22186
22567
|
/**
|
|
@@ -22220,17 +22601,6 @@ function normalizeOutcomeStatus(raw) {
|
|
|
22220
22601
|
};
|
|
22221
22602
|
}
|
|
22222
22603
|
/**
|
|
22223
|
-
* Compute exponential backoff delay with ±50% jitter.
|
|
22224
|
-
*
|
|
22225
|
-
* @param attempt - Zero-indexed attempt number (0 = first retry, 1 = second, etc.)
|
|
22226
|
-
* @returns Delay in milliseconds, floored at 0 and capped at 60,000ms
|
|
22227
|
-
*/
|
|
22228
|
-
function computeBackoffDelay(attempt) {
|
|
22229
|
-
const rawDelay = Math.min(200 * Math.pow(2, attempt), 6e4);
|
|
22230
|
-
const jitter = rawDelay * .5 * (2 * Math.random() - 1);
|
|
22231
|
-
return Math.max(0, rawDelay + jitter);
|
|
22232
|
-
}
|
|
22233
|
-
/**
|
|
22234
22604
|
* Dispatch a node handler with exponential backoff retry on FAIL outcomes.
|
|
22235
22605
|
*
|
|
22236
22606
|
* Emits `graph:node-retried` before each retry attempt.
|
|
@@ -22289,6 +22659,13 @@ function createGraphExecutor() {
|
|
|
22289
22659
|
const checkpointManager = new CheckpointManager();
|
|
22290
22660
|
const checkpointFilePath = path.join(config.logsRoot, "checkpoint.json");
|
|
22291
22661
|
const controller = createConvergenceController();
|
|
22662
|
+
const sessionManager = new SessionBudgetManager();
|
|
22663
|
+
const pipelineManager = new PipelineBudgetManager();
|
|
22664
|
+
const plateauDetector = createPlateauDetector({
|
|
22665
|
+
...config.plateauWindow !== void 0 ? { window: config.plateauWindow } : {},
|
|
22666
|
+
...config.plateauThreshold !== void 0 ? { threshold: config.plateauThreshold } : {}
|
|
22667
|
+
});
|
|
22668
|
+
let convergenceIteration = 0;
|
|
22292
22669
|
let completedNodes = [];
|
|
22293
22670
|
let nodeRetries = {};
|
|
22294
22671
|
let context = new GraphContext();
|
|
@@ -22328,23 +22705,64 @@ function createGraphExecutor() {
|
|
|
22328
22705
|
}
|
|
22329
22706
|
} else currentNode = graph.startNode();
|
|
22330
22707
|
while (true) {
|
|
22708
|
+
const sessionResult = sessionManager.checkBudget((config.wallClockCapMs ?? 0) / 1e3);
|
|
22709
|
+
if (!sessionResult.allowed) {
|
|
22710
|
+
config.eventBus?.emit("convergence:budget-exhausted", {
|
|
22711
|
+
runId: config.runId,
|
|
22712
|
+
level: "session",
|
|
22713
|
+
reason: sessionResult.reason
|
|
22714
|
+
});
|
|
22715
|
+
return {
|
|
22716
|
+
status: "FAIL",
|
|
22717
|
+
failureReason: `Session budget exceeded: ${sessionResult.reason}`
|
|
22718
|
+
};
|
|
22719
|
+
}
|
|
22720
|
+
const pipelineResult = pipelineManager.checkBudget(config.pipelineBudgetCapUsd ?? 0);
|
|
22721
|
+
if (!pipelineResult.allowed) {
|
|
22722
|
+
config.eventBus?.emit("convergence:budget-exhausted", {
|
|
22723
|
+
runId: config.runId,
|
|
22724
|
+
level: "pipeline",
|
|
22725
|
+
reason: pipelineResult.reason
|
|
22726
|
+
});
|
|
22727
|
+
return {
|
|
22728
|
+
status: "FAIL",
|
|
22729
|
+
failureReason: `Pipeline budget exceeded: ${pipelineResult.reason}`
|
|
22730
|
+
};
|
|
22731
|
+
}
|
|
22331
22732
|
const exitNode = graph.exitNode();
|
|
22332
22733
|
if (currentNode.id === exitNode.id) {
|
|
22333
|
-
const gateResult = controller.
|
|
22734
|
+
const gateResult = controller.checkGoalGates(graph, config.runId, config.eventBus);
|
|
22334
22735
|
if (!gateResult.satisfied) {
|
|
22335
|
-
const failingNodeId = gateResult.
|
|
22736
|
+
const failingNodeId = gateResult.failedGates[0];
|
|
22336
22737
|
const failingGateNode = graph.nodes.get(failingNodeId);
|
|
22337
|
-
const
|
|
22338
|
-
if (
|
|
22339
|
-
const retryNode = graph.nodes.get(retryTarget);
|
|
22340
|
-
if (!retryNode) throw new Error(`Retry target node "${retryTarget}" not found in graph`);
|
|
22341
|
-
currentNode = retryNode;
|
|
22342
|
-
continue;
|
|
22343
|
-
}
|
|
22344
|
-
return {
|
|
22738
|
+
const retryTargetId = failingGateNode ? controller.resolveRetryTarget(failingGateNode, graph) : null;
|
|
22739
|
+
if (!retryTargetId) return {
|
|
22345
22740
|
status: "FAIL",
|
|
22346
22741
|
failureReason: "Goal gate failed: no retry target"
|
|
22347
22742
|
};
|
|
22743
|
+
const retryNode = graph.nodes.get(retryTargetId);
|
|
22744
|
+
if (!retryNode) throw new Error(`Retry target node "${retryTargetId}" not found in graph`);
|
|
22745
|
+
convergenceIteration++;
|
|
22746
|
+
const satisfactionScore = context.getNumber("satisfaction_score", 0);
|
|
22747
|
+
plateauDetector.recordScore(convergenceIteration, satisfactionScore);
|
|
22748
|
+
const plateauResult = checkPlateauAndEmit(plateauDetector, {
|
|
22749
|
+
runId: config.runId,
|
|
22750
|
+
nodeId: retryTargetId,
|
|
22751
|
+
...config.eventBus ? { eventBus: config.eventBus } : {}
|
|
22752
|
+
});
|
|
22753
|
+
if (plateauResult.plateaued) return {
|
|
22754
|
+
status: "FAIL",
|
|
22755
|
+
failureReason: `Convergence plateau detected after ${convergenceIteration} iteration(s): scores plateaued at [${plateauResult.scores.join(", ")}]`
|
|
22756
|
+
};
|
|
22757
|
+
const remediation = buildRemediationContext({
|
|
22758
|
+
previousFailureReason: `Goal gate unsatisfied: ${gateResult.failedGates.join(", ")}`,
|
|
22759
|
+
iterationCount: convergenceIteration,
|
|
22760
|
+
satisfactionScoreHistory: plateauResult.scores
|
|
22761
|
+
});
|
|
22762
|
+
injectRemediationContext(context, remediation);
|
|
22763
|
+
skipCycleCheck = true;
|
|
22764
|
+
currentNode = retryNode;
|
|
22765
|
+
continue;
|
|
22348
22766
|
}
|
|
22349
22767
|
return { status: "SUCCESS" };
|
|
22350
22768
|
}
|
|
@@ -22441,6 +22859,8 @@ function createGraphExecutor() {
|
|
|
22441
22859
|
controller.recordOutcome(nodeToDispatch.id, controllerStatus);
|
|
22442
22860
|
}
|
|
22443
22861
|
if (outcome.contextUpdates) for (const [key, value] of Object.entries(outcome.contextUpdates)) context.set(key, value);
|
|
22862
|
+
const nodeCost = context.getNumber("factory.lastNodeCostUsd", 0);
|
|
22863
|
+
if (nodeCost > 0) pipelineManager.addCost(nodeCost);
|
|
22444
22864
|
if (!skipCompletedPush) completedNodes.push(currentNode.id);
|
|
22445
22865
|
skipCompletedPush = false;
|
|
22446
22866
|
await checkpointManager.save(config.logsRoot, {
|
|
@@ -22455,7 +22875,7 @@ function createGraphExecutor() {
|
|
|
22455
22875
|
checkpointPath: checkpointFilePath
|
|
22456
22876
|
});
|
|
22457
22877
|
if (outcome.status === "FAIL") {
|
|
22458
|
-
const retryTarget =
|
|
22878
|
+
const retryTarget = controller.resolveRetryTarget(currentNode, graph);
|
|
22459
22879
|
if (retryTarget) {
|
|
22460
22880
|
const retryNode = graph.nodes.get(retryTarget);
|
|
22461
22881
|
if (!retryNode) throw new Error(`Retry target node "${retryTarget}" not found in graph`);
|
|
@@ -27851,13 +28271,19 @@ function registerFactoryCommand(program) {
|
|
|
27851
28271
|
const logsRoot = path.join(projectDir, ".substrate", "runs", runId);
|
|
27852
28272
|
const stateManager = new RunStateManager({ runDir: logsRoot });
|
|
27853
28273
|
await stateManager.initRun(dotSource);
|
|
28274
|
+
/** wallClockCapMs: FactoryConfig.wall_clock_cap_seconds × 1000 (story 45-10) */
|
|
28275
|
+
const factoryConfig = await loadFactoryConfig(projectDir, opts.config);
|
|
27854
28276
|
const executor = createGraphExecutor();
|
|
27855
28277
|
await executor.run(graph, {
|
|
27856
28278
|
runId,
|
|
27857
28279
|
logsRoot,
|
|
27858
28280
|
handlerRegistry: createDefaultRegistry(),
|
|
27859
28281
|
eventBus,
|
|
27860
|
-
dotSource
|
|
28282
|
+
dotSource,
|
|
28283
|
+
wallClockCapMs: (factoryConfig.factory?.wall_clock_cap_seconds ?? 0) * 1e3,
|
|
28284
|
+
pipelineBudgetCapUsd: factoryConfig.factory?.budget_cap_usd ?? 0,
|
|
28285
|
+
plateauWindow: factoryConfig.factory?.plateau_window ?? 3,
|
|
28286
|
+
plateauThreshold: factoryConfig.factory?.plateau_threshold ?? .05
|
|
27861
28287
|
});
|
|
27862
28288
|
} catch (err) {
|
|
27863
28289
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -29340,4 +29766,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
29340
29766
|
|
|
29341
29767
|
//#endregion
|
|
29342
29768
|
export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, normalizeGraphSummaryToStatus, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
|
|
29343
|
-
//# sourceMappingURL=run-
|
|
29769
|
+
//# sourceMappingURL=run-CUMPhuVq.js.map
|
|
@@ -2,7 +2,7 @@ import "./health-DswaC1q5.js";
|
|
|
2
2
|
import "./logger-KeHncl-f.js";
|
|
3
3
|
import "./helpers-CElYrONe.js";
|
|
4
4
|
import "./dist-CLvAwmT7.js";
|
|
5
|
-
import { normalizeGraphSummaryToStatus, registerRunCommand, runRunAction } from "./run-
|
|
5
|
+
import { normalizeGraphSummaryToStatus, registerRunCommand, runRunAction } from "./run-CUMPhuVq.js";
|
|
6
6
|
import "./routing-CcBOCuC9.js";
|
|
7
7
|
import "./decisions-C0pz9Clx.js";
|
|
8
8
|
|