@nathapp/nax 0.74.0 → 0.75.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/nax.js +847 -587
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -16949,9 +16949,6 @@ var init_schemas_execution = __esm(() => {
|
|
|
16949
16949
|
})
|
|
16950
16950
|
});
|
|
16951
16951
|
QualityConfigSchema = exports_external.object({
|
|
16952
|
-
requireTypecheck: exports_external.boolean().default(true),
|
|
16953
|
-
requireLint: exports_external.boolean().default(true),
|
|
16954
|
-
requireTests: exports_external.boolean().default(true),
|
|
16955
16952
|
scopeTestThreshold: exports_external.number().int().min(0).default(10),
|
|
16956
16953
|
commands: exports_external.object({
|
|
16957
16954
|
typecheck: exports_external.string().optional(),
|
|
@@ -17047,7 +17044,7 @@ var init_schemas_execution = __esm(() => {
|
|
|
17047
17044
|
});
|
|
17048
17045
|
|
|
17049
17046
|
// src/config/schemas-infra.ts
|
|
17050
|
-
var PlanConfigSchema, AcceptanceFixConfigSchema, AcceptanceConfigSchema, LlmRoutingConfigSchema, AgentRoutingProfileSchema, AgentRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PromptAuditConfigSchema, AgentFallbackConfigSchema, DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG, AgentIdleWatchdogConfigSchema, AgentAcpConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, CuratorThresholdsSchema, CuratorConfigSchema;
|
|
17047
|
+
var PlanConfigSchema, AcceptanceFixConfigSchema, AcceptanceConfigSchema, LlmRoutingConfigSchema, AgentRoutingProfileSchema, AgentRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PromptAuditConfigSchema, AgentFallbackConfigSchema, DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG, AgentIdleWatchdogConfigSchema, AgentAcpConfigSchema, AgentTimeoutRetryConfigSchema, DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, CuratorThresholdsSchema, CuratorConfigSchema;
|
|
17051
17048
|
var init_schemas_infra = __esm(() => {
|
|
17052
17049
|
init_zod();
|
|
17053
17050
|
init_schemas_model();
|
|
@@ -17225,6 +17222,14 @@ var init_schemas_infra = __esm(() => {
|
|
|
17225
17222
|
AgentAcpConfigSchema = exports_external.object({
|
|
17226
17223
|
promptRetries: exports_external.number().int().min(0).max(5).default(0)
|
|
17227
17224
|
});
|
|
17225
|
+
AgentTimeoutRetryConfigSchema = exports_external.object({
|
|
17226
|
+
maxAttempts: exports_external.number().int().nonnegative().default(1),
|
|
17227
|
+
budgetMultiplier: exports_external.number().gt(0).max(1).default(0.5)
|
|
17228
|
+
});
|
|
17229
|
+
DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG = {
|
|
17230
|
+
maxAttempts: 1,
|
|
17231
|
+
budgetMultiplier: 0.5
|
|
17232
|
+
};
|
|
17228
17233
|
AgentConfigSchema = exports_external.object({
|
|
17229
17234
|
protocol: exports_external.literal("acp").default("acp"),
|
|
17230
17235
|
default: exports_external.string().trim().min(1, "agent.default must be non-empty").default("claude"),
|
|
@@ -17238,7 +17243,8 @@ var init_schemas_infra = __esm(() => {
|
|
|
17238
17243
|
rebuildContext: true
|
|
17239
17244
|
}),
|
|
17240
17245
|
acp: AgentAcpConfigSchema.default({ promptRetries: 0 }),
|
|
17241
|
-
idleWatchdog: AgentIdleWatchdogConfigSchema.default(DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG)
|
|
17246
|
+
idleWatchdog: AgentIdleWatchdogConfigSchema.default(DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG),
|
|
17247
|
+
timeoutRetry: AgentTimeoutRetryConfigSchema.default(DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG)
|
|
17242
17248
|
});
|
|
17243
17249
|
PrecheckConfigSchema = exports_external.object({
|
|
17244
17250
|
storySizeGate: StorySizeGateConfigSchema
|
|
@@ -17525,9 +17531,6 @@ var init_schemas3 = __esm(() => {
|
|
|
17525
17531
|
}
|
|
17526
17532
|
}),
|
|
17527
17533
|
quality: QualityConfigSchema.default({
|
|
17528
|
-
requireTypecheck: true,
|
|
17529
|
-
requireLint: true,
|
|
17530
|
-
requireTests: true,
|
|
17531
17534
|
scopeTestThreshold: 10,
|
|
17532
17535
|
commands: {},
|
|
17533
17536
|
lintOutput: {
|
|
@@ -17714,7 +17717,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17714
17717
|
promptAudit: { enabled: false },
|
|
17715
17718
|
fallback: { enabled: false, map: {}, maxHopsPerStory: 2, onQualityFailure: false, rebuildContext: true },
|
|
17716
17719
|
acp: { promptRetries: 0 },
|
|
17717
|
-
idleWatchdog: DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG
|
|
17720
|
+
idleWatchdog: DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG,
|
|
17721
|
+
timeoutRetry: DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG
|
|
17718
17722
|
}),
|
|
17719
17723
|
precheck: PrecheckConfigSchema.optional().default({
|
|
17720
17724
|
storySizeGate: {
|
|
@@ -18328,15 +18332,22 @@ function redactString(value) {
|
|
|
18328
18332
|
}
|
|
18329
18333
|
return out;
|
|
18330
18334
|
}
|
|
18331
|
-
function
|
|
18335
|
+
function redactEntry(entry) {
|
|
18336
|
+
return {
|
|
18337
|
+
...entry,
|
|
18338
|
+
message: redactString(entry.message),
|
|
18339
|
+
...entry.data ? { data: redactValue(entry.data) } : {}
|
|
18340
|
+
};
|
|
18341
|
+
}
|
|
18342
|
+
function redactValue(input) {
|
|
18332
18343
|
if (typeof input === "string")
|
|
18333
18344
|
return redactString(input);
|
|
18334
18345
|
if (Array.isArray(input))
|
|
18335
|
-
return input.map(
|
|
18346
|
+
return input.map(redactValue);
|
|
18336
18347
|
if (input !== null && typeof input === "object") {
|
|
18337
18348
|
const out = {};
|
|
18338
18349
|
for (const [key, value] of Object.entries(input)) {
|
|
18339
|
-
out[key] = SECRET_KEY_PATTERN.test(key) ? REDACTED :
|
|
18350
|
+
out[key] = SECRET_KEY_PATTERN.test(key) ? REDACTED : redactValue(value);
|
|
18340
18351
|
}
|
|
18341
18352
|
return out;
|
|
18342
18353
|
}
|
|
@@ -18367,6 +18378,7 @@ class Logger {
|
|
|
18367
18378
|
formatterMode;
|
|
18368
18379
|
suppressConsole;
|
|
18369
18380
|
writeQueueTail = Promise.resolve();
|
|
18381
|
+
pendingLines = [];
|
|
18370
18382
|
constructor(options) {
|
|
18371
18383
|
this.level = options.level;
|
|
18372
18384
|
this.filePath = options.filePath;
|
|
@@ -18401,7 +18413,7 @@ class Logger {
|
|
|
18401
18413
|
const { sessionRole: _omit, ...rest } = data;
|
|
18402
18414
|
strippedData = Object.keys(rest).length > 0 ? rest : undefined;
|
|
18403
18415
|
}
|
|
18404
|
-
const
|
|
18416
|
+
const rawEntry = {
|
|
18405
18417
|
timestamp: new Date().toISOString(),
|
|
18406
18418
|
level,
|
|
18407
18419
|
stage,
|
|
@@ -18410,7 +18422,11 @@ class Logger {
|
|
|
18410
18422
|
...sessionRole && { sessionRole },
|
|
18411
18423
|
...strippedData && { data: strippedData }
|
|
18412
18424
|
};
|
|
18413
|
-
|
|
18425
|
+
const consoleEnabled = this.shouldLog(level) && !this.suppressConsole;
|
|
18426
|
+
if (!consoleEnabled && !this.filePath)
|
|
18427
|
+
return;
|
|
18428
|
+
const entry = redactEntry(rawEntry);
|
|
18429
|
+
if (consoleEnabled) {
|
|
18414
18430
|
let consoleOutput = null;
|
|
18415
18431
|
if (this.formatterMode) {
|
|
18416
18432
|
const formatterOptions = {
|
|
@@ -18451,14 +18467,24 @@ ${JSON.stringify(entry.data, null, 2)}`;
|
|
|
18451
18467
|
writeToFile(entry) {
|
|
18452
18468
|
if (!this.filePath)
|
|
18453
18469
|
return;
|
|
18454
|
-
const safeEntry = entry.data ? { ...entry, data: redactSecrets(entry.data) } : entry;
|
|
18455
|
-
const line = `${formatJsonl(safeEntry)}
|
|
18456
|
-
`;
|
|
18457
18470
|
const filePath = this.filePath;
|
|
18458
|
-
this.
|
|
18459
|
-
process.stderr.write(`[logger] Failed to write to log file: ${error48}
|
|
18471
|
+
this.pendingLines.push(`${formatJsonl(entry)}
|
|
18460
18472
|
`);
|
|
18461
|
-
|
|
18473
|
+
this.writeQueueTail = this.writeQueueTail.then(async () => {
|
|
18474
|
+
while (this.pendingLines.length > 0) {
|
|
18475
|
+
let bytes = 0;
|
|
18476
|
+
let count = 0;
|
|
18477
|
+
while (count < this.pendingLines.length && bytes < MAX_BATCH_BYTES) {
|
|
18478
|
+
bytes += this.pendingLines[count].length;
|
|
18479
|
+
count++;
|
|
18480
|
+
}
|
|
18481
|
+
const batch = this.pendingLines.splice(0, count).join("");
|
|
18482
|
+
await appendFile(filePath, batch).catch((error48) => {
|
|
18483
|
+
process.stderr.write(`[logger] Failed to write to log file: ${error48}
|
|
18484
|
+
`);
|
|
18485
|
+
});
|
|
18486
|
+
}
|
|
18487
|
+
});
|
|
18462
18488
|
}
|
|
18463
18489
|
async flush() {
|
|
18464
18490
|
await this.writeQueueTail;
|
|
@@ -18511,7 +18537,7 @@ function resetLogger() {
|
|
|
18511
18537
|
}
|
|
18512
18538
|
instance = null;
|
|
18513
18539
|
}
|
|
18514
|
-
var LOG_LEVEL_PRIORITY, instance = null, noopLogger;
|
|
18540
|
+
var LOG_LEVEL_PRIORITY, MAX_BATCH_BYTES, instance = null, noopLogger;
|
|
18515
18541
|
var init_logger = __esm(() => {
|
|
18516
18542
|
init_log_format();
|
|
18517
18543
|
init_formatters();
|
|
@@ -18523,6 +18549,7 @@ var init_logger = __esm(() => {
|
|
|
18523
18549
|
info: 2,
|
|
18524
18550
|
debug: 3
|
|
18525
18551
|
};
|
|
18552
|
+
MAX_BATCH_BYTES = 64 * 1024;
|
|
18526
18553
|
noopLogger = new Logger({ level: "silent", useChalk: false, headless: false });
|
|
18527
18554
|
});
|
|
18528
18555
|
|
|
@@ -18567,6 +18594,121 @@ var init_json_file = __esm(() => {
|
|
|
18567
18594
|
init_logger2();
|
|
18568
18595
|
});
|
|
18569
18596
|
|
|
18597
|
+
// src/config/config-guards.ts
|
|
18598
|
+
function rejectLegacyAgentKeys(conf) {
|
|
18599
|
+
const legacyKeys = [];
|
|
18600
|
+
const migrationHints = [];
|
|
18601
|
+
const autoMode = conf.autoMode;
|
|
18602
|
+
if (autoMode && typeof autoMode === "object") {
|
|
18603
|
+
if ("defaultAgent" in autoMode) {
|
|
18604
|
+
legacyKeys.push("autoMode.defaultAgent");
|
|
18605
|
+
migrationHints.push("- Move `autoMode.defaultAgent` \u2192 `agent.default`");
|
|
18606
|
+
}
|
|
18607
|
+
if ("fallbackOrder" in autoMode) {
|
|
18608
|
+
legacyKeys.push("autoMode.fallbackOrder");
|
|
18609
|
+
migrationHints.push("- Move `autoMode.fallbackOrder: [primary, ...]` \u2192 `agent.fallback.map: { <primary>: [<rest>] }` and `agent.fallback.enabled: true`");
|
|
18610
|
+
}
|
|
18611
|
+
}
|
|
18612
|
+
const context = conf.context;
|
|
18613
|
+
const contextV2 = context?.v2;
|
|
18614
|
+
if (contextV2 && typeof contextV2 === "object" && "fallback" in contextV2) {
|
|
18615
|
+
legacyKeys.push("context.v2.fallback");
|
|
18616
|
+
migrationHints.push("- Move `context.v2.fallback` \u2192 `agent.fallback` (see ADR-012 Phase 6)");
|
|
18617
|
+
}
|
|
18618
|
+
if (legacyKeys.length === 0)
|
|
18619
|
+
return;
|
|
18620
|
+
const message = [
|
|
18621
|
+
`Invalid configuration \u2014 legacy agent keys detected: ${legacyKeys.join(", ")}.`,
|
|
18622
|
+
"These were removed in ADR-012 Phase 6. Migrate to the canonical `agent.*` shape:",
|
|
18623
|
+
...migrationHints,
|
|
18624
|
+
"See docs/adr/ADR-012-agent-manager-ownership.md for the full migration guide."
|
|
18625
|
+
].join(`
|
|
18626
|
+
`);
|
|
18627
|
+
throw new NaxError(message, "CONFIG_LEGACY_AGENT_KEYS", { stage: "config", legacyKeys });
|
|
18628
|
+
}
|
|
18629
|
+
function rejectLegacyRectificationKeys(conf) {
|
|
18630
|
+
const legacyKeys = [];
|
|
18631
|
+
const migrationHints = [];
|
|
18632
|
+
const quality = conf.quality;
|
|
18633
|
+
const autofix = quality?.autofix;
|
|
18634
|
+
if (autofix && typeof autofix === "object") {
|
|
18635
|
+
if ("maxTotalAttempts" in autofix) {
|
|
18636
|
+
legacyKeys.push("quality.autofix.maxTotalAttempts");
|
|
18637
|
+
migrationHints.push("- Move `quality.autofix.maxTotalAttempts` \u2192 `execution.rectification.maxAttemptsTotal`");
|
|
18638
|
+
}
|
|
18639
|
+
if ("rethinkAtAttempt" in autofix) {
|
|
18640
|
+
legacyKeys.push("quality.autofix.rethinkAtAttempt");
|
|
18641
|
+
migrationHints.push("- Move `quality.autofix.rethinkAtAttempt` \u2192 `execution.rectification.rethinkAtAttempt`");
|
|
18642
|
+
}
|
|
18643
|
+
if ("urgencyAtAttempt" in autofix) {
|
|
18644
|
+
legacyKeys.push("quality.autofix.urgencyAtAttempt");
|
|
18645
|
+
migrationHints.push("- Move `quality.autofix.urgencyAtAttempt` \u2192 `execution.rectification.urgencyAtAttempt`");
|
|
18646
|
+
}
|
|
18647
|
+
}
|
|
18648
|
+
const execution = conf.execution;
|
|
18649
|
+
const rectification = execution?.rectification;
|
|
18650
|
+
if (rectification && typeof rectification === "object" && "maxRetries" in rectification) {
|
|
18651
|
+
legacyKeys.push("execution.rectification.maxRetries");
|
|
18652
|
+
migrationHints.push("- Rename `execution.rectification.maxRetries` \u2192 `execution.rectification.maxAttemptsTotal` (default changed from 2 to 12)");
|
|
18653
|
+
}
|
|
18654
|
+
const regressionGate = execution?.regressionGate;
|
|
18655
|
+
if (regressionGate && typeof regressionGate === "object" && "maxRectificationAttempts" in regressionGate) {
|
|
18656
|
+
legacyKeys.push("execution.regressionGate.maxRectificationAttempts");
|
|
18657
|
+
migrationHints.push("- Remove `execution.regressionGate.maxRectificationAttempts` \u2014 the regression cycle now shares `execution.rectification.maxAttemptsTotal`");
|
|
18658
|
+
}
|
|
18659
|
+
if (legacyKeys.length === 0)
|
|
18660
|
+
return;
|
|
18661
|
+
const message = [
|
|
18662
|
+
`Invalid configuration \u2014 legacy rectification-cap keys detected: ${legacyKeys.join(", ")}.`,
|
|
18663
|
+
"These were consolidated under `execution.rectification.*` so one config controls the unified",
|
|
18664
|
+
"fix cycle (semantic + adversarial + mechanical + regression). Migrate as follows:",
|
|
18665
|
+
...migrationHints
|
|
18666
|
+
].join(`
|
|
18667
|
+
`);
|
|
18668
|
+
throw new NaxError(message, "CONFIG_LEGACY_RECTIFICATION_KEYS", { stage: "config", legacyKeys });
|
|
18669
|
+
}
|
|
18670
|
+
function rejectDeadQualityFlags(conf) {
|
|
18671
|
+
const quality = conf.quality;
|
|
18672
|
+
if (!quality || typeof quality !== "object")
|
|
18673
|
+
return;
|
|
18674
|
+
const dead = Object.entries(DEAD_QUALITY_FLAGS).filter(([flag]) => (flag in quality));
|
|
18675
|
+
if (dead.length === 0)
|
|
18676
|
+
return;
|
|
18677
|
+
const deadKeys = dead.map(([flag]) => `quality.${flag}`);
|
|
18678
|
+
const message = [
|
|
18679
|
+
`Invalid configuration \u2014 removed quality flags detected: ${deadKeys.join(", ")}.`,
|
|
18680
|
+
"These flags were never read at any gate site: typecheck, lint, and test gates fire",
|
|
18681
|
+
"whenever a command resolves, so setting one to `false` never skipped its gate.",
|
|
18682
|
+
"",
|
|
18683
|
+
"To skip a gate, remove its command from `quality.commands` instead:",
|
|
18684
|
+
...dead.map(([flag, command]) => `- Delete \`quality.${flag}\`; to disable that gate, unset \`quality.commands.${command}\``)
|
|
18685
|
+
].join(`
|
|
18686
|
+
`);
|
|
18687
|
+
throw new NaxError(message, "CONFIG_DEAD_QUALITY_FLAGS", { stage: "config", deadKeys });
|
|
18688
|
+
}
|
|
18689
|
+
function rejectUnimplementedScopedProfile(conf) {
|
|
18690
|
+
const execution = conf.execution;
|
|
18691
|
+
if (execution?.permissionProfile !== "scoped")
|
|
18692
|
+
return;
|
|
18693
|
+
const message = [
|
|
18694
|
+
'Invalid configuration \u2014 execution.permissionProfile: "scoped" is not yet implemented.',
|
|
18695
|
+
"The scoped (per-stage tool allowlist) profile is tracked by GitHub #374 and would",
|
|
18696
|
+
'otherwise silently run as "safe", giving you weaker permissions than intended.',
|
|
18697
|
+
'Use "unrestricted" or "safe" for now.'
|
|
18698
|
+
].join(`
|
|
18699
|
+
`);
|
|
18700
|
+
throw new NaxError(message, "CONFIG_SCOPED_PROFILE_UNIMPLEMENTED", { stage: "config" });
|
|
18701
|
+
}
|
|
18702
|
+
var DEAD_QUALITY_FLAGS;
|
|
18703
|
+
var init_config_guards = __esm(() => {
|
|
18704
|
+
init_errors();
|
|
18705
|
+
DEAD_QUALITY_FLAGS = {
|
|
18706
|
+
requireTypecheck: "typecheck",
|
|
18707
|
+
requireLint: "lint",
|
|
18708
|
+
requireTests: "test"
|
|
18709
|
+
};
|
|
18710
|
+
});
|
|
18711
|
+
|
|
18570
18712
|
// src/config/merge.ts
|
|
18571
18713
|
function mergePackageConfig(root, packageOverride) {
|
|
18572
18714
|
const hasAnyMergeableField = packageOverride.agent !== undefined || packageOverride.models !== undefined || packageOverride.routing !== undefined || packageOverride.execution !== undefined || packageOverride.review !== undefined || packageOverride.acceptance !== undefined || packageOverride.quality !== undefined || packageOverride.context !== undefined || packageOverride.project !== undefined;
|
|
@@ -18649,9 +18791,6 @@ function mergePackageConfig(root, packageOverride) {
|
|
|
18649
18791
|
},
|
|
18650
18792
|
quality: {
|
|
18651
18793
|
...root.quality,
|
|
18652
|
-
requireTests: packageOverride.quality?.requireTests ?? root.quality.requireTests,
|
|
18653
|
-
requireTypecheck: packageOverride.quality?.requireTypecheck ?? root.quality.requireTypecheck,
|
|
18654
|
-
requireLint: packageOverride.quality?.requireLint ?? root.quality.requireLint,
|
|
18655
18794
|
commands: {
|
|
18656
18795
|
...root.quality.commands,
|
|
18657
18796
|
...packageOverride.quality?.commands
|
|
@@ -19102,91 +19241,6 @@ function applyRemovedStrategyCompat(conf) {
|
|
|
19102
19241
|
}
|
|
19103
19242
|
return conf;
|
|
19104
19243
|
}
|
|
19105
|
-
function rejectLegacyAgentKeys(conf) {
|
|
19106
|
-
const legacyKeys = [];
|
|
19107
|
-
const migrationHints = [];
|
|
19108
|
-
const autoMode = conf.autoMode;
|
|
19109
|
-
if (autoMode && typeof autoMode === "object") {
|
|
19110
|
-
if ("defaultAgent" in autoMode) {
|
|
19111
|
-
legacyKeys.push("autoMode.defaultAgent");
|
|
19112
|
-
migrationHints.push("- Move `autoMode.defaultAgent` \u2192 `agent.default`");
|
|
19113
|
-
}
|
|
19114
|
-
if ("fallbackOrder" in autoMode) {
|
|
19115
|
-
legacyKeys.push("autoMode.fallbackOrder");
|
|
19116
|
-
migrationHints.push("- Move `autoMode.fallbackOrder: [primary, ...]` \u2192 `agent.fallback.map: { <primary>: [<rest>] }` and `agent.fallback.enabled: true`");
|
|
19117
|
-
}
|
|
19118
|
-
}
|
|
19119
|
-
const context = conf.context;
|
|
19120
|
-
const contextV2 = context?.v2;
|
|
19121
|
-
if (contextV2 && typeof contextV2 === "object" && "fallback" in contextV2) {
|
|
19122
|
-
legacyKeys.push("context.v2.fallback");
|
|
19123
|
-
migrationHints.push("- Move `context.v2.fallback` \u2192 `agent.fallback` (see ADR-012 Phase 6)");
|
|
19124
|
-
}
|
|
19125
|
-
if (legacyKeys.length === 0)
|
|
19126
|
-
return;
|
|
19127
|
-
const message = [
|
|
19128
|
-
`Invalid configuration \u2014 legacy agent keys detected: ${legacyKeys.join(", ")}.`,
|
|
19129
|
-
"These were removed in ADR-012 Phase 6. Migrate to the canonical `agent.*` shape:",
|
|
19130
|
-
...migrationHints,
|
|
19131
|
-
"See docs/adr/ADR-012-agent-manager-ownership.md for the full migration guide."
|
|
19132
|
-
].join(`
|
|
19133
|
-
`);
|
|
19134
|
-
throw new NaxError(message, "CONFIG_LEGACY_AGENT_KEYS", { stage: "config", legacyKeys });
|
|
19135
|
-
}
|
|
19136
|
-
function rejectLegacyRectificationKeys(conf) {
|
|
19137
|
-
const legacyKeys = [];
|
|
19138
|
-
const migrationHints = [];
|
|
19139
|
-
const quality = conf.quality;
|
|
19140
|
-
const autofix = quality?.autofix;
|
|
19141
|
-
if (autofix && typeof autofix === "object") {
|
|
19142
|
-
if ("maxTotalAttempts" in autofix) {
|
|
19143
|
-
legacyKeys.push("quality.autofix.maxTotalAttempts");
|
|
19144
|
-
migrationHints.push("- Move `quality.autofix.maxTotalAttempts` \u2192 `execution.rectification.maxAttemptsTotal`");
|
|
19145
|
-
}
|
|
19146
|
-
if ("rethinkAtAttempt" in autofix) {
|
|
19147
|
-
legacyKeys.push("quality.autofix.rethinkAtAttempt");
|
|
19148
|
-
migrationHints.push("- Move `quality.autofix.rethinkAtAttempt` \u2192 `execution.rectification.rethinkAtAttempt`");
|
|
19149
|
-
}
|
|
19150
|
-
if ("urgencyAtAttempt" in autofix) {
|
|
19151
|
-
legacyKeys.push("quality.autofix.urgencyAtAttempt");
|
|
19152
|
-
migrationHints.push("- Move `quality.autofix.urgencyAtAttempt` \u2192 `execution.rectification.urgencyAtAttempt`");
|
|
19153
|
-
}
|
|
19154
|
-
}
|
|
19155
|
-
const execution = conf.execution;
|
|
19156
|
-
const rectification = execution?.rectification;
|
|
19157
|
-
if (rectification && typeof rectification === "object" && "maxRetries" in rectification) {
|
|
19158
|
-
legacyKeys.push("execution.rectification.maxRetries");
|
|
19159
|
-
migrationHints.push("- Rename `execution.rectification.maxRetries` \u2192 `execution.rectification.maxAttemptsTotal` (default changed from 2 to 12)");
|
|
19160
|
-
}
|
|
19161
|
-
const regressionGate = execution?.regressionGate;
|
|
19162
|
-
if (regressionGate && typeof regressionGate === "object" && "maxRectificationAttempts" in regressionGate) {
|
|
19163
|
-
legacyKeys.push("execution.regressionGate.maxRectificationAttempts");
|
|
19164
|
-
migrationHints.push("- Remove `execution.regressionGate.maxRectificationAttempts` \u2014 the regression cycle now shares `execution.rectification.maxAttemptsTotal`");
|
|
19165
|
-
}
|
|
19166
|
-
if (legacyKeys.length === 0)
|
|
19167
|
-
return;
|
|
19168
|
-
const message = [
|
|
19169
|
-
`Invalid configuration \u2014 legacy rectification-cap keys detected: ${legacyKeys.join(", ")}.`,
|
|
19170
|
-
"These were consolidated under `execution.rectification.*` so one config controls the unified",
|
|
19171
|
-
"fix cycle (semantic + adversarial + mechanical + regression). Migrate as follows:",
|
|
19172
|
-
...migrationHints
|
|
19173
|
-
].join(`
|
|
19174
|
-
`);
|
|
19175
|
-
throw new NaxError(message, "CONFIG_LEGACY_RECTIFICATION_KEYS", { stage: "config", legacyKeys });
|
|
19176
|
-
}
|
|
19177
|
-
function rejectUnimplementedScopedProfile(conf) {
|
|
19178
|
-
const execution = conf.execution;
|
|
19179
|
-
if (execution?.permissionProfile !== "scoped")
|
|
19180
|
-
return;
|
|
19181
|
-
const message = [
|
|
19182
|
-
'Invalid configuration \u2014 execution.permissionProfile: "scoped" is not yet implemented.',
|
|
19183
|
-
"The scoped (per-stage tool allowlist) profile is tracked by GitHub #374 and would",
|
|
19184
|
-
'otherwise silently run as "safe", giving you weaker permissions than intended.',
|
|
19185
|
-
'Use "unrestricted" or "safe" for now.'
|
|
19186
|
-
].join(`
|
|
19187
|
-
`);
|
|
19188
|
-
throw new NaxError(message, "CONFIG_SCOPED_PROFILE_UNIMPLEMENTED", { stage: "config" });
|
|
19189
|
-
}
|
|
19190
19244
|
function applyBatchModeCompat(conf) {
|
|
19191
19245
|
const routing = conf.routing;
|
|
19192
19246
|
const llm = routing?.llm;
|
|
@@ -19296,6 +19350,7 @@ async function loadConfig(startDir, cliOverrides) {
|
|
|
19296
19350
|
}
|
|
19297
19351
|
rejectLegacyAgentKeys(rawConfig);
|
|
19298
19352
|
rejectLegacyRectificationKeys(rawConfig);
|
|
19353
|
+
rejectDeadQualityFlags(rawConfig);
|
|
19299
19354
|
rejectUnimplementedScopedProfile(rawConfig);
|
|
19300
19355
|
const result = NaxConfigSchema.safeParse(rawConfig);
|
|
19301
19356
|
if (!result.success) {
|
|
@@ -19366,6 +19421,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
|
|
|
19366
19421
|
rawMerged.profileChain = packageChain;
|
|
19367
19422
|
rejectLegacyAgentKeys(rawMerged);
|
|
19368
19423
|
rejectLegacyRectificationKeys(rawMerged);
|
|
19424
|
+
rejectDeadQualityFlags(rawMerged);
|
|
19369
19425
|
rejectUnimplementedScopedProfile(rawMerged);
|
|
19370
19426
|
const result = NaxConfigSchema.safeParse(rawMerged);
|
|
19371
19427
|
if (!result.success) {
|
|
@@ -19386,6 +19442,7 @@ var init_loader = __esm(() => {
|
|
|
19386
19442
|
init_errors();
|
|
19387
19443
|
init_logger2();
|
|
19388
19444
|
init_json_file();
|
|
19445
|
+
init_config_guards();
|
|
19389
19446
|
init_path_security();
|
|
19390
19447
|
init_paths();
|
|
19391
19448
|
init_profile();
|
|
@@ -19679,7 +19736,7 @@ GOOD (write ACs like these):
|
|
|
19679
19736
|
|
|
19680
19737
|
When a spec is provided, these rules govern acceptance criteria generation:
|
|
19681
19738
|
|
|
19682
|
-
1. **Preserve spec ACs.** Every acceptance criterion stated in the spec must appear in \`acceptanceCriteria\`. Never silently drop a spec AC. ACs
|
|
19739
|
+
1. **Preserve spec ACs.** Every acceptance criterion stated in the spec must appear in \`acceptanceCriteria\`. Never silently drop a spec AC. ACs may be lightly rephrased for testability, but must retain the same assertion and concrete identifiers. An AC carrying a deprecated \`[grep]\`/\`[file]\`/\`[verbatim]\` tag describes a file-content check rather than a runtime behaviour: rewrite it as the behaviour that check was meant to prove, and drop the tag.
|
|
19683
19740
|
2. **Do not invent spec ACs.** If you identify useful behavioral edge cases or negative paths that the spec did not explicitly list, place them in \`suggestedCriteria\` (a string array on the same story object) \u2014 never in \`acceptanceCriteria\`. These go through a separate hardening pass.
|
|
19684
19741
|
3. **Respect story scope.** Each story's criteria must only cover what the spec says for that story. Do not assign criteria that belong to a different story's scope (wrong feature area, wrong file, wrong dependency chain).
|
|
19685
19742
|
4. **\`suggestedCriteria\` format.** Each element must be a plain behavioral assertion \u2014 an observable output, return value, state change, or error condition that a test can assert. Never include implementation details (imports, internal structure), design suggestions, or vague descriptions.
|
|
@@ -19850,6 +19907,7 @@ __export(exports_config, {
|
|
|
19850
19907
|
DebateConfigSchema: () => DebateConfigSchema,
|
|
19851
19908
|
DESCRIPTION_QUALITY_RULES: () => DESCRIPTION_QUALITY_RULES,
|
|
19852
19909
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
19910
|
+
DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG: () => DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG,
|
|
19853
19911
|
ConfiguredModelSchema: () => ConfiguredModelSchema,
|
|
19854
19912
|
COMPLEXITY_GUIDE: () => COMPLEXITY_GUIDE,
|
|
19855
19913
|
AutoRouteConfigSchema: () => AutoRouteConfigSchema,
|
|
@@ -21075,8 +21133,23 @@ function buildRunInteractionHandler(options) {
|
|
|
21075
21133
|
}
|
|
21076
21134
|
};
|
|
21077
21135
|
}
|
|
21136
|
+
function buildTurnResult(input) {
|
|
21137
|
+
const { lastResponse, totalTokenUsage, totalExactCostUsd, turnCount, interactions, timedOut, modelDef } = input;
|
|
21138
|
+
const output = timedOut ? "" : extractOutput(lastResponse);
|
|
21139
|
+
const estimatedCostUsd = totalTokenUsage.inputTokens > 0 || totalTokenUsage.outputTokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, modelDef.model) : 0;
|
|
21140
|
+
return {
|
|
21141
|
+
output,
|
|
21142
|
+
tokenUsage: totalTokenUsage,
|
|
21143
|
+
estimatedCostUsd,
|
|
21144
|
+
exactCostUsd: totalExactCostUsd,
|
|
21145
|
+
internalRoundTrips: turnCount,
|
|
21146
|
+
...interactions.length > 0 ? { interactions } : {},
|
|
21147
|
+
timedOut
|
|
21148
|
+
};
|
|
21149
|
+
}
|
|
21078
21150
|
var CONTEXT_TOOL_CALL_PATTERN;
|
|
21079
21151
|
var init_adapter_output = __esm(() => {
|
|
21152
|
+
init_cost();
|
|
21080
21153
|
CONTEXT_TOOL_CALL_PATTERN = /<nax_tool_call\s+name="([^"]+)">\s*([\s\S]*?)\s*<\/nax_tool_call>/i;
|
|
21081
21154
|
});
|
|
21082
21155
|
|
|
@@ -21435,18 +21508,15 @@ class AcpAgentAdapter {
|
|
|
21435
21508
|
if (lastResponse?.stopReason === "error") {
|
|
21436
21509
|
throw new SessionTurnError(lastResponse.cancelled ? "Agent session ended with stop reason: error (externally cancelled)" : "Agent session ended with stop reason: error", lastResponse.cancelled === true, lastResponse.retryable === true);
|
|
21437
21510
|
}
|
|
21438
|
-
|
|
21439
|
-
|
|
21440
|
-
|
|
21441
|
-
|
|
21442
|
-
|
|
21443
|
-
|
|
21444
|
-
|
|
21445
|
-
|
|
21446
|
-
|
|
21447
|
-
internalRoundTrips: turnCount,
|
|
21448
|
-
...interactions.length > 0 ? { interactions } : {}
|
|
21449
|
-
};
|
|
21511
|
+
return buildTurnResult({
|
|
21512
|
+
lastResponse,
|
|
21513
|
+
totalTokenUsage,
|
|
21514
|
+
totalExactCostUsd,
|
|
21515
|
+
turnCount,
|
|
21516
|
+
interactions,
|
|
21517
|
+
timedOut,
|
|
21518
|
+
modelDef
|
|
21519
|
+
});
|
|
21450
21520
|
}
|
|
21451
21521
|
async closeSession(handle) {
|
|
21452
21522
|
const impl = handle;
|
|
@@ -21927,6 +21997,108 @@ var init_default_strategy = __esm(() => {
|
|
|
21927
21997
|
};
|
|
21928
21998
|
});
|
|
21929
21999
|
|
|
22000
|
+
// src/agents/retry/hop-retry-policy.ts
|
|
22001
|
+
function trySameAgentRetry(result, state, deps) {
|
|
22002
|
+
const { staleRetryAttempts, timeoutRetryAttempts, adapterErrorRetries, currentRunOptions } = state;
|
|
22003
|
+
const { config: config2, requestRunOptions, signal } = deps;
|
|
22004
|
+
const isFailStale = result.adapterFailure?.outcome === "fail-stale";
|
|
22005
|
+
const maxStaleRetries = config2.agent?.idleWatchdog?.maxRetryAttempts ?? 3;
|
|
22006
|
+
if (isFailStale && result.adapterFailure?.retriable && staleRetryAttempts < maxStaleRetries) {
|
|
22007
|
+
const newAttempts = staleRetryAttempts + 1;
|
|
22008
|
+
return {
|
|
22009
|
+
outcome: "stale-retry",
|
|
22010
|
+
staleRetryAttempts: newAttempts,
|
|
22011
|
+
kind: { kind: "stale-retry", attempt: newAttempts },
|
|
22012
|
+
fallbackRecord: {
|
|
22013
|
+
outcome: result.adapterFailure?.outcome ?? "fail-stale",
|
|
22014
|
+
category: result.adapterFailure?.category ?? "availability",
|
|
22015
|
+
costUsd: result.estimatedCostUsd ?? 0,
|
|
22016
|
+
reason: result.adapterFailure?.reason
|
|
22017
|
+
}
|
|
22018
|
+
};
|
|
22019
|
+
}
|
|
22020
|
+
const isFailTimeout = result.adapterFailure?.outcome === "fail-timeout";
|
|
22021
|
+
if (isFailTimeout && result.adapterFailure?.retriable) {
|
|
22022
|
+
const timeoutConfig = extractTimeoutRetryConfig(config2);
|
|
22023
|
+
if (timeoutRetryShouldRetry(timeoutRetryAttempts, timeoutConfig)) {
|
|
22024
|
+
const newAttempts = timeoutRetryAttempts + 1;
|
|
22025
|
+
return {
|
|
22026
|
+
outcome: "timeout-retry",
|
|
22027
|
+
timeoutRetryAttempts: newAttempts,
|
|
22028
|
+
kind: { kind: "timeout-retry", attempt: newAttempts },
|
|
22029
|
+
currentRunOptions: resolveTimeoutRetryOptions(currentRunOptions, timeoutConfig, config2.execution),
|
|
22030
|
+
fallbackRecord: {
|
|
22031
|
+
outcome: result.adapterFailure?.outcome ?? "fail-timeout",
|
|
22032
|
+
category: result.adapterFailure?.category ?? "quality",
|
|
22033
|
+
costUsd: result.estimatedCostUsd ?? 0,
|
|
22034
|
+
reason: result.adapterFailure?.reason
|
|
22035
|
+
}
|
|
22036
|
+
};
|
|
22037
|
+
}
|
|
22038
|
+
}
|
|
22039
|
+
const isFailAdapterError = result.adapterFailure?.outcome === "fail-adapter-error";
|
|
22040
|
+
if (isFailAdapterError && !signal?.aborted) {
|
|
22041
|
+
const runConfig = requestRunOptions.config ?? config2;
|
|
22042
|
+
const maxAdapterRetries = result.adapterFailure?.retriable ? runConfig.execution?.sessionErrorRetryableMaxRetries ?? 3 : runConfig.execution?.sessionErrorMaxRetries ?? 1;
|
|
22043
|
+
if (adapterErrorRetries < maxAdapterRetries) {
|
|
22044
|
+
const newAttempts = adapterErrorRetries + 1;
|
|
22045
|
+
return {
|
|
22046
|
+
outcome: "adapter-error",
|
|
22047
|
+
adapterErrorRetries: newAttempts,
|
|
22048
|
+
kind: { kind: "stale-retry", attempt: newAttempts },
|
|
22049
|
+
fallbackRecord: {
|
|
22050
|
+
outcome: result.adapterFailure?.outcome ?? "fail-adapter-error",
|
|
22051
|
+
category: result.adapterFailure?.category ?? "availability",
|
|
22052
|
+
costUsd: result.estimatedCostUsd ?? 0,
|
|
22053
|
+
retriable: result.adapterFailure?.retriable ?? false,
|
|
22054
|
+
maxAttempts: maxAdapterRetries
|
|
22055
|
+
}
|
|
22056
|
+
};
|
|
22057
|
+
}
|
|
22058
|
+
}
|
|
22059
|
+
return null;
|
|
22060
|
+
}
|
|
22061
|
+
function extractTimeoutRetryConfig(config2) {
|
|
22062
|
+
const fromConfig = config2.agent?.timeoutRetry;
|
|
22063
|
+
return {
|
|
22064
|
+
maxAttempts: fromConfig?.maxAttempts ?? DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG.maxAttempts,
|
|
22065
|
+
budgetMultiplier: fromConfig?.budgetMultiplier ?? DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG.budgetMultiplier
|
|
22066
|
+
};
|
|
22067
|
+
}
|
|
22068
|
+
function resolveTimeoutRetryOptions(prev, timeoutConfig, executionConfig) {
|
|
22069
|
+
const budget = prev.timeoutSeconds ?? executionConfig?.sessionTimeoutSeconds ?? DEFAULT_CONFIG.execution.sessionTimeoutSeconds;
|
|
22070
|
+
return { ...prev, timeoutSeconds: budget * timeoutConfig.budgetMultiplier };
|
|
22071
|
+
}
|
|
22072
|
+
function timeoutRetryShouldRetry(attempts, config2) {
|
|
22073
|
+
return attempts < config2.maxAttempts;
|
|
22074
|
+
}
|
|
22075
|
+
function describeRetryLogEvent(retryDecision, storyId, agent) {
|
|
22076
|
+
const attempt = retryDecision.kind.attempt;
|
|
22077
|
+
if (retryDecision.outcome === "adapter-error") {
|
|
22078
|
+
return {
|
|
22079
|
+
recordFallback: false,
|
|
22080
|
+
level: "warn",
|
|
22081
|
+
message: "fail-adapter-error: same-agent retry with fresh session",
|
|
22082
|
+
fields: {
|
|
22083
|
+
storyId,
|
|
22084
|
+
attempt,
|
|
22085
|
+
maxAttempts: retryDecision.fallbackRecord.maxAttempts,
|
|
22086
|
+
retriable: retryDecision.fallbackRecord.retriable,
|
|
22087
|
+
agent
|
|
22088
|
+
}
|
|
22089
|
+
};
|
|
22090
|
+
}
|
|
22091
|
+
return {
|
|
22092
|
+
recordFallback: true,
|
|
22093
|
+
level: "info",
|
|
22094
|
+
message: retryDecision.outcome === "stale-retry" ? "fail-stale: immediate same-agent retry" : "fail-timeout: same-agent retry with reduced budget",
|
|
22095
|
+
fields: { storyId, attempt, agent, reason: retryDecision.fallbackRecord.reason }
|
|
22096
|
+
};
|
|
22097
|
+
}
|
|
22098
|
+
var init_hop_retry_policy = __esm(() => {
|
|
22099
|
+
init_config();
|
|
22100
|
+
});
|
|
22101
|
+
|
|
21930
22102
|
// src/agents/manager.ts
|
|
21931
22103
|
import { EventEmitter } from "events";
|
|
21932
22104
|
|
|
@@ -22033,7 +22205,7 @@ class AgentManager {
|
|
|
22033
22205
|
shouldSwap(failure, hopsSoFar, hasBundle) {
|
|
22034
22206
|
if (!failure)
|
|
22035
22207
|
return false;
|
|
22036
|
-
if (failure.outcome === "fail-aborted")
|
|
22208
|
+
if (failure.outcome === "fail-aborted" || failure.outcome === "fail-timeout")
|
|
22037
22209
|
return false;
|
|
22038
22210
|
const fallback = this._config.agent?.fallback;
|
|
22039
22211
|
if (!fallback?.enabled)
|
|
@@ -22059,8 +22231,10 @@ class AgentManager {
|
|
|
22059
22231
|
let hopsSoFar = 0;
|
|
22060
22232
|
let rateLimitRetry = 0;
|
|
22061
22233
|
let staleRetryAttempts = 0;
|
|
22234
|
+
let timeoutRetryAttempts = 0;
|
|
22062
22235
|
let adapterErrorRetries = 0;
|
|
22063
22236
|
let currentBundle = request.bundle;
|
|
22237
|
+
let currentRunOptions = request.runOptions;
|
|
22064
22238
|
let currentHopKind = { kind: "primary" };
|
|
22065
22239
|
let finalPrompt;
|
|
22066
22240
|
const _opStartMs = Date.now();
|
|
@@ -22072,7 +22246,7 @@ class AgentManager {
|
|
|
22072
22246
|
let result;
|
|
22073
22247
|
let updatedBundle = currentBundle;
|
|
22074
22248
|
if (request.executeHop) {
|
|
22075
|
-
const hopOut = await request.executeHop(currentAgent, currentBundle, currentHopKind,
|
|
22249
|
+
const hopOut = await request.executeHop(currentAgent, currentBundle, currentHopKind, currentRunOptions);
|
|
22076
22250
|
result = hopOut.result;
|
|
22077
22251
|
updatedBundle = hopOut.bundle ?? currentBundle;
|
|
22078
22252
|
finalPrompt = hopOut.prompt ?? finalPrompt;
|
|
@@ -22089,7 +22263,7 @@ class AgentManager {
|
|
|
22089
22263
|
_finalStatus = "error";
|
|
22090
22264
|
return { result: unboundResult, fallbacks, finalBundle: currentBundle, finalPrompt };
|
|
22091
22265
|
}
|
|
22092
|
-
const rawHopOut = await this._runHop(currentAgent,
|
|
22266
|
+
const rawHopOut = await this._runHop(currentAgent, currentRunOptions);
|
|
22093
22267
|
const hopOut = "result" in rawHopOut && rawHopOut.result != null ? rawHopOut : { result: rawHopOut, prompt: undefined };
|
|
22094
22268
|
result = hopOut.result;
|
|
22095
22269
|
finalPrompt = hopOut.prompt ?? finalPrompt;
|
|
@@ -22100,51 +22274,49 @@ class AgentManager {
|
|
|
22100
22274
|
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
22101
22275
|
}
|
|
22102
22276
|
const bundleForSwapCheck = updatedBundle ?? request.bundle;
|
|
22103
|
-
if (request.noFallback) {
|
|
22104
|
-
_finalStatus = "error";
|
|
22105
|
-
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
22106
|
-
}
|
|
22107
22277
|
const isFailStale = result.adapterFailure?.outcome === "fail-stale";
|
|
22108
|
-
const
|
|
22109
|
-
|
|
22110
|
-
|
|
22278
|
+
const retryState = {
|
|
22279
|
+
staleRetryAttempts,
|
|
22280
|
+
timeoutRetryAttempts,
|
|
22281
|
+
adapterErrorRetries,
|
|
22282
|
+
currentRunOptions
|
|
22283
|
+
};
|
|
22284
|
+
const retryDecision = trySameAgentRetry(result, retryState, {
|
|
22285
|
+
config: this._config,
|
|
22286
|
+
requestRunOptions: request.runOptions,
|
|
22287
|
+
signal: request.signal
|
|
22288
|
+
});
|
|
22289
|
+
if (retryDecision) {
|
|
22290
|
+
staleRetryAttempts = retryDecision.outcome === "stale-retry" ? retryDecision.staleRetryAttempts : staleRetryAttempts;
|
|
22291
|
+
timeoutRetryAttempts = retryDecision.outcome === "timeout-retry" ? retryDecision.timeoutRetryAttempts : timeoutRetryAttempts;
|
|
22292
|
+
adapterErrorRetries = retryDecision.outcome === "adapter-error" ? retryDecision.adapterErrorRetries : adapterErrorRetries;
|
|
22293
|
+
currentRunOptions = retryDecision.outcome === "timeout-retry" ? retryDecision.currentRunOptions : currentRunOptions;
|
|
22111
22294
|
const retryHop = {
|
|
22112
22295
|
storyId: request.runOptions.storyId,
|
|
22113
22296
|
priorAgent: currentAgent,
|
|
22114
22297
|
newAgent: currentAgent,
|
|
22115
|
-
hop:
|
|
22116
|
-
outcome:
|
|
22117
|
-
category:
|
|
22298
|
+
hop: retryDecision.kind.attempt,
|
|
22299
|
+
outcome: retryDecision.fallbackRecord.outcome,
|
|
22300
|
+
category: retryDecision.fallbackRecord.category,
|
|
22118
22301
|
timestamp: new Date().toISOString(),
|
|
22119
|
-
costUsd:
|
|
22302
|
+
costUsd: retryDecision.fallbackRecord.costUsd
|
|
22120
22303
|
};
|
|
22121
|
-
|
|
22122
|
-
|
|
22123
|
-
|
|
22124
|
-
|
|
22125
|
-
|
|
22126
|
-
|
|
22127
|
-
|
|
22128
|
-
}
|
|
22129
|
-
|
|
22304
|
+
const logEvent = describeRetryLogEvent(retryDecision, request.runOptions.storyId, currentAgent);
|
|
22305
|
+
if (logEvent.recordFallback) {
|
|
22306
|
+
fallbacks.push(retryHop);
|
|
22307
|
+
this._emitter.emit("onSwapAttempt", retryHop);
|
|
22308
|
+
}
|
|
22309
|
+
if (logEvent.level === "warn") {
|
|
22310
|
+
logger?.warn("agent-manager", logEvent.message, logEvent.fields);
|
|
22311
|
+
} else {
|
|
22312
|
+
logger?.info("agent-manager", logEvent.message, logEvent.fields);
|
|
22313
|
+
}
|
|
22314
|
+
currentHopKind = retryDecision.kind;
|
|
22130
22315
|
continue;
|
|
22131
22316
|
}
|
|
22132
|
-
|
|
22133
|
-
|
|
22134
|
-
|
|
22135
|
-
const maxAdapterRetries = result.adapterFailure?.retriable ? runConfig.execution?.sessionErrorRetryableMaxRetries ?? 3 : runConfig.execution?.sessionErrorMaxRetries ?? 1;
|
|
22136
|
-
if (adapterErrorRetries < maxAdapterRetries) {
|
|
22137
|
-
adapterErrorRetries++;
|
|
22138
|
-
logger?.warn("agent-manager", "fail-adapter-error: same-agent retry with fresh session", {
|
|
22139
|
-
storyId: request.runOptions.storyId,
|
|
22140
|
-
attempt: adapterErrorRetries,
|
|
22141
|
-
maxAttempts: maxAdapterRetries,
|
|
22142
|
-
retriable: result.adapterFailure?.retriable ?? false,
|
|
22143
|
-
agent: currentAgent
|
|
22144
|
-
});
|
|
22145
|
-
currentHopKind = { kind: "stale-retry", attempt: adapterErrorRetries };
|
|
22146
|
-
continue;
|
|
22147
|
-
}
|
|
22317
|
+
if (request.noFallback) {
|
|
22318
|
+
_finalStatus = "error";
|
|
22319
|
+
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
22148
22320
|
}
|
|
22149
22321
|
const hasBundleForSwap = !!bundleForSwapCheck || isFailStale;
|
|
22150
22322
|
if (!this.shouldSwap(result.adapterFailure, hopsSoFar, hasBundleForSwap)) {
|
|
@@ -22558,6 +22730,7 @@ var init_manager = __esm(() => {
|
|
|
22558
22730
|
init_bun_deps();
|
|
22559
22731
|
init_registry();
|
|
22560
22732
|
init_default_strategy();
|
|
22733
|
+
init_hop_retry_policy();
|
|
22561
22734
|
_agentManagerDeps = {
|
|
22562
22735
|
sleep: (ms, signal) => cancellableDelay(ms, signal)
|
|
22563
22736
|
};
|
|
@@ -22835,6 +23008,7 @@ var init_retry = __esm(() => {
|
|
|
22835
23008
|
init_compose();
|
|
22836
23009
|
init_parse_retry();
|
|
22837
23010
|
init_tiered_parse_retry();
|
|
23011
|
+
init_hop_retry_policy();
|
|
22838
23012
|
});
|
|
22839
23013
|
|
|
22840
23014
|
// src/agents/index.ts
|
|
@@ -25288,7 +25462,7 @@ async function getGitRoot(workdir) {
|
|
|
25288
25462
|
return null;
|
|
25289
25463
|
}
|
|
25290
25464
|
}
|
|
25291
|
-
async function gitWithTimeout(args, workdir) {
|
|
25465
|
+
async function gitWithTimeout(args, workdir, timeoutMs = GIT_TIMEOUT_MS) {
|
|
25292
25466
|
const proc = _gitDeps.spawn(["git", ...args], {
|
|
25293
25467
|
cwd: workdir,
|
|
25294
25468
|
stdout: "pipe",
|
|
@@ -25300,7 +25474,7 @@ async function gitWithTimeout(args, workdir) {
|
|
|
25300
25474
|
try {
|
|
25301
25475
|
proc.kill("SIGKILL");
|
|
25302
25476
|
} catch {}
|
|
25303
|
-
},
|
|
25477
|
+
}, timeoutMs);
|
|
25304
25478
|
const exitCode = await proc.exited;
|
|
25305
25479
|
clearTimeout(timerId);
|
|
25306
25480
|
if (timedOut) {
|
|
@@ -25432,6 +25606,38 @@ async function captureOutputFiles(workdir, baseRef, scopePrefix) {
|
|
|
25432
25606
|
return [];
|
|
25433
25607
|
}
|
|
25434
25608
|
}
|
|
25609
|
+
async function captureWorkingTreeChanges(workdir, baseRef, scopePrefix) {
|
|
25610
|
+
if (!baseRef)
|
|
25611
|
+
return [];
|
|
25612
|
+
const runDiff = async (args) => {
|
|
25613
|
+
const fullArgs = scopePrefix ? [...args, "--", `${scopePrefix}/`] : args;
|
|
25614
|
+
const { stdout, exitCode } = await gitWithTimeout(fullArgs, workdir, TIMEOUT_RETRY_GIT_TIMEOUT_MS);
|
|
25615
|
+
if (exitCode !== 0)
|
|
25616
|
+
return [];
|
|
25617
|
+
return stdout.trim().split(`
|
|
25618
|
+
`).filter(Boolean);
|
|
25619
|
+
};
|
|
25620
|
+
try {
|
|
25621
|
+
const [committed, uncommitted, untracked] = await Promise.all([
|
|
25622
|
+
runDiff(["diff", "--name-only", `${baseRef}..HEAD`]),
|
|
25623
|
+
runDiff(["diff", "--name-only", "HEAD"]),
|
|
25624
|
+
runDiff(["ls-files", "--others", "--exclude-standard"])
|
|
25625
|
+
]);
|
|
25626
|
+
const seen = new Set;
|
|
25627
|
+
const merged = [];
|
|
25628
|
+
for (const list of [committed, uncommitted, untracked]) {
|
|
25629
|
+
for (const file3 of list) {
|
|
25630
|
+
if (!seen.has(file3)) {
|
|
25631
|
+
seen.add(file3);
|
|
25632
|
+
merged.push(file3);
|
|
25633
|
+
}
|
|
25634
|
+
}
|
|
25635
|
+
}
|
|
25636
|
+
return merged;
|
|
25637
|
+
} catch {
|
|
25638
|
+
return [];
|
|
25639
|
+
}
|
|
25640
|
+
}
|
|
25435
25641
|
async function captureDiffSummary(workdir, baseRef, scopePrefix) {
|
|
25436
25642
|
if (!baseRef)
|
|
25437
25643
|
return "";
|
|
@@ -25454,7 +25660,7 @@ async function captureDiffSummary(workdir, baseRef, scopePrefix) {
|
|
|
25454
25660
|
return "";
|
|
25455
25661
|
}
|
|
25456
25662
|
}
|
|
25457
|
-
var _gitDeps, GIT_TIMEOUT_MS = 1e4;
|
|
25663
|
+
var _gitDeps, GIT_TIMEOUT_MS = 1e4, TIMEOUT_RETRY_GIT_TIMEOUT_MS = 3000;
|
|
25458
25664
|
var init_git = __esm(() => {
|
|
25459
25665
|
init_logger2();
|
|
25460
25666
|
init_bun_deps();
|
|
@@ -28697,81 +28903,12 @@ function generateHumanHaltSummary(prd) {
|
|
|
28697
28903
|
`);
|
|
28698
28904
|
}
|
|
28699
28905
|
|
|
28700
|
-
// src/prd/verbatim-fidelity.ts
|
|
28701
|
-
function normalizeWs(text) {
|
|
28702
|
-
return text.replace(/\s+/g, " ").trim();
|
|
28703
|
-
}
|
|
28704
|
-
function stripBackticks(text) {
|
|
28705
|
-
return text.replace(/`/g, "");
|
|
28706
|
-
}
|
|
28707
|
-
function canonical2(text) {
|
|
28708
|
-
return normalizeWs(stripBackticks(text));
|
|
28709
|
-
}
|
|
28710
|
-
function leadingTagGroup(line) {
|
|
28711
|
-
return line.match(LEADING_TAG_GROUP)?.[1] ?? null;
|
|
28712
|
-
}
|
|
28713
|
-
function isVerbatimBullet(line) {
|
|
28714
|
-
const tags = leadingTagGroup(line);
|
|
28715
|
-
return tags !== null && /\[verbatim\]/i.test(tags);
|
|
28716
|
-
}
|
|
28717
|
-
function isContinuation(line) {
|
|
28718
|
-
if (line.trim().length === 0)
|
|
28719
|
-
return false;
|
|
28720
|
-
if (HEADING.test(line))
|
|
28721
|
-
return false;
|
|
28722
|
-
if (LIST_ITEM_START2.test(line))
|
|
28723
|
-
return false;
|
|
28724
|
-
return true;
|
|
28725
|
-
}
|
|
28726
|
-
function stripTagPrefix(block) {
|
|
28727
|
-
return block.replace(LEADING_TAG_GROUP, "");
|
|
28728
|
-
}
|
|
28729
|
-
function extractVerbatimAcs(specContent) {
|
|
28730
|
-
const lines = specContent.split(`
|
|
28731
|
-
`);
|
|
28732
|
-
const blocks = [];
|
|
28733
|
-
for (let i = 0;i < lines.length; i++) {
|
|
28734
|
-
if (!isVerbatimBullet(lines[i]))
|
|
28735
|
-
continue;
|
|
28736
|
-
const parts = [lines[i].trim()];
|
|
28737
|
-
let j = i + 1;
|
|
28738
|
-
while (j < lines.length && isContinuation(lines[j])) {
|
|
28739
|
-
parts.push(lines[j].trim());
|
|
28740
|
-
j += 1;
|
|
28741
|
-
}
|
|
28742
|
-
blocks.push(parts.join(" "));
|
|
28743
|
-
i = j - 1;
|
|
28744
|
-
}
|
|
28745
|
-
return blocks;
|
|
28746
|
-
}
|
|
28747
|
-
function prdAcPayloads(prd) {
|
|
28748
|
-
return (prd.userStories ?? []).flatMap((story) => (story.acceptanceCriteria ?? []).map(canonical2));
|
|
28749
|
-
}
|
|
28750
|
-
function findMissingVerbatimAcs(specContent, prd) {
|
|
28751
|
-
const prdAcs = prdAcPayloads(prd);
|
|
28752
|
-
const missing = [];
|
|
28753
|
-
for (const block of extractVerbatimAcs(specContent)) {
|
|
28754
|
-
const payload = canonical2(stripTagPrefix(block));
|
|
28755
|
-
if (payload.length === 0)
|
|
28756
|
-
continue;
|
|
28757
|
-
if (!prdAcs.some((ac) => ac.includes(payload)))
|
|
28758
|
-
missing.push(block);
|
|
28759
|
-
}
|
|
28760
|
-
return missing;
|
|
28761
|
-
}
|
|
28762
|
-
var LEADING_TAG_GROUP, LIST_ITEM_START2, HEADING;
|
|
28763
|
-
var init_verbatim_fidelity = __esm(() => {
|
|
28764
|
-
LEADING_TAG_GROUP = /^\s*(?:[-*]|\d+\.)?\s*((?:\[[a-z][a-z-]*\]\s*)+)/i;
|
|
28765
|
-
LIST_ITEM_START2 = /^\s*(?:[-*]|\d+\.)\s/;
|
|
28766
|
-
HEADING = /^\s*#/;
|
|
28767
|
-
});
|
|
28768
|
-
|
|
28769
28906
|
// src/prd/spec-drift.ts
|
|
28770
|
-
function
|
|
28771
|
-
return ac.match(
|
|
28907
|
+
function leadingTagGroup(ac) {
|
|
28908
|
+
return ac.match(LEADING_TAG_GROUP)?.[1] ?? null;
|
|
28772
28909
|
}
|
|
28773
28910
|
function hasDeprecatedTag(ac) {
|
|
28774
|
-
const tags =
|
|
28911
|
+
const tags = leadingTagGroup(ac);
|
|
28775
28912
|
return tags !== null && DEPRECATED_TAG.test(tags);
|
|
28776
28913
|
}
|
|
28777
28914
|
function hasShellPattern(ac) {
|
|
@@ -28791,9 +28928,9 @@ function findSpecDriftViolations(prd) {
|
|
|
28791
28928
|
}
|
|
28792
28929
|
return violations;
|
|
28793
28930
|
}
|
|
28794
|
-
var
|
|
28931
|
+
var LEADING_TAG_GROUP, DEPRECATED_TAG, SHELL_PIPE, SHELL_WC, SHELL_GREP_FLAG;
|
|
28795
28932
|
var init_spec_drift = __esm(() => {
|
|
28796
|
-
|
|
28933
|
+
LEADING_TAG_GROUP = /^\s*(?:[-*]|\d+\.)?\s*((?:\[[a-z][a-z-]*\]\s*)+)/i;
|
|
28797
28934
|
DEPRECATED_TAG = /\[(grep|file|verbatim)\]/i;
|
|
28798
28935
|
SHELL_PIPE = /`[^`]*\b(grep|find|wc|awk|sed|sort|head|tail|xargs|cut|uniq)\b[^`]*\|[^`]*`/i;
|
|
28799
28936
|
SHELL_WC = /`[^`]*\bwc\b[^`]*`/;
|
|
@@ -29221,9 +29358,7 @@ __export(exports_prd, {
|
|
|
29221
29358
|
getContextFiles: () => getContextFiles,
|
|
29222
29359
|
generateHumanHaltSummary: () => generateHumanHaltSummary,
|
|
29223
29360
|
findSpecDriftViolations: () => findSpecDriftViolations,
|
|
29224
|
-
findMissingVerbatimAcs: () => findMissingVerbatimAcs,
|
|
29225
29361
|
findMissingOutOfScope: () => findMissingOutOfScope,
|
|
29226
|
-
extractVerbatimAcs: () => extractVerbatimAcs,
|
|
29227
29362
|
extractSpecOutOfScope: () => extractSpecOutOfScope,
|
|
29228
29363
|
deriveNextStoryId: () => deriveNextStoryId,
|
|
29229
29364
|
countStories: () => countStories,
|
|
@@ -29421,7 +29556,6 @@ var init_prd = __esm(() => {
|
|
|
29421
29556
|
init_errors();
|
|
29422
29557
|
init_json_file();
|
|
29423
29558
|
init_out_of_scope();
|
|
29424
|
-
init_verbatim_fidelity();
|
|
29425
29559
|
init_spec_drift();
|
|
29426
29560
|
init_out_of_scope();
|
|
29427
29561
|
init_inject();
|
|
@@ -29878,6 +30012,13 @@ var init_plugin_loader = __esm(() => {
|
|
|
29878
30012
|
});
|
|
29879
30013
|
|
|
29880
30014
|
// src/context/engine/providers/plugin-cache.ts
|
|
30015
|
+
function withDisposeDeadline(p, deadlineMs) {
|
|
30016
|
+
let handle;
|
|
30017
|
+
const deadline = new Promise((resolve8) => {
|
|
30018
|
+
handle = setTimeout(resolve8, deadlineMs);
|
|
30019
|
+
});
|
|
30020
|
+
return Promise.race([p, deadline]).finally(() => clearTimeout(handle));
|
|
30021
|
+
}
|
|
29881
30022
|
function stableCacheKey(configs, workdir) {
|
|
29882
30023
|
const sorted = [...configs].sort((a, b) => a.module.localeCompare(b.module));
|
|
29883
30024
|
return `${workdir}:${JSON.stringify(sorted)}`;
|
|
@@ -29908,31 +30049,32 @@ class PluginProviderCache {
|
|
|
29908
30049
|
return;
|
|
29909
30050
|
this.disposed = true;
|
|
29910
30051
|
const logger = getLogger();
|
|
30052
|
+
const disposals = [];
|
|
29911
30053
|
for (const providers of this.cache.values()) {
|
|
29912
30054
|
for (const provider of providers) {
|
|
29913
30055
|
const initialisable = provider;
|
|
29914
30056
|
if (typeof initialisable.dispose !== "function")
|
|
29915
30057
|
continue;
|
|
29916
|
-
|
|
29917
|
-
await Promise.race([initialisable.dispose(), Bun.sleep(DISPOSE_TIMEOUT_MS)]);
|
|
29918
|
-
} catch (err) {
|
|
30058
|
+
disposals.push(withDisposeDeadline(Promise.resolve().then(() => initialisable.dispose?.()).then(() => {}), _pluginCacheDeps.disposeTimeoutMs).catch((err) => {
|
|
29919
30059
|
logger.warn("context-engine", "Plugin provider dispose() threw \u2014 continuing teardown", {
|
|
29920
30060
|
providerId: provider.id,
|
|
29921
30061
|
error: err instanceof Error ? err.message : String(err)
|
|
29922
30062
|
});
|
|
29923
|
-
}
|
|
30063
|
+
}));
|
|
29924
30064
|
}
|
|
29925
30065
|
}
|
|
30066
|
+
await Promise.all(disposals);
|
|
29926
30067
|
this.cache.clear();
|
|
29927
30068
|
}
|
|
29928
30069
|
}
|
|
29929
|
-
var
|
|
30070
|
+
var DISPOSE_TIMEOUT_MS = 5000, _pluginCacheDeps;
|
|
29930
30071
|
var init_plugin_cache = __esm(() => {
|
|
29931
30072
|
init_errors();
|
|
29932
30073
|
init_logger2();
|
|
29933
30074
|
init_plugin_loader();
|
|
29934
30075
|
_pluginCacheDeps = {
|
|
29935
|
-
loadProviders: loadPluginProviders
|
|
30076
|
+
loadProviders: loadPluginProviders,
|
|
30077
|
+
disposeTimeoutMs: DISPOSE_TIMEOUT_MS
|
|
29936
30078
|
};
|
|
29937
30079
|
});
|
|
29938
30080
|
|
|
@@ -33450,7 +33592,7 @@ var init_acceptance_builder = __esm(() => {
|
|
|
33450
33592
|
});
|
|
33451
33593
|
|
|
33452
33594
|
// src/review/ac-quote-validator.ts
|
|
33453
|
-
function
|
|
33595
|
+
function normalizeWs(s) {
|
|
33454
33596
|
return s.replace(/\s+/g, " ").trim();
|
|
33455
33597
|
}
|
|
33456
33598
|
function stripMarkdownInline(s) {
|
|
@@ -33485,8 +33627,8 @@ function validateAcQuote(finding, acceptanceCriteria) {
|
|
|
33485
33627
|
if (typeof acIndex !== "number" || acIndex < 1 || acIndex > acceptanceCriteria.length) {
|
|
33486
33628
|
return { valid: false, code: "ac_index_out_of_range" };
|
|
33487
33629
|
}
|
|
33488
|
-
const acText =
|
|
33489
|
-
const normalizedQuote =
|
|
33630
|
+
const acText = normalizeWs(stripMarkdownInline(acceptanceCriteria[acIndex - 1]));
|
|
33631
|
+
const normalizedQuote = normalizeWs(stripMarkdownInline(acQuote));
|
|
33490
33632
|
if (!acText.toLowerCase().includes(normalizedQuote.toLowerCase())) {
|
|
33491
33633
|
return { valid: false, code: "ac_quote_not_substring" };
|
|
33492
33634
|
}
|
|
@@ -33560,8 +33702,8 @@ function validateScopeQuote(finding, outOfScope) {
|
|
|
33560
33702
|
if (typeof scopeIndex !== "number" || scopeIndex < 1 || scopeIndex > outOfScope.length) {
|
|
33561
33703
|
return { valid: false, code: "scope_index_out_of_range" };
|
|
33562
33704
|
}
|
|
33563
|
-
const entry =
|
|
33564
|
-
const quote =
|
|
33705
|
+
const entry = normalizeWs(stripMarkdownInline(outOfScope[scopeIndex - 1]));
|
|
33706
|
+
const quote = normalizeWs(stripMarkdownInline(scopeQuote));
|
|
33565
33707
|
if (!entry.toLowerCase().includes(quote.toLowerCase())) {
|
|
33566
33708
|
return { valid: false, code: "scope_quote_not_substring" };
|
|
33567
33709
|
}
|
|
@@ -33780,13 +33922,31 @@ var init_adapters = __esm(() => {
|
|
|
33780
33922
|
init_typecheck();
|
|
33781
33923
|
});
|
|
33782
33924
|
|
|
33783
|
-
// src/operations/
|
|
33784
|
-
function
|
|
33785
|
-
|
|
33786
|
-
|
|
33787
|
-
|
|
33925
|
+
// src/operations/turn-failure-classification.ts
|
|
33926
|
+
function classifyEmptyOutputFailure(turn) {
|
|
33927
|
+
if (turn.adapterFailure)
|
|
33928
|
+
return turn.adapterFailure;
|
|
33929
|
+
if (turn.output && turn.output.trim().length > 0)
|
|
33930
|
+
return null;
|
|
33931
|
+
if (turn.timedOut) {
|
|
33932
|
+
return {
|
|
33933
|
+
category: "quality",
|
|
33934
|
+
outcome: "fail-timeout",
|
|
33935
|
+
retriable: true,
|
|
33936
|
+
message: "[callOp] agent timed out before producing output",
|
|
33937
|
+
reason: "wall-clock-timeout"
|
|
33938
|
+
};
|
|
33788
33939
|
}
|
|
33940
|
+
return {
|
|
33941
|
+
category: "availability",
|
|
33942
|
+
outcome: "fail-stale",
|
|
33943
|
+
retriable: true,
|
|
33944
|
+
message: "[callOp] agent returned no output",
|
|
33945
|
+
reason: "empty-output"
|
|
33946
|
+
};
|
|
33789
33947
|
}
|
|
33948
|
+
|
|
33949
|
+
// src/operations/plan-fidelity.ts
|
|
33790
33950
|
function backfillOutOfScope(prd, specContent, featureName) {
|
|
33791
33951
|
const missing = findMissingOutOfScope(specContent, prd);
|
|
33792
33952
|
if (missing.length === 0)
|
|
@@ -33808,7 +33968,7 @@ function warnOnSpecDrift(prd, featureName) {
|
|
|
33808
33968
|
});
|
|
33809
33969
|
}
|
|
33810
33970
|
}
|
|
33811
|
-
var
|
|
33971
|
+
var init_plan_fidelity = __esm(() => {
|
|
33812
33972
|
init_logger2();
|
|
33813
33973
|
init_prd();
|
|
33814
33974
|
});
|
|
@@ -33820,7 +33980,7 @@ var init_plan = __esm(() => {
|
|
|
33820
33980
|
init_config();
|
|
33821
33981
|
init_schema2();
|
|
33822
33982
|
init_prompts();
|
|
33823
|
-
|
|
33983
|
+
init_plan_fidelity();
|
|
33824
33984
|
planInteractiveOp = {
|
|
33825
33985
|
kind: "run",
|
|
33826
33986
|
name: "plan-interactive",
|
|
@@ -33865,7 +34025,6 @@ ${outputFormat}`, overridable: false }
|
|
|
33865
34025
|
verify: async (parsed, input, _ctx) => {
|
|
33866
34026
|
if (!parsed.userStories || parsed.userStories.length === 0)
|
|
33867
34027
|
return null;
|
|
33868
|
-
warnOnDroppedVerbatimAcs(parsed, input.specContent, input.featureName);
|
|
33869
34028
|
return backfillOutOfScope(parsed, input.specContent, input.featureName);
|
|
33870
34029
|
},
|
|
33871
34030
|
recover: async (input, ctx) => {
|
|
@@ -33951,20 +34110,6 @@ function validateRefinedPrd(prd) {
|
|
|
33951
34110
|
validateRefinedStory(story);
|
|
33952
34111
|
return prd;
|
|
33953
34112
|
}
|
|
33954
|
-
async function readMissingVerbatimAcs(input) {
|
|
33955
|
-
const content = await _planRefineDeps.readFile(input.outputPath);
|
|
33956
|
-
if (!content)
|
|
33957
|
-
return [];
|
|
33958
|
-
try {
|
|
33959
|
-
const prd = validatePlanOutput(content, input.featureName, input.branchName);
|
|
33960
|
-
return findMissingVerbatimAcs(input.specContent, prd);
|
|
33961
|
-
} catch {
|
|
33962
|
-
getSafeLogger()?.debug("plan", "Skipped [verbatim] self-heal \u2014 draft PRD not yet parseable", {
|
|
33963
|
-
featureName: input.featureName
|
|
33964
|
-
});
|
|
33965
|
-
return [];
|
|
33966
|
-
}
|
|
33967
|
-
}
|
|
33968
34113
|
async function readSpecDriftViolations(input) {
|
|
33969
34114
|
const content = await _planRefineDeps.readFile(input.outputPath);
|
|
33970
34115
|
if (!content)
|
|
@@ -34053,17 +34198,6 @@ async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
|
|
|
34053
34198
|
return prd;
|
|
34054
34199
|
return { ...prd, userStories: results.map((r) => r.story) };
|
|
34055
34200
|
}
|
|
34056
|
-
function verbatimSelfHealStep(builder) {
|
|
34057
|
-
return makeSelfHealStep({
|
|
34058
|
-
detect: (input) => readMissingVerbatimAcs(input),
|
|
34059
|
-
buildRepair: (missing, input) => builder.buildVerbatimRepair(missing, input.outputPath),
|
|
34060
|
-
log: {
|
|
34061
|
-
kind: "plan",
|
|
34062
|
-
message: "Refine dropped [verbatim] spec ACs \u2014 issuing one repair turn",
|
|
34063
|
-
meta: (input, missing) => ({ featureName: input.featureName, missingCount: missing.length })
|
|
34064
|
-
}
|
|
34065
|
-
});
|
|
34066
|
-
}
|
|
34067
34201
|
async function readMissingOutOfScope(input) {
|
|
34068
34202
|
const content = await _planRefineDeps.readFile(input.outputPath);
|
|
34069
34203
|
if (!content)
|
|
@@ -34110,8 +34244,8 @@ var init_plan_refine = __esm(() => {
|
|
|
34110
34244
|
init_prd();
|
|
34111
34245
|
init_schema2();
|
|
34112
34246
|
init_prompts();
|
|
34247
|
+
init_plan_fidelity();
|
|
34113
34248
|
init_self_heal();
|
|
34114
|
-
init_verbatim_warn();
|
|
34115
34249
|
_planRefineDeps = {
|
|
34116
34250
|
readFile: async (path3) => {
|
|
34117
34251
|
try {
|
|
@@ -34195,7 +34329,6 @@ ${outputFormat}`,
|
|
|
34195
34329
|
estimatedCostUsd: (turn1.estimatedCostUsd ?? 0) + (turn2.estimatedCostUsd ?? 0)
|
|
34196
34330
|
};
|
|
34197
34331
|
const steps = [
|
|
34198
|
-
verbatimSelfHealStep(builder),
|
|
34199
34332
|
outOfScopeSelfHealStep(builder),
|
|
34200
34333
|
...specGuard ? [specDriftSelfHealStep(builder)] : []
|
|
34201
34334
|
];
|
|
@@ -34206,7 +34339,6 @@ ${outputFormat}`,
|
|
|
34206
34339
|
},
|
|
34207
34340
|
verify: async (parsed, input, ctx) => {
|
|
34208
34341
|
const validated = validateRefinedPrd(parsed);
|
|
34209
|
-
warnOnDroppedVerbatimAcs(validated, input.specContent, input.featureName);
|
|
34210
34342
|
if (ctx.config.plan.specGuard) {
|
|
34211
34343
|
warnOnSpecDrift(validated, input.featureName);
|
|
34212
34344
|
}
|
|
@@ -35638,6 +35770,11 @@ function categoryToFixTarget(category) {
|
|
|
35638
35770
|
return "source";
|
|
35639
35771
|
return category != null && BLOCKING_CATEGORIES.has(category) ? "source" : "test";
|
|
35640
35772
|
}
|
|
35773
|
+
function resolveFixTarget({ base, file: file3, isTestFile: isTestFile3 }) {
|
|
35774
|
+
if (file3 && isTestFile3?.(file3))
|
|
35775
|
+
return "test";
|
|
35776
|
+
return base;
|
|
35777
|
+
}
|
|
35641
35778
|
var init_category_fix_target = __esm(() => {
|
|
35642
35779
|
init_ac_structural_counterfactual();
|
|
35643
35780
|
});
|
|
@@ -35665,7 +35802,7 @@ function normalizeSeverity(sev) {
|
|
|
35665
35802
|
return sev;
|
|
35666
35803
|
return "info";
|
|
35667
35804
|
}
|
|
35668
|
-
function toAdversarialReviewFindings(findings) {
|
|
35805
|
+
function toAdversarialReviewFindings(findings, opts = {}) {
|
|
35669
35806
|
return findings.map((f) => {
|
|
35670
35807
|
const metaExtras = {};
|
|
35671
35808
|
if (f.acQuote)
|
|
@@ -35686,7 +35823,7 @@ function toAdversarialReviewFindings(findings) {
|
|
|
35686
35823
|
line: f.line,
|
|
35687
35824
|
message: f.issue,
|
|
35688
35825
|
suggestion: f.suggestion,
|
|
35689
|
-
fixTarget: categoryToFixTarget(f.category),
|
|
35826
|
+
fixTarget: resolveFixTarget({ base: categoryToFixTarget(f.category), file: f.file, isTestFile: opts.isTestFile }),
|
|
35690
35827
|
meta: Object.keys(metaExtras).length > 0 ? metaExtras : undefined
|
|
35691
35828
|
};
|
|
35692
35829
|
});
|
|
@@ -35750,7 +35887,7 @@ function downgradeToUnverifiable(finding) {
|
|
|
35750
35887
|
severity: "unverifiable"
|
|
35751
35888
|
};
|
|
35752
35889
|
}
|
|
35753
|
-
function llmFindingToFinding(f) {
|
|
35890
|
+
function llmFindingToFinding(f, opts = {}) {
|
|
35754
35891
|
const metaExtras = {};
|
|
35755
35892
|
if (f.verifiedBy)
|
|
35756
35893
|
metaExtras.verifiedBy = f.verifiedBy;
|
|
@@ -35766,15 +35903,16 @@ function llmFindingToFinding(f) {
|
|
|
35766
35903
|
line: f.line,
|
|
35767
35904
|
message: f.issue,
|
|
35768
35905
|
suggestion: f.suggestion ?? undefined,
|
|
35769
|
-
fixTarget: "source",
|
|
35906
|
+
fixTarget: resolveFixTarget({ base: "source", file: f.file, isTestFile: opts.isTestFile }),
|
|
35770
35907
|
meta: Object.keys(metaExtras).length > 0 ? metaExtras : undefined
|
|
35771
35908
|
};
|
|
35772
35909
|
}
|
|
35773
|
-
function toReviewFindings(findings) {
|
|
35774
|
-
return findings.map(llmFindingToFinding);
|
|
35910
|
+
function toReviewFindings(findings, opts = {}) {
|
|
35911
|
+
return findings.map((f) => llmFindingToFinding(f, opts));
|
|
35775
35912
|
}
|
|
35776
35913
|
var UNVERIFIED_FINDING_PATTERNS;
|
|
35777
35914
|
var init_semantic_helpers = __esm(() => {
|
|
35915
|
+
init_category_fix_target();
|
|
35778
35916
|
init_severity();
|
|
35779
35917
|
UNVERIFIED_FINDING_PATTERNS = [
|
|
35780
35918
|
"cannot verify",
|
|
@@ -35918,9 +36056,9 @@ function parseRequoteResponse(output) {
|
|
|
35918
36056
|
const parsed = tryParseLLMJson(output);
|
|
35919
36057
|
if (!isRecord(parsed))
|
|
35920
36058
|
return null;
|
|
35921
|
-
const
|
|
35922
|
-
if (
|
|
35923
|
-
return
|
|
36059
|
+
const canonical2 = extractCanonical(parsed);
|
|
36060
|
+
if (canonical2)
|
|
36061
|
+
return canonical2;
|
|
35924
36062
|
const findings = parsed.findings;
|
|
35925
36063
|
if (!Array.isArray(findings) || findings.length !== 1)
|
|
35926
36064
|
return null;
|
|
@@ -35967,6 +36105,10 @@ function withRepromptMarker(output, info) {
|
|
|
35967
36105
|
return output;
|
|
35968
36106
|
return JSON.stringify({ ...parsed, _repromptInfo: info });
|
|
35969
36107
|
}
|
|
36108
|
+
function semanticTestFileMatch(input) {
|
|
36109
|
+
const patterns = input.resolvedTestPatterns?.regex ?? [];
|
|
36110
|
+
return (file3) => patterns.some((re) => re.test(file3));
|
|
36111
|
+
}
|
|
35970
36112
|
function extractRepromptInfo(raw) {
|
|
35971
36113
|
if (!raw || typeof raw !== "object")
|
|
35972
36114
|
return;
|
|
@@ -36258,7 +36400,7 @@ var init_semantic_review = __esm(() => {
|
|
|
36258
36400
|
...parsed,
|
|
36259
36401
|
passed,
|
|
36260
36402
|
findings: accepted,
|
|
36261
|
-
normalizedFindings: toReviewFindings(blocking),
|
|
36403
|
+
normalizedFindings: toReviewFindings(blocking, { isTestFile: semanticTestFileMatch(input) }),
|
|
36262
36404
|
acDropped: dropped
|
|
36263
36405
|
};
|
|
36264
36406
|
}
|
|
@@ -36674,10 +36816,10 @@ var init_adversarial_review = __esm(() => {
|
|
|
36674
36816
|
...parsed,
|
|
36675
36817
|
passed,
|
|
36676
36818
|
findings: accepted,
|
|
36677
|
-
normalizedFindings: toAdversarialReviewFindings(blocking),
|
|
36819
|
+
normalizedFindings: toAdversarialReviewFindings(blocking, { isTestFile: testFileMatch }),
|
|
36678
36820
|
advisoryFindings: [
|
|
36679
|
-
...toAdversarialReviewFindings(advisory),
|
|
36680
|
-
...tagCoverageGap(toAdversarialReviewFindings(demoted))
|
|
36821
|
+
...toAdversarialReviewFindings(advisory, { isTestFile: testFileMatch }),
|
|
36822
|
+
...tagCoverageGap(toAdversarialReviewFindings(demoted, { isTestFile: testFileMatch }))
|
|
36681
36823
|
],
|
|
36682
36824
|
acDropped: dropped
|
|
36683
36825
|
};
|
|
@@ -39063,14 +39205,9 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
|
|
|
39063
39205
|
const pid = proc.pid;
|
|
39064
39206
|
killProcessGroup(pid, "SIGTERM");
|
|
39065
39207
|
let exitedDuringGrace = false;
|
|
39066
|
-
await
|
|
39067
|
-
|
|
39068
|
-
|
|
39069
|
-
}),
|
|
39070
|
-
new Promise((resolve11) => {
|
|
39071
|
-
setTimeout(resolve11, gracePeriodMs);
|
|
39072
|
-
})
|
|
39073
|
-
]);
|
|
39208
|
+
await raceWithDeadline(proc.exited.then(() => {
|
|
39209
|
+
exitedDuringGrace = true;
|
|
39210
|
+
}).catch(() => {}), gracePeriodMs);
|
|
39074
39211
|
if (!exitedDuringGrace) {
|
|
39075
39212
|
killProcessGroup(pid, "SIGKILL");
|
|
39076
39213
|
}
|
|
@@ -39946,7 +40083,7 @@ async function runQualityCommand(opts) {
|
|
|
39946
40083
|
let sigkillTimer;
|
|
39947
40084
|
proc.exited.then(() => {
|
|
39948
40085
|
exitedBeforeSigkill = true;
|
|
39949
|
-
});
|
|
40086
|
+
}).catch(() => {});
|
|
39950
40087
|
const killTimer = setTimeout(() => {
|
|
39951
40088
|
timedOut = true;
|
|
39952
40089
|
killProcessGroup(proc.pid, "SIGTERM");
|
|
@@ -41378,7 +41515,7 @@ var init_operations = __esm(() => {
|
|
|
41378
41515
|
init_plan();
|
|
41379
41516
|
init_plan_refine();
|
|
41380
41517
|
init_self_heal();
|
|
41381
|
-
|
|
41518
|
+
init_plan_fidelity();
|
|
41382
41519
|
init_decompose2();
|
|
41383
41520
|
init_build_hop_callback();
|
|
41384
41521
|
init_classify_route();
|
|
@@ -41495,11 +41632,15 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41495
41632
|
const storyId = ctx.storyId;
|
|
41496
41633
|
const packageDir = ctx.packageDir;
|
|
41497
41634
|
let totalCostUsd = 0;
|
|
41635
|
+
const spentStrategies = new Set;
|
|
41636
|
+
let unresolvedDetail;
|
|
41637
|
+
const finish = (result) => unresolvedDetail !== undefined && result.unresolvedDetail === undefined ? { ...result, unresolvedDetail } : result;
|
|
41498
41638
|
for (;; ) {
|
|
41499
41639
|
if (cycle.findings.length === 0 && cycle.verdict === undefined) {
|
|
41500
41640
|
return { iterations: cycle.iterations, finalFindings: [], exitReason: "resolved", costUsd: totalCostUsd };
|
|
41501
41641
|
}
|
|
41502
|
-
const
|
|
41642
|
+
const selectable = cycle.strategies.filter((s) => !spentStrategies.has(s.name));
|
|
41643
|
+
const active = selectActiveStrategies(selectable, cycle.findings, cycle.verdict);
|
|
41503
41644
|
if (active.length === 0) {
|
|
41504
41645
|
const orphanSources = [...new Set(cycle.findings.map((f) => f.source))];
|
|
41505
41646
|
logger?.warn("findings.cycle", "cycle exited \u2014 no matching strategy (orphaned findings)", {
|
|
@@ -41508,14 +41649,15 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41508
41649
|
cycleName,
|
|
41509
41650
|
reason: "no-strategy",
|
|
41510
41651
|
findingsCount: cycle.findings.length,
|
|
41511
|
-
orphanSources
|
|
41652
|
+
orphanSources,
|
|
41653
|
+
...spentStrategies.size > 0 ? { retiredStrategies: [...spentStrategies] } : {}
|
|
41512
41654
|
});
|
|
41513
|
-
return {
|
|
41655
|
+
return finish({
|
|
41514
41656
|
iterations: cycle.iterations,
|
|
41515
41657
|
finalFindings: cycle.findings,
|
|
41516
41658
|
exitReason: "no-strategy",
|
|
41517
41659
|
costUsd: totalCostUsd
|
|
41518
|
-
};
|
|
41660
|
+
});
|
|
41519
41661
|
}
|
|
41520
41662
|
const uncappedActive = active.filter((s) => countStrategyAttempts(cycle.iterations, s.name) < s.maxAttempts);
|
|
41521
41663
|
if (uncappedActive.length === 0) {
|
|
@@ -41527,13 +41669,13 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41527
41669
|
reason: "max-attempts-per-strategy",
|
|
41528
41670
|
exhaustedStrategy: exhaustedStrategy?.name
|
|
41529
41671
|
});
|
|
41530
|
-
return {
|
|
41672
|
+
return finish({
|
|
41531
41673
|
iterations: cycle.iterations,
|
|
41532
41674
|
finalFindings: cycle.findings,
|
|
41533
41675
|
exitReason: "max-attempts-per-strategy",
|
|
41534
41676
|
exhaustedStrategy: exhaustedStrategy?.name,
|
|
41535
41677
|
costUsd: totalCostUsd
|
|
41536
|
-
};
|
|
41678
|
+
});
|
|
41537
41679
|
}
|
|
41538
41680
|
const totalAttempts = countTotalAttempts(cycle.iterations);
|
|
41539
41681
|
if (totalAttempts >= cycle.config.maxAttemptsTotal) {
|
|
@@ -41545,12 +41687,12 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41545
41687
|
totalAttempts,
|
|
41546
41688
|
maxAttemptsTotal: cycle.config.maxAttemptsTotal
|
|
41547
41689
|
});
|
|
41548
|
-
return {
|
|
41690
|
+
return finish({
|
|
41549
41691
|
iterations: cycle.iterations,
|
|
41550
41692
|
finalFindings: cycle.findings,
|
|
41551
41693
|
exitReason: "max-attempts-total",
|
|
41552
41694
|
costUsd: totalCostUsd
|
|
41553
|
-
};
|
|
41695
|
+
});
|
|
41554
41696
|
}
|
|
41555
41697
|
for (const strategy of uncappedActive) {
|
|
41556
41698
|
const bailReason = strategy.bailWhen?.(cycle.iterations) ?? null;
|
|
@@ -41563,13 +41705,13 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41563
41705
|
strategyName: strategy.name,
|
|
41564
41706
|
bailDetail: bailReason
|
|
41565
41707
|
});
|
|
41566
|
-
return {
|
|
41708
|
+
return finish({
|
|
41567
41709
|
iterations: cycle.iterations,
|
|
41568
41710
|
finalFindings: cycle.findings,
|
|
41569
41711
|
exitReason: "bail-when",
|
|
41570
41712
|
bailDetail: bailReason,
|
|
41571
41713
|
costUsd: totalCostUsd
|
|
41572
|
-
};
|
|
41714
|
+
});
|
|
41573
41715
|
}
|
|
41574
41716
|
}
|
|
41575
41717
|
const group = selectExecutionGroup(uncappedActive);
|
|
@@ -41590,33 +41732,49 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41590
41732
|
costUsd: extracted.costUsd
|
|
41591
41733
|
});
|
|
41592
41734
|
}
|
|
41593
|
-
const
|
|
41594
|
-
if (
|
|
41595
|
-
const
|
|
41596
|
-
|
|
41597
|
-
|
|
41598
|
-
|
|
41599
|
-
|
|
41600
|
-
|
|
41601
|
-
|
|
41602
|
-
|
|
41603
|
-
|
|
41604
|
-
|
|
41605
|
-
|
|
41735
|
+
const unresolvedFas = fixesApplied.filter((fa) => fa.unresolved);
|
|
41736
|
+
if (unresolvedFas.length > 0) {
|
|
41737
|
+
const firstUnresolved = unresolvedFas[0];
|
|
41738
|
+
unresolvedDetail = firstUnresolved.unresolved;
|
|
41739
|
+
for (const fa of unresolvedFas)
|
|
41740
|
+
spentStrategies.add(fa.strategyName);
|
|
41741
|
+
const allGaveUp = unresolvedFas.length === fixesApplied.length;
|
|
41742
|
+
if (allGaveUp) {
|
|
41743
|
+
const finishedAt2 = now();
|
|
41744
|
+
cycle.iterations.push({
|
|
41745
|
+
iterationNum: cycle.iterations.length + 1,
|
|
41746
|
+
findingsBefore,
|
|
41747
|
+
fixesApplied,
|
|
41748
|
+
findingsAfter: cycle.findings,
|
|
41749
|
+
outcome: "unchanged",
|
|
41750
|
+
startedAt,
|
|
41751
|
+
finishedAt: finishedAt2
|
|
41752
|
+
});
|
|
41753
|
+
totalCostUsd += fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
|
|
41754
|
+
logger?.info("findings.cycle", "cycle exited \u2014 agent gave up", {
|
|
41755
|
+
storyId,
|
|
41756
|
+
packageDir,
|
|
41757
|
+
cycleName,
|
|
41758
|
+
reason: "agent-gave-up",
|
|
41759
|
+
strategyName: firstUnresolved.strategyName,
|
|
41760
|
+
unresolvedDetail: firstUnresolved.unresolved
|
|
41761
|
+
});
|
|
41762
|
+
return finish({
|
|
41763
|
+
iterations: cycle.iterations,
|
|
41764
|
+
finalFindings: cycle.findings,
|
|
41765
|
+
exitReason: "agent-gave-up",
|
|
41766
|
+
unresolvedDetail: firstUnresolved.unresolved,
|
|
41767
|
+
costUsd: totalCostUsd
|
|
41768
|
+
});
|
|
41769
|
+
}
|
|
41770
|
+
logger?.info("findings.cycle", "strategy gave up \u2014 retired, continuing with co-run siblings", {
|
|
41606
41771
|
storyId,
|
|
41607
41772
|
packageDir,
|
|
41608
41773
|
cycleName,
|
|
41609
|
-
|
|
41610
|
-
|
|
41611
|
-
|
|
41774
|
+
strategyName: firstUnresolved.strategyName,
|
|
41775
|
+
unresolvedDetail: firstUnresolved.unresolved,
|
|
41776
|
+
ranWithoutGivingUp: fixesApplied.filter((fa) => !fa.unresolved).map((fa) => fa.strategyName)
|
|
41612
41777
|
});
|
|
41613
|
-
return {
|
|
41614
|
-
iterations: cycle.iterations,
|
|
41615
|
-
finalFindings: cycle.findings,
|
|
41616
|
-
exitReason: "agent-gave-up",
|
|
41617
|
-
unresolvedDetail: unresolvedFa.unresolved,
|
|
41618
|
-
costUsd: totalCostUsd
|
|
41619
|
-
};
|
|
41620
41778
|
}
|
|
41621
41779
|
const allExhausted = group.every((s) => {
|
|
41622
41780
|
const prior = countStrategyAttempts(cycle.iterations, s.name);
|
|
@@ -41624,6 +41782,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41624
41782
|
return prior + current >= s.maxAttempts;
|
|
41625
41783
|
});
|
|
41626
41784
|
if (allExhausted) {
|
|
41785
|
+
totalCostUsd += fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
|
|
41627
41786
|
let liteFindingsAfter;
|
|
41628
41787
|
let liteShortCircuited = false;
|
|
41629
41788
|
try {
|
|
@@ -41648,13 +41807,13 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41648
41807
|
cycleName,
|
|
41649
41808
|
error: errorMessage(err)
|
|
41650
41809
|
});
|
|
41651
|
-
return {
|
|
41810
|
+
return finish({
|
|
41652
41811
|
iterations: cycle.iterations,
|
|
41653
41812
|
finalFindings: cycle.findings,
|
|
41654
41813
|
exitReason: "max-attempts-per-strategy",
|
|
41655
41814
|
exhaustedStrategy: group[0]?.name,
|
|
41656
41815
|
costUsd: totalCostUsd
|
|
41657
|
-
};
|
|
41816
|
+
});
|
|
41658
41817
|
}
|
|
41659
41818
|
const outcome2 = classifyOutcome(findingsBefore, liteFindingsAfter);
|
|
41660
41819
|
const finishedAt2 = now();
|
|
@@ -41675,18 +41834,16 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41675
41834
|
cycleName,
|
|
41676
41835
|
reason: "resolved"
|
|
41677
41836
|
});
|
|
41678
|
-
return {
|
|
41837
|
+
return finish({
|
|
41679
41838
|
iterations: cycle.iterations,
|
|
41680
41839
|
finalFindings: [],
|
|
41681
41840
|
exitReason: "resolved",
|
|
41682
41841
|
costUsd: totalCostUsd
|
|
41683
|
-
};
|
|
41842
|
+
});
|
|
41684
41843
|
}
|
|
41685
41844
|
if (liteShortCircuited) {
|
|
41686
41845
|
const companions = uncappedActive.filter((s) => !group.includes(s));
|
|
41687
41846
|
if (companions.length > 0) {
|
|
41688
|
-
const iterCostUsd = fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
|
|
41689
|
-
totalCostUsd += iterCostUsd;
|
|
41690
41847
|
logger?.info("findings.cycle", "exclusive strategy exhausted \u2014 continuing to companion strategies", {
|
|
41691
41848
|
storyId,
|
|
41692
41849
|
packageDir,
|
|
@@ -41703,12 +41860,12 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41703
41860
|
reason: "validate-short-circuit",
|
|
41704
41861
|
liteFindingsAfterCount: liteFindingsAfter.length
|
|
41705
41862
|
});
|
|
41706
|
-
return {
|
|
41863
|
+
return finish({
|
|
41707
41864
|
iterations: cycle.iterations,
|
|
41708
41865
|
finalFindings: liteFindingsAfter,
|
|
41709
41866
|
exitReason: "validate-short-circuit",
|
|
41710
41867
|
costUsd: totalCostUsd
|
|
41711
|
-
};
|
|
41868
|
+
});
|
|
41712
41869
|
}
|
|
41713
41870
|
logger?.info("findings.cycle", "cycle exited \u2014 strategy attempt cap reached (lite validate)", {
|
|
41714
41871
|
storyId,
|
|
@@ -41718,13 +41875,13 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41718
41875
|
exhaustedStrategy: group[0]?.name,
|
|
41719
41876
|
liteFindingsAfterCount: liteFindingsAfter.length
|
|
41720
41877
|
});
|
|
41721
|
-
return {
|
|
41878
|
+
return finish({
|
|
41722
41879
|
iterations: cycle.iterations,
|
|
41723
41880
|
finalFindings: liteFindingsAfter,
|
|
41724
41881
|
exitReason: "max-attempts-per-strategy",
|
|
41725
41882
|
exhaustedStrategy: group[0]?.name,
|
|
41726
41883
|
costUsd: totalCostUsd
|
|
41727
|
-
};
|
|
41884
|
+
});
|
|
41728
41885
|
}
|
|
41729
41886
|
let findingsAfter;
|
|
41730
41887
|
let validatorAttempt = 0;
|
|
@@ -41742,12 +41899,12 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41742
41899
|
reason: "validator-error",
|
|
41743
41900
|
error: errorMessage(err)
|
|
41744
41901
|
});
|
|
41745
|
-
return {
|
|
41902
|
+
return finish({
|
|
41746
41903
|
iterations: cycle.iterations,
|
|
41747
41904
|
finalFindings: cycle.findings,
|
|
41748
41905
|
exitReason: "validator-error",
|
|
41749
41906
|
costUsd: totalCostUsd
|
|
41750
|
-
};
|
|
41907
|
+
});
|
|
41751
41908
|
}
|
|
41752
41909
|
logger?.warn("findings.cycle", "validator retry", {
|
|
41753
41910
|
storyId,
|
|
@@ -42084,11 +42241,9 @@ function buildMeta(f, originalSeverity) {
|
|
|
42084
42241
|
function findingCategory(f) {
|
|
42085
42242
|
return "category" in f && f.category ? f.category : undefined;
|
|
42086
42243
|
}
|
|
42087
|
-
function deriveFixTargetForReviewFinding(category, source) {
|
|
42088
|
-
|
|
42089
|
-
|
|
42090
|
-
}
|
|
42091
|
-
return categoryToFixTarget(category);
|
|
42244
|
+
function deriveFixTargetForReviewFinding(category, source, file3, isTestFile3) {
|
|
42245
|
+
const base = source === "semantic-review" || source === "semantic-debate-review" ? "source" : categoryToFixTarget(category);
|
|
42246
|
+
return resolveFixTarget({ base, file: file3, isTestFile: isTestFile3 });
|
|
42092
42247
|
}
|
|
42093
42248
|
function llmFindingToReviewFinding(f, opts = {}) {
|
|
42094
42249
|
const category = findingCategory(f);
|
|
@@ -42105,7 +42260,7 @@ function llmFindingToReviewFinding(f, opts = {}) {
|
|
|
42105
42260
|
result.category = category;
|
|
42106
42261
|
if (source)
|
|
42107
42262
|
result.source = source;
|
|
42108
|
-
result.fixTarget = deriveFixTargetForReviewFinding(category, source);
|
|
42263
|
+
result.fixTarget = deriveFixTargetForReviewFinding(category, source, f.file, opts.isTestFile);
|
|
42109
42264
|
const meta3 = buildMeta(f, f.severity !== narrowed ? f.severity : undefined);
|
|
42110
42265
|
if (meta3)
|
|
42111
42266
|
result.meta = meta3;
|
|
@@ -42275,7 +42430,7 @@ var package_default;
|
|
|
42275
42430
|
var init_package = __esm(() => {
|
|
42276
42431
|
package_default = {
|
|
42277
42432
|
name: "@nathapp/nax",
|
|
42278
|
-
version: "0.
|
|
42433
|
+
version: "0.75.0",
|
|
42279
42434
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
42280
42435
|
type: "module",
|
|
42281
42436
|
bin: {
|
|
@@ -42378,8 +42533,8 @@ var init_version = __esm(() => {
|
|
|
42378
42533
|
NAX_VERSION = package_default.version;
|
|
42379
42534
|
NAX_COMMIT = (() => {
|
|
42380
42535
|
try {
|
|
42381
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
42382
|
-
return "
|
|
42536
|
+
if (/^[0-9a-f]{6,10}$/.test("05b2a123"))
|
|
42537
|
+
return "05b2a123";
|
|
42383
42538
|
} catch {}
|
|
42384
42539
|
try {
|
|
42385
42540
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -42785,12 +42940,12 @@ async function runAdversarialReview(opts) {
|
|
|
42785
42940
|
} = classifyRecurrence(allFindings, priorAdversarialIterations ?? [], recurrenceCfg, testFileMatch, threshold);
|
|
42786
42941
|
const advisoryFindings = [...advisoryOnly, ...demoted];
|
|
42787
42942
|
const advisoryReviewFindings = [
|
|
42788
|
-
...llmFindingsToReviewFindings(advisoryOnly, { source: "adversarial-review" }),
|
|
42789
|
-
...tagCoverageGap(llmFindingsToReviewFindings(demoted, { source: "adversarial-review" }))
|
|
42943
|
+
...llmFindingsToReviewFindings(advisoryOnly, { source: "adversarial-review", isTestFile: testFileMatch }),
|
|
42944
|
+
...tagCoverageGap(llmFindingsToReviewFindings(demoted, { source: "adversarial-review", isTestFile: testFileMatch }))
|
|
42790
42945
|
];
|
|
42791
42946
|
const advisoryFindingsAsFindings = [
|
|
42792
|
-
...toAdversarialReviewFindings(advisoryOnly),
|
|
42793
|
-
...tagCoverageGap(toAdversarialReviewFindings(demoted))
|
|
42947
|
+
...toAdversarialReviewFindings(advisoryOnly, { isTestFile: testFileMatch }),
|
|
42948
|
+
...tagCoverageGap(toAdversarialReviewFindings(demoted, { isTestFile: testFileMatch }))
|
|
42794
42949
|
];
|
|
42795
42950
|
const acDropped = opResult.acDropped ?? [];
|
|
42796
42951
|
let diffFiles;
|
|
@@ -42870,7 +43025,7 @@ async function runAdversarialReview(opts) {
|
|
|
42870
43025
|
blockingThreshold: threshold,
|
|
42871
43026
|
result: {
|
|
42872
43027
|
passed: false,
|
|
42873
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review" })
|
|
43028
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review", isTestFile: testFileMatch })
|
|
42874
43029
|
},
|
|
42875
43030
|
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
42876
43031
|
diffAvailable,
|
|
@@ -42887,7 +43042,7 @@ ${formatFindings(blockingFindings)}` : "Adversarial review failed (no findings)"
|
|
|
42887
43042
|
exitCode: 1,
|
|
42888
43043
|
output,
|
|
42889
43044
|
durationMs,
|
|
42890
|
-
findings: blockingFindings.length > 0 ? toAdversarialReviewFindings(blockingFindings) : undefined,
|
|
43045
|
+
findings: blockingFindings.length > 0 ? toAdversarialReviewFindings(blockingFindings, { isTestFile: testFileMatch }) : undefined,
|
|
42891
43046
|
advisoryFindings: advisoryFindings.length > 0 ? advisoryFindingsAsFindings : undefined,
|
|
42892
43047
|
cost: llmCost
|
|
42893
43048
|
};
|
|
@@ -42895,7 +43050,7 @@ ${formatFindings(blockingFindings)}` : "Adversarial review failed (no findings)"
|
|
|
42895
43050
|
if (!opResult.passed && acDropped.length > 0) {
|
|
42896
43051
|
const allHallucinated = acDropped.every((d) => d.code === "ac_quote_not_substring");
|
|
42897
43052
|
if (allHallucinated) {
|
|
42898
|
-
const demotedFindings = toAdversarialReviewFindings(acDropped.map((d) => ({ ...d.finding, severity: "warning", acQuote: undefined, acIndex: undefined })));
|
|
43053
|
+
const demotedFindings = toAdversarialReviewFindings(acDropped.map((d) => ({ ...d.finding, severity: "warning", acQuote: undefined, acIndex: undefined })), { isTestFile: testFileMatch });
|
|
42899
43054
|
const existingAdvisory = advisoryFindings.length > 0 ? advisoryFindingsAsFindings : [];
|
|
42900
43055
|
const allAdvisory = [...existingAdvisory, ...demotedFindings];
|
|
42901
43056
|
logger?.warn("review", "Adversarial review passed: all blocking findings discarded as hallucinated AC quotes", {
|
|
@@ -42983,7 +43138,7 @@ ${dropSummary}`,
|
|
|
42983
43138
|
blockingThreshold: threshold,
|
|
42984
43139
|
result: {
|
|
42985
43140
|
passed: true,
|
|
42986
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review" })
|
|
43141
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review", isTestFile: testFileMatch })
|
|
42987
43142
|
},
|
|
42988
43143
|
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
42989
43144
|
diffAvailable,
|
|
@@ -43488,7 +43643,8 @@ async function runSemanticDebate(opts) {
|
|
|
43488
43643
|
prompt,
|
|
43489
43644
|
productionExcludePatterns,
|
|
43490
43645
|
blockingThreshold,
|
|
43491
|
-
createDebateRunner
|
|
43646
|
+
createDebateRunner,
|
|
43647
|
+
isTestFile: isTestFile3
|
|
43492
43648
|
} = opts;
|
|
43493
43649
|
const logger = getSafeLogger();
|
|
43494
43650
|
const configuredStageConfig = naxConfig.debate?.stages.review;
|
|
@@ -43564,9 +43720,9 @@ async function runSemanticDebate(opts) {
|
|
|
43564
43720
|
blockingThreshold: debateThreshold,
|
|
43565
43721
|
result: {
|
|
43566
43722
|
passed: false,
|
|
43567
|
-
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review" })
|
|
43723
|
+
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review", isTestFile: isTestFile3 })
|
|
43568
43724
|
},
|
|
43569
|
-
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review" }) : undefined
|
|
43725
|
+
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review", isTestFile: isTestFile3 }) : undefined
|
|
43570
43726
|
});
|
|
43571
43727
|
return {
|
|
43572
43728
|
check: "semantic",
|
|
@@ -43577,8 +43733,8 @@ async function runSemanticDebate(opts) {
|
|
|
43577
43733
|
|
|
43578
43734
|
${formatFindings2(debateBlocking)}`,
|
|
43579
43735
|
durationMs,
|
|
43580
|
-
findings: toReviewFindings(debateBlocking),
|
|
43581
|
-
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory) : undefined,
|
|
43736
|
+
findings: toReviewFindings(debateBlocking, { isTestFile: isTestFile3 }),
|
|
43737
|
+
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory, { isTestFile: isTestFile3 }) : undefined,
|
|
43582
43738
|
cost: debateCost
|
|
43583
43739
|
};
|
|
43584
43740
|
}
|
|
@@ -43596,9 +43752,9 @@ ${formatFindings2(debateBlocking)}`,
|
|
|
43596
43752
|
blockingThreshold: debateThreshold,
|
|
43597
43753
|
result: {
|
|
43598
43754
|
passed: true,
|
|
43599
|
-
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review" })
|
|
43755
|
+
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review", isTestFile: isTestFile3 })
|
|
43600
43756
|
},
|
|
43601
|
-
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review" }) : undefined
|
|
43757
|
+
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review", isTestFile: isTestFile3 }) : undefined
|
|
43602
43758
|
});
|
|
43603
43759
|
return {
|
|
43604
43760
|
check: "semantic",
|
|
@@ -43607,7 +43763,7 @@ ${formatFindings2(debateBlocking)}`,
|
|
|
43607
43763
|
exitCode: 0,
|
|
43608
43764
|
output: "Semantic review passed (debate, all findings were advisory \u2014 below blocking threshold)",
|
|
43609
43765
|
durationMs,
|
|
43610
|
-
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory) : undefined,
|
|
43766
|
+
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory, { isTestFile: isTestFile3 }) : undefined,
|
|
43611
43767
|
cost: debateCost
|
|
43612
43768
|
};
|
|
43613
43769
|
}
|
|
@@ -43622,9 +43778,9 @@ ${formatFindings2(debateBlocking)}`,
|
|
|
43622
43778
|
blockingThreshold: debateThreshold,
|
|
43623
43779
|
result: {
|
|
43624
43780
|
passed: true,
|
|
43625
|
-
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review" })
|
|
43781
|
+
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review", isTestFile: isTestFile3 })
|
|
43626
43782
|
},
|
|
43627
|
-
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review" }) : undefined
|
|
43783
|
+
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review", isTestFile: isTestFile3 }) : undefined
|
|
43628
43784
|
});
|
|
43629
43785
|
return {
|
|
43630
43786
|
check: "semantic",
|
|
@@ -43633,7 +43789,7 @@ ${formatFindings2(debateBlocking)}`,
|
|
|
43633
43789
|
exitCode: 0,
|
|
43634
43790
|
output: "Semantic review passed",
|
|
43635
43791
|
durationMs,
|
|
43636
|
-
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory) : undefined,
|
|
43792
|
+
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory, { isTestFile: isTestFile3 }) : undefined,
|
|
43637
43793
|
cost: debateCost
|
|
43638
43794
|
};
|
|
43639
43795
|
}
|
|
@@ -43679,10 +43835,13 @@ async function runSemanticReview(opts) {
|
|
|
43679
43835
|
contextBundle,
|
|
43680
43836
|
projectDir,
|
|
43681
43837
|
naxIgnoreIndex,
|
|
43682
|
-
runtime
|
|
43838
|
+
runtime,
|
|
43839
|
+
resolvedTestPatterns
|
|
43683
43840
|
} = opts;
|
|
43684
43841
|
const startTime = Date.now();
|
|
43685
43842
|
const logger = getSafeLogger();
|
|
43843
|
+
const testFilePatterns = resolvedTestPatterns?.regex ?? [];
|
|
43844
|
+
const testFileMatch = (file3) => testFilePatterns.some((re) => re.test(file3));
|
|
43686
43845
|
if (featureName === undefined) {
|
|
43687
43846
|
logger?.debug("semantic", "featureName missing \u2014 semantic session name will not include feature", {
|
|
43688
43847
|
storyId: story.id
|
|
@@ -43815,6 +43974,7 @@ async function runSemanticReview(opts) {
|
|
|
43815
43974
|
prompt,
|
|
43816
43975
|
productionExcludePatterns: excludePatterns,
|
|
43817
43976
|
blockingThreshold,
|
|
43977
|
+
isTestFile: testFileMatch,
|
|
43818
43978
|
createDebateRunner: _semanticDeps.createDebateRunner
|
|
43819
43979
|
});
|
|
43820
43980
|
}
|
|
@@ -43974,9 +44134,9 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
43974
44134
|
blockingThreshold: threshold,
|
|
43975
44135
|
result: {
|
|
43976
44136
|
passed: false,
|
|
43977
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review" })
|
|
44137
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review", isTestFile: testFileMatch })
|
|
43978
44138
|
},
|
|
43979
|
-
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review" }) : undefined
|
|
44139
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
43980
44140
|
});
|
|
43981
44141
|
return {
|
|
43982
44142
|
check: "semantic",
|
|
@@ -43985,8 +44145,8 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
43985
44145
|
exitCode: 1,
|
|
43986
44146
|
output,
|
|
43987
44147
|
durationMs,
|
|
43988
|
-
findings: toReviewFindings(blockingFindings),
|
|
43989
|
-
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings) : undefined,
|
|
44148
|
+
findings: toReviewFindings(blockingFindings, { isTestFile: testFileMatch }),
|
|
44149
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
43990
44150
|
cost: llmCost
|
|
43991
44151
|
};
|
|
43992
44152
|
}
|
|
@@ -44006,7 +44166,7 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
44006
44166
|
passed: false,
|
|
44007
44167
|
blockingThreshold: threshold,
|
|
44008
44168
|
result: { passed: false, findings: [] },
|
|
44009
|
-
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review" }) : undefined
|
|
44169
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
44010
44170
|
});
|
|
44011
44171
|
return {
|
|
44012
44172
|
check: "semantic",
|
|
@@ -44015,7 +44175,7 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
44015
44175
|
exitCode: 1,
|
|
44016
44176
|
output: 'Semantic review failed: blocking finding(s) were dropped \u2014 acIndex was missing or out of range. The model emitted "passed: false" without valid AC attribution.',
|
|
44017
44177
|
durationMs,
|
|
44018
|
-
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings) : undefined,
|
|
44178
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
44019
44179
|
cost: llmCost
|
|
44020
44180
|
};
|
|
44021
44181
|
}
|
|
@@ -44032,9 +44192,9 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
44032
44192
|
blockingThreshold: threshold,
|
|
44033
44193
|
result: {
|
|
44034
44194
|
passed: true,
|
|
44035
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review" })
|
|
44195
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review", isTestFile: testFileMatch })
|
|
44036
44196
|
},
|
|
44037
|
-
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review" }) : undefined
|
|
44197
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
44038
44198
|
});
|
|
44039
44199
|
return {
|
|
44040
44200
|
check: "semantic",
|
|
@@ -44043,7 +44203,7 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
44043
44203
|
exitCode: 0,
|
|
44044
44204
|
output: allFindings.length === 0 ? "Semantic review passed" : "Semantic review passed (all findings were advisory \u2014 below blocking threshold)",
|
|
44045
44205
|
durationMs,
|
|
44046
|
-
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings) : undefined,
|
|
44206
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
44047
44207
|
cost: llmCost
|
|
44048
44208
|
};
|
|
44049
44209
|
}
|
|
@@ -45339,6 +45499,43 @@ var init_rectifier_builder = __esm(() => {
|
|
|
45339
45499
|
];
|
|
45340
45500
|
});
|
|
45341
45501
|
|
|
45502
|
+
// src/prompts/builders/timeout-retry-builder.ts
|
|
45503
|
+
function formatDuration2(ms) {
|
|
45504
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
45505
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
45506
|
+
const seconds = totalSeconds % 60;
|
|
45507
|
+
if (minutes === 0)
|
|
45508
|
+
return `${seconds}s`;
|
|
45509
|
+
return `${minutes}m ${seconds}s`;
|
|
45510
|
+
}
|
|
45511
|
+
function timeoutRetry(input) {
|
|
45512
|
+
const { prompt, changedFiles, elapsedMs, attempt } = input;
|
|
45513
|
+
const duration3 = formatDuration2(elapsedMs);
|
|
45514
|
+
const attemptNumber = attempt + 1;
|
|
45515
|
+
if (changedFiles.length === 0) {
|
|
45516
|
+
return `The previous attempt hit a timeout after ${elapsedMs}ms (${duration3}) with no file changes on disk.
|
|
45517
|
+
This is attempt ${attemptNumber} of the same story \u2014 the previous attempt left nothing behind, so the approach was wrong.
|
|
45518
|
+
Change your approach: pick a narrower scope, fewer file edits, or a different angle on the acceptance criteria.
|
|
45519
|
+
|
|
45520
|
+
---
|
|
45521
|
+
|
|
45522
|
+
${prompt}`;
|
|
45523
|
+
}
|
|
45524
|
+
const fileList = changedFiles.map((p) => `- ${p}`).join(`
|
|
45525
|
+
`);
|
|
45526
|
+
return `The previous attempt hit a timeout after ${elapsedMs}ms (${duration3}), but left these files on disk:
|
|
45527
|
+
|
|
45528
|
+
${fileList}
|
|
45529
|
+
|
|
45530
|
+
This is attempt ${attemptNumber} of the same story \u2014 continue from the existing state above.
|
|
45531
|
+
Read the files listed, pick up where the previous attempt stopped, and finish the story.
|
|
45532
|
+
Do NOT delete or revert the existing work; treat the working tree as the starting point.
|
|
45533
|
+
|
|
45534
|
+
---
|
|
45535
|
+
|
|
45536
|
+
${prompt}`;
|
|
45537
|
+
}
|
|
45538
|
+
|
|
45342
45539
|
// src/prompts/builders/one-shot-builder.ts
|
|
45343
45540
|
class OneShotPromptBuilder {
|
|
45344
45541
|
acc = new SectionAccumulator;
|
|
@@ -45501,7 +45698,7 @@ No acceptance criterion may use a deprecated verification tag (\`[grep]\`, \`[fi
|
|
|
45501
45698
|
Review the draft with a strict self-audit mindset. Re-read the codebase context and compare the PRD against it. Focus only on the issues below, then rewrite the PRD if needed.
|
|
45502
45699
|
|
|
45503
45700
|
#### spec-ac-preservation
|
|
45504
|
-
Enumerate every acceptance criterion the spec states. Confirm each one appears in some story's acceptanceCriteria \u2014 never drop a spec AC during this audit. If an AC looks unsupported by the current codebase, keep it: the story may be adding that capability.
|
|
45701
|
+
Enumerate every acceptance criterion the spec states. Confirm each one appears in some story's acceptanceCriteria \u2014 never drop a spec AC during this audit. If an AC looks unsupported by the current codebase, keep it: the story may be adding that capability. An AC carrying a deprecated \`[grep]\`/\`[file]\`/\`[verbatim]\` tag is a file-content check, not a runtime behaviour \u2014 rewrite it as the behaviour it was meant to prove and drop the tag; that rewrite is required, not a dropped AC.
|
|
45505
45702
|
|
|
45506
45703
|
#### ac-testable
|
|
45507
45704
|
For each acceptance criterion, ask whether the assertion is observable through a return value, exception, log output, file content, or state change. If any AC is not directly testable, rewrite it so it is observable.
|
|
@@ -45553,23 +45750,6 @@ For each one:
|
|
|
45553
45750
|
- Remove any shell-command patterns (\`grep -\`, \`wc\`, pipe \`|\` inside backticks). Express the same invariant as an assertion on the runtime value.
|
|
45554
45751
|
- Do not remove or weaken acceptance criteria that are already correct.
|
|
45555
45752
|
|
|
45556
|
-
Write the corrected PRD to this file path: ${outputFilePath}
|
|
45557
|
-
Do not output the PRD in chat. After writing the file, reply with a brief text confirmation only.`;
|
|
45558
|
-
}
|
|
45559
|
-
buildVerbatimRepair(missingAcs, outputFilePath) {
|
|
45560
|
-
const list = missingAcs.map((ac) => `- ${ac}`).join(`
|
|
45561
|
-
`);
|
|
45562
|
-
return `Your revised PRD dropped or altered acceptance criteria the spec marked \`[verbatim]\`. These are load-bearing executable checks (greps, file-existence checks, regex/count assertions, or architectural invariants) and MUST survive character-for-character \u2014 paraphrasing destroys the verification mechanism.
|
|
45563
|
-
|
|
45564
|
-
The following \`[verbatim]\` spec acceptance criteria are missing or altered in the PRD:
|
|
45565
|
-
|
|
45566
|
-
${list}
|
|
45567
|
-
|
|
45568
|
-
For each one:
|
|
45569
|
-
- Add it to the \`acceptanceCriteria\` array of the single most relevant user story.
|
|
45570
|
-
- Preserve every backtick-quoted command, file path, regex, and count exactly as written in the spec. Do not paraphrase, retag, split, or move them into a description.
|
|
45571
|
-
- Do not remove or weaken any acceptance criteria that are already correct.
|
|
45572
|
-
|
|
45573
45753
|
Write the corrected PRD to this file path: ${outputFilePath}
|
|
45574
45754
|
Do not output the PRD in chat. After writing the file, reply with a brief text confirmation only.`;
|
|
45575
45755
|
}
|
|
@@ -46310,10 +46490,17 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46310
46490
|
hopBodyInput
|
|
46311
46491
|
} = ctx;
|
|
46312
46492
|
const stage = pipelineStage ?? "run";
|
|
46493
|
+
let preAttemptGitRefPromise;
|
|
46494
|
+
let priorHopStartedAt;
|
|
46313
46495
|
return async (agentName, hopBundle, hopKind, resolvedRunOptions) => {
|
|
46314
46496
|
const logger = getLogger();
|
|
46315
46497
|
let workingBundle = hopBundle;
|
|
46316
46498
|
let prompt = resolvedRunOptions.prompt;
|
|
46499
|
+
const elapsedSincePriorHop = priorHopStartedAt ? Date.now() - priorHopStartedAt : 0;
|
|
46500
|
+
priorHopStartedAt = Date.now();
|
|
46501
|
+
if (hopKind.kind === "primary" && !preAttemptGitRefPromise) {
|
|
46502
|
+
preAttemptGitRefPromise = _buildHopCallbackDeps.captureGitRef(workdir);
|
|
46503
|
+
}
|
|
46317
46504
|
if (hopKind.kind === "swap" && hopBundle) {
|
|
46318
46505
|
workingBundle = _buildHopCallbackDeps.rebuildForAgent(hopBundle, agentName, hopKind.failure, story.id);
|
|
46319
46506
|
if (projectDir && featureName && workingBundle.manifest.rebuildInfo) {
|
|
@@ -46342,6 +46529,17 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46342
46529
|
if (hopKind.kind === "swap" && sessionId) {
|
|
46343
46530
|
sessionManager.handoff?.(sessionId, agentName, hopKind.failure.outcome);
|
|
46344
46531
|
}
|
|
46532
|
+
if (hopKind.kind === "timeout-retry") {
|
|
46533
|
+
const preAttemptGitRef = preAttemptGitRefPromise ? await preAttemptGitRefPromise : undefined;
|
|
46534
|
+
const changedFiles = preAttemptGitRef ? await _buildHopCallbackDeps.captureWorkingTreeChanges(workdir, preAttemptGitRef) : [];
|
|
46535
|
+
const elapsedMs = elapsedSincePriorHop;
|
|
46536
|
+
prompt = _buildHopCallbackDeps.timeoutRetry({
|
|
46537
|
+
prompt: resolvedRunOptions.prompt,
|
|
46538
|
+
changedFiles,
|
|
46539
|
+
elapsedMs,
|
|
46540
|
+
attempt: hopKind.attempt
|
|
46541
|
+
});
|
|
46542
|
+
}
|
|
46345
46543
|
const contextToolRuntime = workingBundle ? _buildHopCallbackDeps.createContextToolRuntime({
|
|
46346
46544
|
bundle: workingBundle,
|
|
46347
46545
|
story,
|
|
@@ -46400,6 +46598,7 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46400
46598
|
signal: resolvedRunOptions.abortSignal
|
|
46401
46599
|
});
|
|
46402
46600
|
}
|
|
46601
|
+
let timedOut = false;
|
|
46403
46602
|
try {
|
|
46404
46603
|
const send = (turnPrompt) => agentManager.runAsSession(agentName, handle, turnPrompt, {
|
|
46405
46604
|
storyId: story.id,
|
|
@@ -46417,9 +46616,12 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46417
46616
|
...maxInteractionTurns !== undefined ? { maxTurns: maxInteractionTurns } : {}
|
|
46418
46617
|
});
|
|
46419
46618
|
const turnResult = hopBody ? await hopBody(prompt, { send, input: hopBodyInput }) : await send(prompt);
|
|
46619
|
+
if (turnResult.timedOut)
|
|
46620
|
+
timedOut = true;
|
|
46420
46621
|
return { result: turnResultToAgentResult(turnResult), bundle: workingBundle, prompt };
|
|
46421
46622
|
} catch (err) {
|
|
46422
46623
|
const sessionFailure = err instanceof SessionFailureError ? err.adapterFailure : undefined;
|
|
46624
|
+
timedOut = sessionFailure?.outcome === "fail-timeout";
|
|
46423
46625
|
const turnError = err instanceof SessionTurnError ? err : undefined;
|
|
46424
46626
|
const errMessage = err instanceof Error ? err.message : String(err);
|
|
46425
46627
|
return {
|
|
@@ -46441,7 +46643,7 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46441
46643
|
prompt
|
|
46442
46644
|
};
|
|
46443
46645
|
} finally {
|
|
46444
|
-
if (hopKind.kind !== "stale-retry" && !resolvedRunOptions.keepOpen) {
|
|
46646
|
+
if (hopKind.kind !== "stale-retry" && (!resolvedRunOptions.keepOpen || timedOut)) {
|
|
46445
46647
|
await sessionManager.closeSession(handle);
|
|
46446
46648
|
}
|
|
46447
46649
|
}
|
|
@@ -46456,10 +46658,14 @@ var init_build_hop_callback = __esm(() => {
|
|
|
46456
46658
|
init_manifest_store();
|
|
46457
46659
|
init_logger2();
|
|
46458
46660
|
init_prompts();
|
|
46661
|
+
init_git();
|
|
46459
46662
|
_buildHopCallbackDeps = {
|
|
46460
46663
|
rebuildForAgent: (prior, newAgentId, failure, storyId) => new ContextOrchestrator([]).rebuildForAgent(prior, { newAgentId, failure, storyId }),
|
|
46461
46664
|
writeRebuildManifest,
|
|
46462
|
-
createContextToolRuntime
|
|
46665
|
+
createContextToolRuntime,
|
|
46666
|
+
captureGitRef,
|
|
46667
|
+
captureWorkingTreeChanges,
|
|
46668
|
+
timeoutRetry: (input) => timeoutRetry(input)
|
|
46463
46669
|
};
|
|
46464
46670
|
});
|
|
46465
46671
|
|
|
@@ -46664,17 +46870,10 @@ async function callOp(ctx, op, input) {
|
|
|
46664
46870
|
effective = { ...turn, output: fileContent };
|
|
46665
46871
|
}
|
|
46666
46872
|
}
|
|
46667
|
-
if (!effective.output?.trim()
|
|
46668
|
-
|
|
46669
|
-
|
|
46670
|
-
adapterFailure:
|
|
46671
|
-
outcome: "fail-stale",
|
|
46672
|
-
category: "availability",
|
|
46673
|
-
retriable: true,
|
|
46674
|
-
message: `[${op.name}] agent returned no output`,
|
|
46675
|
-
reason: "empty-output"
|
|
46676
|
-
}
|
|
46677
|
-
};
|
|
46873
|
+
if (!effective.output?.trim()) {
|
|
46874
|
+
const failure = classifyEmptyOutputFailure(effective);
|
|
46875
|
+
if (failure)
|
|
46876
|
+
return { ...effective, adapterFailure: failure };
|
|
46678
46877
|
}
|
|
46679
46878
|
return effective;
|
|
46680
46879
|
};
|
|
@@ -52249,11 +52448,142 @@ var init_cli = __esm(() => {
|
|
|
52249
52448
|
CLIConfigSchema = exports_external.object({}).passthrough();
|
|
52250
52449
|
});
|
|
52251
52450
|
|
|
52451
|
+
// src/interaction/plugins/telegram-format.ts
|
|
52452
|
+
function buildHeader(request) {
|
|
52453
|
+
const emoji3 = getStageEmoji(request.stage);
|
|
52454
|
+
let text = `${emoji3} *${request.stage.toUpperCase()}*
|
|
52455
|
+
`;
|
|
52456
|
+
text += `*Feature:* ${request.featureName}
|
|
52457
|
+
`;
|
|
52458
|
+
if (request.storyId) {
|
|
52459
|
+
text += `*Story:* ${request.storyId}
|
|
52460
|
+
`;
|
|
52461
|
+
}
|
|
52462
|
+
text += `
|
|
52463
|
+
`;
|
|
52464
|
+
return text;
|
|
52465
|
+
}
|
|
52466
|
+
function buildBody(request) {
|
|
52467
|
+
let text = `${sanitizeMarkdown(request.summary)}
|
|
52468
|
+
`;
|
|
52469
|
+
if (request.detail) {
|
|
52470
|
+
text += `
|
|
52471
|
+
${sanitizeMarkdown(request.detail)}
|
|
52472
|
+
`;
|
|
52473
|
+
}
|
|
52474
|
+
if (request.options && request.options.length > 0) {
|
|
52475
|
+
text += `
|
|
52476
|
+
*Options:*
|
|
52477
|
+
`;
|
|
52478
|
+
for (const opt of request.options) {
|
|
52479
|
+
const desc = opt.description ? ` - ${sanitizeMarkdown(opt.description)}` : "";
|
|
52480
|
+
text += ` - ${opt.label}${desc}
|
|
52481
|
+
`;
|
|
52482
|
+
}
|
|
52483
|
+
}
|
|
52484
|
+
if (request.timeout) {
|
|
52485
|
+
const timeoutSec = Math.floor(request.timeout / 1000);
|
|
52486
|
+
text += `
|
|
52487
|
+
\u23F1 Timeout: ${timeoutSec}s | Fallback: ${request.fallback}`;
|
|
52488
|
+
}
|
|
52489
|
+
return text;
|
|
52490
|
+
}
|
|
52491
|
+
function sanitizeMarkdown(text) {
|
|
52492
|
+
return text.replace(/\\(?=[_*`\[])/g, "\\\\").replace(/_/g, "\\_").replace(/`/g, "\\`").replace(/\*/g, "\\*").replace(/\[/g, "\\[");
|
|
52493
|
+
}
|
|
52494
|
+
function splitText(text, maxChars) {
|
|
52495
|
+
if (text.length <= maxChars)
|
|
52496
|
+
return [text];
|
|
52497
|
+
const chunks = [];
|
|
52498
|
+
let remaining = text;
|
|
52499
|
+
while (remaining.length > maxChars) {
|
|
52500
|
+
const slice = remaining.slice(0, maxChars);
|
|
52501
|
+
const lastNewline = slice.lastIndexOf(`
|
|
52502
|
+
`);
|
|
52503
|
+
if (lastNewline > maxChars * 0.5) {
|
|
52504
|
+
chunks.push(remaining.slice(0, lastNewline));
|
|
52505
|
+
remaining = remaining.slice(lastNewline + 1);
|
|
52506
|
+
} else {
|
|
52507
|
+
chunks.push(slice);
|
|
52508
|
+
remaining = remaining.slice(maxChars);
|
|
52509
|
+
}
|
|
52510
|
+
}
|
|
52511
|
+
if (remaining.length > 0)
|
|
52512
|
+
chunks.push(remaining);
|
|
52513
|
+
return chunks;
|
|
52514
|
+
}
|
|
52515
|
+
function buildKeyboard(request) {
|
|
52516
|
+
switch (request.type) {
|
|
52517
|
+
case "confirm":
|
|
52518
|
+
return [
|
|
52519
|
+
[
|
|
52520
|
+
{ text: "\u2705 Approve", callback_data: `${request.id}:approve` },
|
|
52521
|
+
{ text: "\u274C Reject", callback_data: `${request.id}:reject` }
|
|
52522
|
+
],
|
|
52523
|
+
[
|
|
52524
|
+
{ text: "\u23ED Skip", callback_data: `${request.id}:skip` },
|
|
52525
|
+
{ text: "\uD83D\uDED1 Abort", callback_data: `${request.id}:abort` }
|
|
52526
|
+
]
|
|
52527
|
+
];
|
|
52528
|
+
case "choose": {
|
|
52529
|
+
if (!request.options || request.options.length === 0)
|
|
52530
|
+
return null;
|
|
52531
|
+
const rows = [];
|
|
52532
|
+
for (const opt of request.options) {
|
|
52533
|
+
rows.push([{ text: opt.label, callback_data: `${request.id}:choose:${opt.key}` }]);
|
|
52534
|
+
}
|
|
52535
|
+
rows.push([
|
|
52536
|
+
{ text: "\u23ED Skip", callback_data: `${request.id}:skip` },
|
|
52537
|
+
{ text: "\uD83D\uDED1 Abort", callback_data: `${request.id}:abort` }
|
|
52538
|
+
]);
|
|
52539
|
+
return rows;
|
|
52540
|
+
}
|
|
52541
|
+
case "review":
|
|
52542
|
+
return [
|
|
52543
|
+
[
|
|
52544
|
+
{ text: "\u2705 Approve", callback_data: `${request.id}:approve` },
|
|
52545
|
+
{ text: "\u274C Reject", callback_data: `${request.id}:reject` }
|
|
52546
|
+
],
|
|
52547
|
+
[
|
|
52548
|
+
{ text: "\u23ED Skip", callback_data: `${request.id}:skip` },
|
|
52549
|
+
{ text: "\uD83D\uDED1 Abort", callback_data: `${request.id}:abort` }
|
|
52550
|
+
]
|
|
52551
|
+
];
|
|
52552
|
+
default:
|
|
52553
|
+
return null;
|
|
52554
|
+
}
|
|
52555
|
+
}
|
|
52556
|
+
function getStageEmoji(stage) {
|
|
52557
|
+
switch (stage) {
|
|
52558
|
+
case "pre-flight":
|
|
52559
|
+
return "\uD83D\uDE80";
|
|
52560
|
+
case "execution":
|
|
52561
|
+
return "\u2699\uFE0F";
|
|
52562
|
+
case "review":
|
|
52563
|
+
return "\uD83D\uDD0D";
|
|
52564
|
+
case "merge":
|
|
52565
|
+
return "\uD83D\uDD00";
|
|
52566
|
+
case "cost":
|
|
52567
|
+
return "\uD83D\uDCB0";
|
|
52568
|
+
default:
|
|
52569
|
+
return "\uD83D\uDCCC";
|
|
52570
|
+
}
|
|
52571
|
+
}
|
|
52572
|
+
var MAX_MESSAGE_CHARS = 4000;
|
|
52573
|
+
|
|
52252
52574
|
// src/interaction/plugins/telegram.ts
|
|
52253
|
-
|
|
52575
|
+
function normalizeChatId(raw) {
|
|
52576
|
+
const chatId = raw.trim();
|
|
52577
|
+
return { chatId, unmatchable: !NUMERIC_CHAT_ID.test(chatId) };
|
|
52578
|
+
}
|
|
52579
|
+
var _telegramPluginDeps, CALLBACK_API_TIMEOUT_MS = 4000, NUMERIC_CHAT_ID, TelegramConfigSchema, TelegramInteractionPlugin;
|
|
52254
52580
|
var init_telegram = __esm(() => {
|
|
52255
52581
|
init_zod();
|
|
52256
52582
|
init_logger2();
|
|
52583
|
+
_telegramPluginDeps = {
|
|
52584
|
+
fetch: globalThis.fetch.bind(globalThis)
|
|
52585
|
+
};
|
|
52586
|
+
NUMERIC_CHAT_ID = /^-?\d+$/;
|
|
52257
52587
|
TelegramConfigSchema = exports_external.object({
|
|
52258
52588
|
botToken: exports_external.string().optional(),
|
|
52259
52589
|
chatId: exports_external.string().optional()
|
|
@@ -52267,6 +52597,7 @@ var init_telegram = __esm(() => {
|
|
|
52267
52597
|
lastUpdateId = 0;
|
|
52268
52598
|
backoffMs = 1000;
|
|
52269
52599
|
maxBackoffMs = 30000;
|
|
52600
|
+
static MAX_DRAIN_PAGES = 10;
|
|
52270
52601
|
static INTERACTIVE_REQUEST_TYPES = new Set([
|
|
52271
52602
|
"confirm",
|
|
52272
52603
|
"choose",
|
|
@@ -52276,10 +52607,29 @@ var init_telegram = __esm(() => {
|
|
|
52276
52607
|
async init(config2) {
|
|
52277
52608
|
const cfg = TelegramConfigSchema.parse(config2);
|
|
52278
52609
|
this.botToken = cfg.botToken ?? process.env.NAX_TELEGRAM_TOKEN ?? process.env.TELEGRAM_BOT_TOKEN ?? null;
|
|
52279
|
-
|
|
52610
|
+
const rawChatId = cfg.chatId ?? process.env.NAX_TELEGRAM_CHAT_ID ?? null;
|
|
52611
|
+
const normalized = rawChatId === null ? null : normalizeChatId(rawChatId);
|
|
52612
|
+
this.chatId = normalized?.chatId || null;
|
|
52280
52613
|
if (!this.botToken || !this.chatId) {
|
|
52281
52614
|
throw new Error("Telegram plugin requires botToken and chatId (env: NAX_TELEGRAM_TOKEN or TELEGRAM_BOT_TOKEN, NAX_TELEGRAM_CHAT_ID)");
|
|
52282
52615
|
}
|
|
52616
|
+
if (normalized?.unmatchable) {
|
|
52617
|
+
this.logger?.warn("interaction", "Telegram chatId is not numeric \u2014 inbound updates cannot be matched, so interactive prompts will always fall back to their timeout. Use the numeric chat id (see getUpdates or @userinfobot).", { chatId: this.chatId });
|
|
52618
|
+
}
|
|
52619
|
+
}
|
|
52620
|
+
async drainBacklog() {
|
|
52621
|
+
for (let page = 0;page < TelegramInteractionPlugin.MAX_DRAIN_PAGES; page++) {
|
|
52622
|
+
const result = await this.fetchUpdates();
|
|
52623
|
+
if (!result.ok) {
|
|
52624
|
+
this.logger?.warn("interaction", "Telegram backlog drain failed \u2014 stale updates may be misread as a response");
|
|
52625
|
+
return;
|
|
52626
|
+
}
|
|
52627
|
+
if (result.rawCount === 0)
|
|
52628
|
+
return;
|
|
52629
|
+
}
|
|
52630
|
+
this.logger?.warn("interaction", "Telegram backlog drain hit page cap \u2014 stale updates may remain", {
|
|
52631
|
+
pages: TelegramInteractionPlugin.MAX_DRAIN_PAGES
|
|
52632
|
+
});
|
|
52283
52633
|
}
|
|
52284
52634
|
async destroy() {
|
|
52285
52635
|
this.pendingMessages.clear();
|
|
@@ -52288,10 +52638,13 @@ var init_telegram = __esm(() => {
|
|
|
52288
52638
|
if (!this.botToken || !this.chatId) {
|
|
52289
52639
|
throw new Error("Telegram plugin not initialized");
|
|
52290
52640
|
}
|
|
52291
|
-
|
|
52292
|
-
|
|
52293
|
-
|
|
52294
|
-
const
|
|
52641
|
+
if (TelegramInteractionPlugin.INTERACTIVE_REQUEST_TYPES.has(request.type)) {
|
|
52642
|
+
await this.drainBacklog();
|
|
52643
|
+
}
|
|
52644
|
+
const header = buildHeader(request);
|
|
52645
|
+
const keyboard = buildKeyboard(request);
|
|
52646
|
+
const body = buildBody(request);
|
|
52647
|
+
const chunks = splitText(body, MAX_MESSAGE_CHARS - header.length - 10);
|
|
52295
52648
|
try {
|
|
52296
52649
|
const sentIds = [];
|
|
52297
52650
|
for (let i = 0;i < chunks.length; i++) {
|
|
@@ -52299,7 +52652,7 @@ var init_telegram = __esm(() => {
|
|
|
52299
52652
|
const partLabel = chunks.length > 1 ? `[${i + 1}/${chunks.length}] ` : "";
|
|
52300
52653
|
const text = `${header}
|
|
52301
52654
|
${partLabel}${chunks[i]}`;
|
|
52302
|
-
const response = await fetch(`https://api.telegram.org/bot${this.botToken}/sendMessage`, {
|
|
52655
|
+
const response = await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/sendMessage`, {
|
|
52303
52656
|
method: "POST",
|
|
52304
52657
|
headers: { "Content-Type": "application/json" },
|
|
52305
52658
|
body: JSON.stringify({
|
|
@@ -52320,7 +52673,7 @@ ${partLabel}${chunks[i]}`;
|
|
|
52320
52673
|
sentIds.push(data.result.message_id);
|
|
52321
52674
|
}
|
|
52322
52675
|
if (TelegramInteractionPlugin.INTERACTIVE_REQUEST_TYPES.has(request.type)) {
|
|
52323
|
-
this.pendingMessages.set(request.id, sentIds);
|
|
52676
|
+
this.pendingMessages.set(request.id, { type: request.type, ids: sentIds });
|
|
52324
52677
|
}
|
|
52325
52678
|
} catch (err) {
|
|
52326
52679
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -52380,140 +52733,25 @@ ${partLabel}${chunks[i]}`;
|
|
|
52380
52733
|
await this.sendTimeoutMessage(requestId);
|
|
52381
52734
|
this.pendingMessages.delete(requestId);
|
|
52382
52735
|
}
|
|
52383
|
-
buildHeader(request) {
|
|
52384
|
-
const emoji3 = this.getStageEmoji(request.stage);
|
|
52385
|
-
let text = `${emoji3} *${request.stage.toUpperCase()}*
|
|
52386
|
-
`;
|
|
52387
|
-
text += `*Feature:* ${request.featureName}
|
|
52388
|
-
`;
|
|
52389
|
-
if (request.storyId) {
|
|
52390
|
-
text += `*Story:* ${request.storyId}
|
|
52391
|
-
`;
|
|
52392
|
-
}
|
|
52393
|
-
text += `
|
|
52394
|
-
`;
|
|
52395
|
-
return text;
|
|
52396
|
-
}
|
|
52397
|
-
buildBody(request) {
|
|
52398
|
-
let text = `${this.sanitizeMarkdown(request.summary)}
|
|
52399
|
-
`;
|
|
52400
|
-
if (request.detail) {
|
|
52401
|
-
text += `
|
|
52402
|
-
${this.sanitizeMarkdown(request.detail)}
|
|
52403
|
-
`;
|
|
52404
|
-
}
|
|
52405
|
-
if (request.options && request.options.length > 0) {
|
|
52406
|
-
text += `
|
|
52407
|
-
*Options:*
|
|
52408
|
-
`;
|
|
52409
|
-
for (const opt of request.options) {
|
|
52410
|
-
const desc = opt.description ? ` - ${this.sanitizeMarkdown(opt.description)}` : "";
|
|
52411
|
-
text += ` - ${opt.label}${desc}
|
|
52412
|
-
`;
|
|
52413
|
-
}
|
|
52414
|
-
}
|
|
52415
|
-
if (request.timeout) {
|
|
52416
|
-
const timeoutSec = Math.floor(request.timeout / 1000);
|
|
52417
|
-
text += `
|
|
52418
|
-
\u23F1 Timeout: ${timeoutSec}s | Fallback: ${request.fallback}`;
|
|
52419
|
-
}
|
|
52420
|
-
return text;
|
|
52421
|
-
}
|
|
52422
|
-
sanitizeMarkdown(text) {
|
|
52423
|
-
return text.replace(/\\(?=[_*`\[])/g, "\\\\").replace(/_/g, "\\_").replace(/`/g, "\\`").replace(/\*/g, "\\*").replace(/\[/g, "\\[");
|
|
52424
|
-
}
|
|
52425
|
-
splitText(text, maxChars) {
|
|
52426
|
-
if (text.length <= maxChars)
|
|
52427
|
-
return [text];
|
|
52428
|
-
const chunks = [];
|
|
52429
|
-
let remaining = text;
|
|
52430
|
-
while (remaining.length > maxChars) {
|
|
52431
|
-
const slice = remaining.slice(0, maxChars);
|
|
52432
|
-
const lastNewline = slice.lastIndexOf(`
|
|
52433
|
-
`);
|
|
52434
|
-
if (lastNewline > maxChars * 0.5) {
|
|
52435
|
-
chunks.push(remaining.slice(0, lastNewline));
|
|
52436
|
-
remaining = remaining.slice(lastNewline + 1);
|
|
52437
|
-
} else {
|
|
52438
|
-
chunks.push(slice);
|
|
52439
|
-
remaining = remaining.slice(maxChars);
|
|
52440
|
-
}
|
|
52441
|
-
}
|
|
52442
|
-
if (remaining.length > 0)
|
|
52443
|
-
chunks.push(remaining);
|
|
52444
|
-
return chunks;
|
|
52445
|
-
}
|
|
52446
|
-
buildKeyboard(request) {
|
|
52447
|
-
switch (request.type) {
|
|
52448
|
-
case "confirm":
|
|
52449
|
-
return [
|
|
52450
|
-
[
|
|
52451
|
-
{ text: "\u2705 Approve", callback_data: `${request.id}:approve` },
|
|
52452
|
-
{ text: "\u274C Reject", callback_data: `${request.id}:reject` }
|
|
52453
|
-
],
|
|
52454
|
-
[
|
|
52455
|
-
{ text: "\u23ED Skip", callback_data: `${request.id}:skip` },
|
|
52456
|
-
{ text: "\uD83D\uDED1 Abort", callback_data: `${request.id}:abort` }
|
|
52457
|
-
]
|
|
52458
|
-
];
|
|
52459
|
-
case "choose": {
|
|
52460
|
-
if (!request.options || request.options.length === 0)
|
|
52461
|
-
return null;
|
|
52462
|
-
const rows = [];
|
|
52463
|
-
for (const opt of request.options) {
|
|
52464
|
-
rows.push([{ text: opt.label, callback_data: `${request.id}:choose:${opt.key}` }]);
|
|
52465
|
-
}
|
|
52466
|
-
rows.push([
|
|
52467
|
-
{ text: "\u23ED Skip", callback_data: `${request.id}:skip` },
|
|
52468
|
-
{ text: "\uD83D\uDED1 Abort", callback_data: `${request.id}:abort` }
|
|
52469
|
-
]);
|
|
52470
|
-
return rows;
|
|
52471
|
-
}
|
|
52472
|
-
case "review":
|
|
52473
|
-
return [
|
|
52474
|
-
[
|
|
52475
|
-
{ text: "\u2705 Approve", callback_data: `${request.id}:approve` },
|
|
52476
|
-
{ text: "\u274C Reject", callback_data: `${request.id}:reject` }
|
|
52477
|
-
],
|
|
52478
|
-
[
|
|
52479
|
-
{ text: "\u23ED Skip", callback_data: `${request.id}:skip` },
|
|
52480
|
-
{ text: "\uD83D\uDED1 Abort", callback_data: `${request.id}:abort` }
|
|
52481
|
-
]
|
|
52482
|
-
];
|
|
52483
|
-
default:
|
|
52484
|
-
return null;
|
|
52485
|
-
}
|
|
52486
|
-
}
|
|
52487
|
-
getStageEmoji(stage) {
|
|
52488
|
-
switch (stage) {
|
|
52489
|
-
case "pre-flight":
|
|
52490
|
-
return "\uD83D\uDE80";
|
|
52491
|
-
case "execution":
|
|
52492
|
-
return "\u2699\uFE0F";
|
|
52493
|
-
case "review":
|
|
52494
|
-
return "\uD83D\uDD0D";
|
|
52495
|
-
case "merge":
|
|
52496
|
-
return "\uD83D\uDD00";
|
|
52497
|
-
case "cost":
|
|
52498
|
-
return "\uD83D\uDCB0";
|
|
52499
|
-
default:
|
|
52500
|
-
return "\uD83D\uDCCC";
|
|
52501
|
-
}
|
|
52502
|
-
}
|
|
52503
52736
|
async getUpdates() {
|
|
52737
|
+
const result = await this.fetchUpdates();
|
|
52738
|
+
return result.updates;
|
|
52739
|
+
}
|
|
52740
|
+
async fetchUpdates() {
|
|
52504
52741
|
if (!this.botToken)
|
|
52505
|
-
return [];
|
|
52742
|
+
return { ok: true, updates: [], rawCount: 0 };
|
|
52506
52743
|
try {
|
|
52507
52744
|
const controller = new AbortController;
|
|
52508
52745
|
const timer = setTimeout(() => controller.abort(), 8000);
|
|
52509
52746
|
let response;
|
|
52510
52747
|
try {
|
|
52511
|
-
response = await fetch(`https://api.telegram.org/bot${this.botToken}/getUpdates`, {
|
|
52748
|
+
response = await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/getUpdates`, {
|
|
52512
52749
|
method: "POST",
|
|
52513
52750
|
headers: { "Content-Type": "application/json" },
|
|
52514
52751
|
body: JSON.stringify({
|
|
52515
52752
|
offset: this.lastUpdateId + 1,
|
|
52516
|
-
timeout: 1
|
|
52753
|
+
timeout: 1,
|
|
52754
|
+
limit: 100
|
|
52517
52755
|
}),
|
|
52518
52756
|
signal: controller.signal
|
|
52519
52757
|
});
|
|
@@ -52528,17 +52766,31 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52528
52766
|
if (!data.ok || !data.result) {
|
|
52529
52767
|
throw new Error("Telegram API returned ok=false or missing result");
|
|
52530
52768
|
}
|
|
52531
|
-
const
|
|
52532
|
-
if (
|
|
52533
|
-
this.lastUpdateId = Math.max(...
|
|
52769
|
+
const raw = data.result;
|
|
52770
|
+
if (raw.length > 0) {
|
|
52771
|
+
this.lastUpdateId = Math.max(...raw.map((u) => u.update_id));
|
|
52772
|
+
}
|
|
52773
|
+
const updates = raw.filter((u) => this.isFromConfiguredChat(u));
|
|
52774
|
+
if (updates.length !== raw.length) {
|
|
52775
|
+
this.logger?.debug("interaction", "Telegram updates rejected -- not from the configured chat", {
|
|
52776
|
+
rejected: raw.length - updates.length
|
|
52777
|
+
});
|
|
52534
52778
|
}
|
|
52535
52779
|
this.backoffMs = 1000;
|
|
52536
|
-
return updates;
|
|
52780
|
+
return { ok: true, updates, rawCount: raw.length };
|
|
52537
52781
|
} catch (err) {
|
|
52538
52782
|
this.backoffMs = Math.min(this.backoffMs * 2, this.maxBackoffMs);
|
|
52539
|
-
|
|
52783
|
+
this.logger?.debug("interaction", "Telegram getUpdates failed \u2014 retrying with backoff", {
|
|
52784
|
+
error: err instanceof Error ? err.message : String(err),
|
|
52785
|
+
backoffMs: this.backoffMs
|
|
52786
|
+
});
|
|
52787
|
+
return { ok: false, updates: [], rawCount: 0 };
|
|
52540
52788
|
}
|
|
52541
52789
|
}
|
|
52790
|
+
isFromConfiguredChat(update) {
|
|
52791
|
+
const chatId = update.callback_query?.message?.chat?.id ?? update.message?.chat?.id;
|
|
52792
|
+
return chatId !== undefined && String(chatId) === this.chatId;
|
|
52793
|
+
}
|
|
52542
52794
|
parseUpdate(requestId, update) {
|
|
52543
52795
|
if (update.callback_query) {
|
|
52544
52796
|
const data = update.callback_query.data;
|
|
@@ -52558,11 +52810,11 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52558
52810
|
};
|
|
52559
52811
|
}
|
|
52560
52812
|
if (update.message?.text) {
|
|
52561
|
-
const
|
|
52562
|
-
if (!
|
|
52813
|
+
const pending = this.pendingMessages.get(requestId);
|
|
52814
|
+
if (!pending || pending.type !== "input")
|
|
52563
52815
|
return null;
|
|
52564
52816
|
const replyToId = update.message.reply_to_message?.message_id;
|
|
52565
|
-
if (replyToId !== undefined && !
|
|
52817
|
+
if (replyToId !== undefined && !pending.ids.includes(replyToId))
|
|
52566
52818
|
return null;
|
|
52567
52819
|
return {
|
|
52568
52820
|
requestId,
|
|
@@ -52581,7 +52833,7 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52581
52833
|
const controller = new AbortController;
|
|
52582
52834
|
const timer = setTimeout(() => controller.abort(), CALLBACK_API_TIMEOUT_MS);
|
|
52583
52835
|
try {
|
|
52584
|
-
await fetch(`https://api.telegram.org/bot${this.botToken}/answerCallbackQuery`, {
|
|
52836
|
+
await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/answerCallbackQuery`, {
|
|
52585
52837
|
method: "POST",
|
|
52586
52838
|
headers: { "Content-Type": "application/json" },
|
|
52587
52839
|
body: JSON.stringify({
|
|
@@ -52601,7 +52853,7 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52601
52853
|
const controller = new AbortController;
|
|
52602
52854
|
const timer = setTimeout(() => controller.abort(), CALLBACK_API_TIMEOUT_MS);
|
|
52603
52855
|
try {
|
|
52604
|
-
await fetch(`https://api.telegram.org/bot${this.botToken}/editMessageReplyMarkup`, {
|
|
52856
|
+
await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/editMessageReplyMarkup`, {
|
|
52605
52857
|
method: "POST",
|
|
52606
52858
|
headers: { "Content-Type": "application/json" },
|
|
52607
52859
|
body: JSON.stringify({
|
|
@@ -52617,14 +52869,14 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52617
52869
|
} catch {}
|
|
52618
52870
|
}
|
|
52619
52871
|
async sendTimeoutMessage(requestId) {
|
|
52620
|
-
const
|
|
52621
|
-
if (!
|
|
52872
|
+
const pending = this.pendingMessages.get(requestId);
|
|
52873
|
+
if (!pending || !this.botToken || !this.chatId) {
|
|
52622
52874
|
this.pendingMessages.delete(requestId);
|
|
52623
52875
|
return;
|
|
52624
52876
|
}
|
|
52625
|
-
const lastId =
|
|
52877
|
+
const lastId = pending.ids[pending.ids.length - 1];
|
|
52626
52878
|
try {
|
|
52627
|
-
await fetch(`https://api.telegram.org/bot${this.botToken}/editMessageText`, {
|
|
52879
|
+
await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/editMessageText`, {
|
|
52628
52880
|
method: "POST",
|
|
52629
52881
|
headers: { "Content-Type": "application/json" },
|
|
52630
52882
|
body: JSON.stringify({
|
|
@@ -58708,7 +58960,8 @@ async function refreshReviewInputForDispatch(opName, input) {
|
|
|
58708
58960
|
stat: fresh2.stat,
|
|
58709
58961
|
diff: fresh2.diff,
|
|
58710
58962
|
excludePatterns: fresh2.excludePatterns,
|
|
58711
|
-
storyGitRef: fresh2.effectiveRef ?? semInput.storyGitRef
|
|
58963
|
+
storyGitRef: fresh2.effectiveRef ?? semInput.storyGitRef,
|
|
58964
|
+
resolvedTestPatterns: _refresh.resolvedTestPatterns
|
|
58712
58965
|
};
|
|
58713
58966
|
}
|
|
58714
58967
|
const { _refresh: __, ...advInput } = input;
|
|
@@ -59755,6 +60008,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
59755
60008
|
excludePatterns: prepared.excludePatterns,
|
|
59756
60009
|
featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-semantic"),
|
|
59757
60010
|
priorSemanticIterations: ctx.priorSemanticIterations,
|
|
60011
|
+
resolvedTestPatterns,
|
|
59758
60012
|
blockingThreshold: ctx.config.review.blockingThreshold,
|
|
59759
60013
|
_refresh: {
|
|
59760
60014
|
projectDir: ctx.projectDir,
|
|
@@ -59792,6 +60046,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
59792
60046
|
refExcludePatterns: prepared.refExcludePatterns,
|
|
59793
60047
|
featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-adversarial"),
|
|
59794
60048
|
priorAdversarialIterations: ctx.priorAdversarialIterations,
|
|
60049
|
+
resolvedTestPatterns,
|
|
59795
60050
|
blockingThreshold: ctx.config.review.blockingThreshold,
|
|
59796
60051
|
_refresh: {
|
|
59797
60052
|
projectDir: ctx.projectDir,
|
|
@@ -62784,7 +63039,7 @@ var init_forge = __esm(() => {
|
|
|
62784
63039
|
function buildTitle(ctx) {
|
|
62785
63040
|
return `feat: ${ctx.feature}`;
|
|
62786
63041
|
}
|
|
62787
|
-
function
|
|
63042
|
+
function formatDuration3(totalMs) {
|
|
62788
63043
|
const clampedMs = Math.max(0, Math.round(totalMs));
|
|
62789
63044
|
const totalSeconds = Math.floor(clampedMs / MS_PER_SECOND);
|
|
62790
63045
|
const minutes = Math.floor(totalSeconds / SECONDS_PER_MINUTE);
|
|
@@ -62800,7 +63055,7 @@ function buildSummaryLines(ctx) {
|
|
|
62800
63055
|
"## Run summary",
|
|
62801
63056
|
`- Feature: ${ctx.feature}`,
|
|
62802
63057
|
`- Stories: ${passed} / ${failed} / ${skipped}`,
|
|
62803
|
-
`- Duration: ${
|
|
63058
|
+
`- Duration: ${formatDuration3(ctx.totalDurationMs)}`,
|
|
62804
63059
|
`- PRD: ${ctx.prdPath}`,
|
|
62805
63060
|
""
|
|
62806
63061
|
];
|
|
@@ -62821,7 +63076,7 @@ function buildStoryTable(stories) {
|
|
|
62821
63076
|
lines.push("");
|
|
62822
63077
|
return lines;
|
|
62823
63078
|
}
|
|
62824
|
-
function
|
|
63079
|
+
function buildBody2(ctx, template) {
|
|
62825
63080
|
const blocks = [];
|
|
62826
63081
|
blocks.push("> Auto-opened by nax \u2014 review pending. Run nax-finish before merge.");
|
|
62827
63082
|
blocks.push("");
|
|
@@ -62996,7 +63251,7 @@ var init_auto_pr = __esm(() => {
|
|
|
62996
63251
|
});
|
|
62997
63252
|
const prCtx = toPrBodyContext(context);
|
|
62998
63253
|
const title = buildTitle(prCtx);
|
|
62999
|
-
const body =
|
|
63254
|
+
const body = buildBody2(prCtx, template);
|
|
63000
63255
|
return await _autoPrDeps.openDraft(forge, { title, body, branch: context.branch, draft: cfg.draft }, { run: _autoPrDeps.run, readText: _autoPrDeps.readText }, context.workdir);
|
|
63001
63256
|
} catch (err) {
|
|
63002
63257
|
context.logger.warn("Auto-PR execute failed", { error: String(err) });
|
|
@@ -68780,7 +69035,7 @@ function extractQuoteTriples(reason) {
|
|
|
68780
69035
|
}
|
|
68781
69036
|
return triples;
|
|
68782
69037
|
}
|
|
68783
|
-
function
|
|
69038
|
+
function normalizeWs2(s) {
|
|
68784
69039
|
return s.replace(/\s+/g, " ").trim();
|
|
68785
69040
|
}
|
|
68786
69041
|
async function verifyQuoteTriple(triple, workdir, deps = _quoteIntegrityDeps) {
|
|
@@ -68794,7 +69049,7 @@ async function verifyQuoteTriple(triple, workdir, deps = _quoteIntegrityDeps) {
|
|
|
68794
69049
|
const end = Math.min(lines.length, triple.line + CONTEXT_LINES);
|
|
68795
69050
|
const window2 = lines.slice(start, end).join(`
|
|
68796
69051
|
`);
|
|
68797
|
-
return
|
|
69052
|
+
return normalizeWs2(window2).toLowerCase().includes(normalizeWs2(triple.quote).toLowerCase());
|
|
68798
69053
|
}
|
|
68799
69054
|
async function verifyEscalationQuotes(reason, workdir, storyId, deps = _quoteIntegrityDeps) {
|
|
68800
69055
|
const triples = extractQuoteTriples(reason);
|
|
@@ -69792,6 +70047,12 @@ async function executeParallelBatch(stories, _projectRoot, config2, context, wor
|
|
|
69792
70047
|
error: result.error
|
|
69793
70048
|
});
|
|
69794
70049
|
}
|
|
70050
|
+
}).catch((error48) => {
|
|
70051
|
+
results.failed.push({ story, error: errorMessage(error48) });
|
|
70052
|
+
logger?.error("parallel", "Story execution threw", {
|
|
70053
|
+
storyId: story.id,
|
|
70054
|
+
error: errorMessage(error48)
|
|
70055
|
+
});
|
|
69795
70056
|
}).finally(() => {
|
|
69796
70057
|
executing.delete(executePromise);
|
|
69797
70058
|
});
|
|
@@ -69800,7 +70061,7 @@ async function executeParallelBatch(stories, _projectRoot, config2, context, wor
|
|
|
69800
70061
|
await Promise.race(executing);
|
|
69801
70062
|
}
|
|
69802
70063
|
}
|
|
69803
|
-
await Promise.
|
|
70064
|
+
await Promise.allSettled(executing);
|
|
69804
70065
|
return results;
|
|
69805
70066
|
}
|
|
69806
70067
|
var _parallelWorkerDeps;
|
|
@@ -102438,7 +102699,6 @@ class DebatePlanStrategy {
|
|
|
102438
102699
|
});
|
|
102439
102700
|
if (debateResult.outcome !== "failed" && debateResult.output) {
|
|
102440
102701
|
const prd2 = validatePlanOutput(debateResult.output, ctx.options.feature, ctx.branchName);
|
|
102441
|
-
warnOnDroppedVerbatimAcs(prd2, ctx.specContent, ctx.options.feature);
|
|
102442
102702
|
const scoped2 = backfillOutOfScope(prd2, ctx.specContent, ctx.options.feature);
|
|
102443
102703
|
const withProject2 = { ...scoped2, project: ctx.projectName };
|
|
102444
102704
|
return _debatePlanDeps.writeOrRecoverPrd(ctx, withProject2);
|
|
@@ -103247,9 +103507,6 @@ var FIELD_DESCRIPTIONS = {
|
|
|
103247
103507
|
"execution.regressionGate.timeoutSeconds": "Timeout for regression run in seconds",
|
|
103248
103508
|
"execution.storyIsolation": 'Story isolation mode. "shared" (default): all stories run on the main branch. "worktree": each story runs in an isolated git worktree (.nax-wt/<storyId>/); passed stories merge into main, failed commits never reach main.',
|
|
103249
103509
|
quality: "Quality gate configuration",
|
|
103250
|
-
"quality.requireTypecheck": "Require typecheck to pass",
|
|
103251
|
-
"quality.requireLint": "Require lint to pass",
|
|
103252
|
-
"quality.requireTests": "Require tests to pass",
|
|
103253
103510
|
"quality.commands": "Custom quality commands",
|
|
103254
103511
|
"quality.commands.typecheck": "Custom typecheck command",
|
|
103255
103512
|
"quality.commands.lint": "Custom lint command",
|
|
@@ -105478,7 +105735,7 @@ var DEFAULT_LIMIT = 20;
|
|
|
105478
105735
|
var _runsCmdDeps = {
|
|
105479
105736
|
getRunsDir
|
|
105480
105737
|
};
|
|
105481
|
-
function
|
|
105738
|
+
function formatDuration4(ms) {
|
|
105482
105739
|
if (ms <= 0)
|
|
105483
105740
|
return "-";
|
|
105484
105741
|
const minutes = Math.floor(ms / 60000);
|
|
@@ -105593,7 +105850,7 @@ async function runsCommand(options = {}) {
|
|
|
105593
105850
|
pad3(row.feature, COL.feature),
|
|
105594
105851
|
pad3(colored, COL.status + (colored.length - visibleLength(colored))),
|
|
105595
105852
|
pad3(`${row.passed}/${row.total}`, COL.stories),
|
|
105596
|
-
pad3(
|
|
105853
|
+
pad3(formatDuration4(row.durationMs), COL.duration),
|
|
105597
105854
|
formatDate(row.registeredAt)
|
|
105598
105855
|
].join(" ");
|
|
105599
105856
|
console.log(line);
|
|
@@ -112874,7 +113131,7 @@ function useAgentStreamEvents(bus) {
|
|
|
112874
113131
|
if (!bus)
|
|
112875
113132
|
return;
|
|
112876
113133
|
const unsubscribe = bus.onAgentStream((event) => {
|
|
112877
|
-
const next =
|
|
113134
|
+
const next = activeCallsRef.current;
|
|
112878
113135
|
switch (event.kind) {
|
|
112879
113136
|
case "agent.call_started": {
|
|
112880
113137
|
next.set(event.callId, {
|
|
@@ -112958,10 +113215,13 @@ function useAgentStreamEvents(bus) {
|
|
|
112958
113215
|
default:
|
|
112959
113216
|
break;
|
|
112960
113217
|
}
|
|
112961
|
-
activeCallsRef.current = next;
|
|
112962
113218
|
dirtyRef.current = true;
|
|
112963
113219
|
});
|
|
112964
|
-
return
|
|
113220
|
+
return () => {
|
|
113221
|
+
unsubscribe();
|
|
113222
|
+
activeCallsRef.current.clear();
|
|
113223
|
+
lastTokensRef.current.clear();
|
|
113224
|
+
};
|
|
112965
113225
|
}, [bus]);
|
|
112966
113226
|
import_react31.useEffect(() => {
|
|
112967
113227
|
const interval = setInterval(() => {
|