@nathapp/nax 0.74.0 → 0.75.1
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 +932 -609
- 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,
|
|
@@ -20338,6 +20396,60 @@ var init_env = __esm(() => {
|
|
|
20338
20396
|
];
|
|
20339
20397
|
});
|
|
20340
20398
|
|
|
20399
|
+
// src/agents/acp/model-spec.ts
|
|
20400
|
+
function parseModelSpec(raw) {
|
|
20401
|
+
const match = EFFORT_SUFFIX.exec(raw);
|
|
20402
|
+
if (!match)
|
|
20403
|
+
return { model: raw };
|
|
20404
|
+
return { model: match[1], effort: match[2] };
|
|
20405
|
+
}
|
|
20406
|
+
var EFFORT_SUFFIX;
|
|
20407
|
+
var init_model_spec = __esm(() => {
|
|
20408
|
+
EFFORT_SUFFIX = /^([^[\]]+)\[([^[\]]+)\]$/;
|
|
20409
|
+
});
|
|
20410
|
+
|
|
20411
|
+
// src/agents/acp/reasoning-effort.ts
|
|
20412
|
+
async function applyReasoningEffort(params) {
|
|
20413
|
+
const { effort, agentName, sessionName, cwd, storyId, spawn: spawn2 } = params;
|
|
20414
|
+
if (!effort)
|
|
20415
|
+
return;
|
|
20416
|
+
const cmd = ["acpx", "--cwd", cwd, agentName, "set", "reasoning_effort", effort, "-s", sessionName];
|
|
20417
|
+
const { exitCode, stdout, stderr } = await spawn2(cmd);
|
|
20418
|
+
if (exitCode !== 0) {
|
|
20419
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to set reasoning_effort; continuing at adapter default", {
|
|
20420
|
+
storyId,
|
|
20421
|
+
effort,
|
|
20422
|
+
session: sessionName,
|
|
20423
|
+
cause: stdout || stderr
|
|
20424
|
+
});
|
|
20425
|
+
}
|
|
20426
|
+
}
|
|
20427
|
+
var init_reasoning_effort = __esm(() => {
|
|
20428
|
+
init_logger2();
|
|
20429
|
+
});
|
|
20430
|
+
|
|
20431
|
+
// src/agents/acp/session-ids.ts
|
|
20432
|
+
function parseSessionIds(stdout) {
|
|
20433
|
+
for (const line of stdout.split(`
|
|
20434
|
+
`).reverse()) {
|
|
20435
|
+
const trimmed = line.trim();
|
|
20436
|
+
if (!trimmed.startsWith("{"))
|
|
20437
|
+
continue;
|
|
20438
|
+
try {
|
|
20439
|
+
const parsed = JSON.parse(trimmed);
|
|
20440
|
+
const sessionId = parsed.acpxSessionId;
|
|
20441
|
+
const recordId = parsed.acpxRecordId;
|
|
20442
|
+
if (typeof sessionId === "string" && sessionId.length > 0) {
|
|
20443
|
+
return {
|
|
20444
|
+
sessionId,
|
|
20445
|
+
recordId: typeof recordId === "string" && recordId.length > 0 ? recordId : undefined
|
|
20446
|
+
};
|
|
20447
|
+
}
|
|
20448
|
+
} catch {}
|
|
20449
|
+
}
|
|
20450
|
+
return { sessionId: undefined, recordId: undefined };
|
|
20451
|
+
}
|
|
20452
|
+
|
|
20341
20453
|
// src/agents/acp/spawn-client.ts
|
|
20342
20454
|
import { randomUUID } from "crypto";
|
|
20343
20455
|
async function readAndParseLines(stream, state, onActivity) {
|
|
@@ -20380,6 +20492,7 @@ class SpawnAcpSession {
|
|
|
20380
20492
|
sessionName;
|
|
20381
20493
|
cwd;
|
|
20382
20494
|
model;
|
|
20495
|
+
modelLabel;
|
|
20383
20496
|
timeoutSeconds;
|
|
20384
20497
|
promptRetries;
|
|
20385
20498
|
permissionMode;
|
|
@@ -20400,6 +20513,7 @@ class SpawnAcpSession {
|
|
|
20400
20513
|
this.sessionName = opts.sessionName;
|
|
20401
20514
|
this.cwd = opts.cwd;
|
|
20402
20515
|
this.model = opts.model;
|
|
20516
|
+
this.modelLabel = opts.modelLabel ?? opts.model;
|
|
20403
20517
|
this.timeoutSeconds = opts.timeoutSeconds;
|
|
20404
20518
|
this.promptRetries = opts.promptRetries;
|
|
20405
20519
|
this.permissionMode = opts.permissionMode;
|
|
@@ -20472,7 +20586,7 @@ class SpawnAcpSession {
|
|
|
20472
20586
|
emit?.({
|
|
20473
20587
|
...baseEvent,
|
|
20474
20588
|
kind: "agent.call_started",
|
|
20475
|
-
model: this.
|
|
20589
|
+
model: this.modelLabel,
|
|
20476
20590
|
timeoutSeconds: this.timeoutSeconds,
|
|
20477
20591
|
timestamp: now()
|
|
20478
20592
|
});
|
|
@@ -20643,29 +20757,11 @@ class SpawnAcpSession {
|
|
|
20643
20757
|
await this.trackedSpawn(cmd);
|
|
20644
20758
|
}
|
|
20645
20759
|
}
|
|
20646
|
-
function parseSessionIds(stdout) {
|
|
20647
|
-
for (const line of stdout.split(`
|
|
20648
|
-
`).reverse()) {
|
|
20649
|
-
const trimmed = line.trim();
|
|
20650
|
-
if (!trimmed.startsWith("{"))
|
|
20651
|
-
continue;
|
|
20652
|
-
try {
|
|
20653
|
-
const parsed = JSON.parse(trimmed);
|
|
20654
|
-
const sessionId = parsed.acpxSessionId;
|
|
20655
|
-
const recordId = parsed.acpxRecordId;
|
|
20656
|
-
if (typeof sessionId === "string" && sessionId.length > 0) {
|
|
20657
|
-
return {
|
|
20658
|
-
sessionId,
|
|
20659
|
-
recordId: typeof recordId === "string" && recordId.length > 0 ? recordId : undefined
|
|
20660
|
-
};
|
|
20661
|
-
}
|
|
20662
|
-
} catch {}
|
|
20663
|
-
}
|
|
20664
|
-
return { sessionId: undefined, recordId: undefined };
|
|
20665
|
-
}
|
|
20666
20760
|
|
|
20667
20761
|
class SpawnAcpClient {
|
|
20668
20762
|
model;
|
|
20763
|
+
rawModel;
|
|
20764
|
+
reasoningEffort;
|
|
20669
20765
|
cwd;
|
|
20670
20766
|
timeoutSeconds;
|
|
20671
20767
|
promptRetries;
|
|
@@ -20680,7 +20776,11 @@ class SpawnAcpClient {
|
|
|
20680
20776
|
constructor(cmdStr, cwd, timeoutSeconds, onPidSpawned, promptRetries, onPidExited, opts) {
|
|
20681
20777
|
const parts = cmdStr.split(/\s+/);
|
|
20682
20778
|
const modelIdx = parts.indexOf("--model");
|
|
20683
|
-
|
|
20779
|
+
const rawModel = modelIdx >= 0 && parts[modelIdx + 1] ? parts[modelIdx + 1] : "default";
|
|
20780
|
+
const spec = parseModelSpec(rawModel);
|
|
20781
|
+
this.rawModel = rawModel;
|
|
20782
|
+
this.model = spec.model;
|
|
20783
|
+
this.reasoningEffort = spec.effort;
|
|
20684
20784
|
const lastToken = parts[parts.length - 1];
|
|
20685
20785
|
if (!lastToken || lastToken.startsWith("-")) {
|
|
20686
20786
|
throw new Error(`[acp-adapter] Could not parse agentName from cmdStr: "${cmdStr}"`);
|
|
@@ -20736,11 +20836,20 @@ class SpawnAcpClient {
|
|
|
20736
20836
|
throw new Error(`[acp-adapter] Failed to create session: ${stdout || stderr || `exit code ${exitCode}`}`);
|
|
20737
20837
|
}
|
|
20738
20838
|
const { sessionId, recordId } = parseSessionIds(stdout);
|
|
20839
|
+
await applyReasoningEffort({
|
|
20840
|
+
effort: this.reasoningEffort,
|
|
20841
|
+
agentName: opts.agentName,
|
|
20842
|
+
sessionName,
|
|
20843
|
+
cwd: this.cwd,
|
|
20844
|
+
storyId: this.storyId,
|
|
20845
|
+
spawn: (c) => this.trackedSpawn(c)
|
|
20846
|
+
});
|
|
20739
20847
|
return new SpawnAcpSession({
|
|
20740
20848
|
agentName: opts.agentName,
|
|
20741
20849
|
sessionName,
|
|
20742
20850
|
cwd: this.cwd,
|
|
20743
20851
|
model: this.model,
|
|
20852
|
+
modelLabel: this.rawModel,
|
|
20744
20853
|
timeoutSeconds: this.timeoutSeconds,
|
|
20745
20854
|
promptRetries: this.promptRetries,
|
|
20746
20855
|
permissionMode: opts.permissionMode,
|
|
@@ -20763,11 +20872,20 @@ class SpawnAcpClient {
|
|
|
20763
20872
|
return null;
|
|
20764
20873
|
}
|
|
20765
20874
|
const { sessionId, recordId } = parseSessionIds(stdout);
|
|
20875
|
+
await applyReasoningEffort({
|
|
20876
|
+
effort: this.reasoningEffort,
|
|
20877
|
+
agentName,
|
|
20878
|
+
sessionName,
|
|
20879
|
+
cwd: this.cwd,
|
|
20880
|
+
storyId: this.storyId,
|
|
20881
|
+
spawn: (c) => this.trackedSpawn(c)
|
|
20882
|
+
});
|
|
20766
20883
|
return new SpawnAcpSession({
|
|
20767
20884
|
agentName,
|
|
20768
20885
|
sessionName,
|
|
20769
20886
|
cwd: this.cwd,
|
|
20770
20887
|
model: this.model,
|
|
20888
|
+
modelLabel: this.rawModel,
|
|
20771
20889
|
timeoutSeconds: this.timeoutSeconds,
|
|
20772
20890
|
promptRetries: this.promptRetries,
|
|
20773
20891
|
permissionMode,
|
|
@@ -20806,6 +20924,8 @@ var init_spawn_client = __esm(() => {
|
|
|
20806
20924
|
init_logger2();
|
|
20807
20925
|
init_bun_deps();
|
|
20808
20926
|
init_env();
|
|
20927
|
+
init_model_spec();
|
|
20928
|
+
init_reasoning_effort();
|
|
20809
20929
|
_spawnClientDeps = {
|
|
20810
20930
|
spawn: typedSpawn,
|
|
20811
20931
|
streamDrainTimeoutMs: ACPX_STREAM_DRAIN_TIMEOUT_MS
|
|
@@ -21075,8 +21195,23 @@ function buildRunInteractionHandler(options) {
|
|
|
21075
21195
|
}
|
|
21076
21196
|
};
|
|
21077
21197
|
}
|
|
21198
|
+
function buildTurnResult(input) {
|
|
21199
|
+
const { lastResponse, totalTokenUsage, totalExactCostUsd, turnCount, interactions, timedOut, modelDef } = input;
|
|
21200
|
+
const output = timedOut ? "" : extractOutput(lastResponse);
|
|
21201
|
+
const estimatedCostUsd = totalTokenUsage.inputTokens > 0 || totalTokenUsage.outputTokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, modelDef.model) : 0;
|
|
21202
|
+
return {
|
|
21203
|
+
output,
|
|
21204
|
+
tokenUsage: totalTokenUsage,
|
|
21205
|
+
estimatedCostUsd,
|
|
21206
|
+
exactCostUsd: totalExactCostUsd,
|
|
21207
|
+
internalRoundTrips: turnCount,
|
|
21208
|
+
...interactions.length > 0 ? { interactions } : {},
|
|
21209
|
+
timedOut
|
|
21210
|
+
};
|
|
21211
|
+
}
|
|
21078
21212
|
var CONTEXT_TOOL_CALL_PATTERN;
|
|
21079
21213
|
var init_adapter_output = __esm(() => {
|
|
21214
|
+
init_cost();
|
|
21080
21215
|
CONTEXT_TOOL_CALL_PATTERN = /<nax_tool_call\s+name="([^"]+)">\s*([\s\S]*?)\s*<\/nax_tool_call>/i;
|
|
21081
21216
|
});
|
|
21082
21217
|
|
|
@@ -21435,18 +21570,15 @@ class AcpAgentAdapter {
|
|
|
21435
21570
|
if (lastResponse?.stopReason === "error") {
|
|
21436
21571
|
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
21572
|
}
|
|
21438
|
-
|
|
21439
|
-
|
|
21440
|
-
|
|
21441
|
-
|
|
21442
|
-
|
|
21443
|
-
|
|
21444
|
-
|
|
21445
|
-
|
|
21446
|
-
|
|
21447
|
-
internalRoundTrips: turnCount,
|
|
21448
|
-
...interactions.length > 0 ? { interactions } : {}
|
|
21449
|
-
};
|
|
21573
|
+
return buildTurnResult({
|
|
21574
|
+
lastResponse,
|
|
21575
|
+
totalTokenUsage,
|
|
21576
|
+
totalExactCostUsd,
|
|
21577
|
+
turnCount,
|
|
21578
|
+
interactions,
|
|
21579
|
+
timedOut,
|
|
21580
|
+
modelDef
|
|
21581
|
+
});
|
|
21450
21582
|
}
|
|
21451
21583
|
async closeSession(handle) {
|
|
21452
21584
|
const impl = handle;
|
|
@@ -21651,6 +21783,7 @@ var init_acp = __esm(() => {
|
|
|
21651
21783
|
init_spawn_client();
|
|
21652
21784
|
init_adapter_lifecycle();
|
|
21653
21785
|
init_token_mapper();
|
|
21786
|
+
init_model_spec();
|
|
21654
21787
|
});
|
|
21655
21788
|
|
|
21656
21789
|
// src/agents/registry.ts
|
|
@@ -21927,6 +22060,108 @@ var init_default_strategy = __esm(() => {
|
|
|
21927
22060
|
};
|
|
21928
22061
|
});
|
|
21929
22062
|
|
|
22063
|
+
// src/agents/retry/hop-retry-policy.ts
|
|
22064
|
+
function trySameAgentRetry(result, state, deps) {
|
|
22065
|
+
const { staleRetryAttempts, timeoutRetryAttempts, adapterErrorRetries, currentRunOptions } = state;
|
|
22066
|
+
const { config: config2, requestRunOptions, signal } = deps;
|
|
22067
|
+
const isFailStale = result.adapterFailure?.outcome === "fail-stale";
|
|
22068
|
+
const maxStaleRetries = config2.agent?.idleWatchdog?.maxRetryAttempts ?? 3;
|
|
22069
|
+
if (isFailStale && result.adapterFailure?.retriable && staleRetryAttempts < maxStaleRetries) {
|
|
22070
|
+
const newAttempts = staleRetryAttempts + 1;
|
|
22071
|
+
return {
|
|
22072
|
+
outcome: "stale-retry",
|
|
22073
|
+
staleRetryAttempts: newAttempts,
|
|
22074
|
+
kind: { kind: "stale-retry", attempt: newAttempts },
|
|
22075
|
+
fallbackRecord: {
|
|
22076
|
+
outcome: result.adapterFailure?.outcome ?? "fail-stale",
|
|
22077
|
+
category: result.adapterFailure?.category ?? "availability",
|
|
22078
|
+
costUsd: result.estimatedCostUsd ?? 0,
|
|
22079
|
+
reason: result.adapterFailure?.reason
|
|
22080
|
+
}
|
|
22081
|
+
};
|
|
22082
|
+
}
|
|
22083
|
+
const isFailTimeout = result.adapterFailure?.outcome === "fail-timeout";
|
|
22084
|
+
if (isFailTimeout && result.adapterFailure?.retriable) {
|
|
22085
|
+
const timeoutConfig = extractTimeoutRetryConfig(config2);
|
|
22086
|
+
if (timeoutRetryShouldRetry(timeoutRetryAttempts, timeoutConfig)) {
|
|
22087
|
+
const newAttempts = timeoutRetryAttempts + 1;
|
|
22088
|
+
return {
|
|
22089
|
+
outcome: "timeout-retry",
|
|
22090
|
+
timeoutRetryAttempts: newAttempts,
|
|
22091
|
+
kind: { kind: "timeout-retry", attempt: newAttempts },
|
|
22092
|
+
currentRunOptions: resolveTimeoutRetryOptions(currentRunOptions, timeoutConfig, config2.execution),
|
|
22093
|
+
fallbackRecord: {
|
|
22094
|
+
outcome: result.adapterFailure?.outcome ?? "fail-timeout",
|
|
22095
|
+
category: result.adapterFailure?.category ?? "quality",
|
|
22096
|
+
costUsd: result.estimatedCostUsd ?? 0,
|
|
22097
|
+
reason: result.adapterFailure?.reason
|
|
22098
|
+
}
|
|
22099
|
+
};
|
|
22100
|
+
}
|
|
22101
|
+
}
|
|
22102
|
+
const isFailAdapterError = result.adapterFailure?.outcome === "fail-adapter-error";
|
|
22103
|
+
if (isFailAdapterError && !signal?.aborted) {
|
|
22104
|
+
const runConfig = requestRunOptions.config ?? config2;
|
|
22105
|
+
const maxAdapterRetries = result.adapterFailure?.retriable ? runConfig.execution?.sessionErrorRetryableMaxRetries ?? 3 : runConfig.execution?.sessionErrorMaxRetries ?? 1;
|
|
22106
|
+
if (adapterErrorRetries < maxAdapterRetries) {
|
|
22107
|
+
const newAttempts = adapterErrorRetries + 1;
|
|
22108
|
+
return {
|
|
22109
|
+
outcome: "adapter-error",
|
|
22110
|
+
adapterErrorRetries: newAttempts,
|
|
22111
|
+
kind: { kind: "stale-retry", attempt: newAttempts },
|
|
22112
|
+
fallbackRecord: {
|
|
22113
|
+
outcome: result.adapterFailure?.outcome ?? "fail-adapter-error",
|
|
22114
|
+
category: result.adapterFailure?.category ?? "availability",
|
|
22115
|
+
costUsd: result.estimatedCostUsd ?? 0,
|
|
22116
|
+
retriable: result.adapterFailure?.retriable ?? false,
|
|
22117
|
+
maxAttempts: maxAdapterRetries
|
|
22118
|
+
}
|
|
22119
|
+
};
|
|
22120
|
+
}
|
|
22121
|
+
}
|
|
22122
|
+
return null;
|
|
22123
|
+
}
|
|
22124
|
+
function extractTimeoutRetryConfig(config2) {
|
|
22125
|
+
const fromConfig = config2.agent?.timeoutRetry;
|
|
22126
|
+
return {
|
|
22127
|
+
maxAttempts: fromConfig?.maxAttempts ?? DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG.maxAttempts,
|
|
22128
|
+
budgetMultiplier: fromConfig?.budgetMultiplier ?? DEFAULT_AGENT_TIMEOUT_RETRY_CONFIG.budgetMultiplier
|
|
22129
|
+
};
|
|
22130
|
+
}
|
|
22131
|
+
function resolveTimeoutRetryOptions(prev, timeoutConfig, executionConfig) {
|
|
22132
|
+
const budget = prev.timeoutSeconds ?? executionConfig?.sessionTimeoutSeconds ?? DEFAULT_CONFIG.execution.sessionTimeoutSeconds;
|
|
22133
|
+
return { ...prev, timeoutSeconds: budget * timeoutConfig.budgetMultiplier };
|
|
22134
|
+
}
|
|
22135
|
+
function timeoutRetryShouldRetry(attempts, config2) {
|
|
22136
|
+
return attempts < config2.maxAttempts;
|
|
22137
|
+
}
|
|
22138
|
+
function describeRetryLogEvent(retryDecision, storyId, agent) {
|
|
22139
|
+
const attempt = retryDecision.kind.attempt;
|
|
22140
|
+
if (retryDecision.outcome === "adapter-error") {
|
|
22141
|
+
return {
|
|
22142
|
+
recordFallback: false,
|
|
22143
|
+
level: "warn",
|
|
22144
|
+
message: "fail-adapter-error: same-agent retry with fresh session",
|
|
22145
|
+
fields: {
|
|
22146
|
+
storyId,
|
|
22147
|
+
attempt,
|
|
22148
|
+
maxAttempts: retryDecision.fallbackRecord.maxAttempts,
|
|
22149
|
+
retriable: retryDecision.fallbackRecord.retriable,
|
|
22150
|
+
agent
|
|
22151
|
+
}
|
|
22152
|
+
};
|
|
22153
|
+
}
|
|
22154
|
+
return {
|
|
22155
|
+
recordFallback: true,
|
|
22156
|
+
level: "info",
|
|
22157
|
+
message: retryDecision.outcome === "stale-retry" ? "fail-stale: immediate same-agent retry" : "fail-timeout: same-agent retry with reduced budget",
|
|
22158
|
+
fields: { storyId, attempt, agent, reason: retryDecision.fallbackRecord.reason }
|
|
22159
|
+
};
|
|
22160
|
+
}
|
|
22161
|
+
var init_hop_retry_policy = __esm(() => {
|
|
22162
|
+
init_config();
|
|
22163
|
+
});
|
|
22164
|
+
|
|
21930
22165
|
// src/agents/manager.ts
|
|
21931
22166
|
import { EventEmitter } from "events";
|
|
21932
22167
|
|
|
@@ -22033,7 +22268,7 @@ class AgentManager {
|
|
|
22033
22268
|
shouldSwap(failure, hopsSoFar, hasBundle) {
|
|
22034
22269
|
if (!failure)
|
|
22035
22270
|
return false;
|
|
22036
|
-
if (failure.outcome === "fail-aborted")
|
|
22271
|
+
if (failure.outcome === "fail-aborted" || failure.outcome === "fail-timeout")
|
|
22037
22272
|
return false;
|
|
22038
22273
|
const fallback = this._config.agent?.fallback;
|
|
22039
22274
|
if (!fallback?.enabled)
|
|
@@ -22059,8 +22294,10 @@ class AgentManager {
|
|
|
22059
22294
|
let hopsSoFar = 0;
|
|
22060
22295
|
let rateLimitRetry = 0;
|
|
22061
22296
|
let staleRetryAttempts = 0;
|
|
22297
|
+
let timeoutRetryAttempts = 0;
|
|
22062
22298
|
let adapterErrorRetries = 0;
|
|
22063
22299
|
let currentBundle = request.bundle;
|
|
22300
|
+
let currentRunOptions = request.runOptions;
|
|
22064
22301
|
let currentHopKind = { kind: "primary" };
|
|
22065
22302
|
let finalPrompt;
|
|
22066
22303
|
const _opStartMs = Date.now();
|
|
@@ -22072,7 +22309,7 @@ class AgentManager {
|
|
|
22072
22309
|
let result;
|
|
22073
22310
|
let updatedBundle = currentBundle;
|
|
22074
22311
|
if (request.executeHop) {
|
|
22075
|
-
const hopOut = await request.executeHop(currentAgent, currentBundle, currentHopKind,
|
|
22312
|
+
const hopOut = await request.executeHop(currentAgent, currentBundle, currentHopKind, currentRunOptions);
|
|
22076
22313
|
result = hopOut.result;
|
|
22077
22314
|
updatedBundle = hopOut.bundle ?? currentBundle;
|
|
22078
22315
|
finalPrompt = hopOut.prompt ?? finalPrompt;
|
|
@@ -22089,7 +22326,7 @@ class AgentManager {
|
|
|
22089
22326
|
_finalStatus = "error";
|
|
22090
22327
|
return { result: unboundResult, fallbacks, finalBundle: currentBundle, finalPrompt };
|
|
22091
22328
|
}
|
|
22092
|
-
const rawHopOut = await this._runHop(currentAgent,
|
|
22329
|
+
const rawHopOut = await this._runHop(currentAgent, currentRunOptions);
|
|
22093
22330
|
const hopOut = "result" in rawHopOut && rawHopOut.result != null ? rawHopOut : { result: rawHopOut, prompt: undefined };
|
|
22094
22331
|
result = hopOut.result;
|
|
22095
22332
|
finalPrompt = hopOut.prompt ?? finalPrompt;
|
|
@@ -22100,51 +22337,49 @@ class AgentManager {
|
|
|
22100
22337
|
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
22101
22338
|
}
|
|
22102
22339
|
const bundleForSwapCheck = updatedBundle ?? request.bundle;
|
|
22103
|
-
if (request.noFallback) {
|
|
22104
|
-
_finalStatus = "error";
|
|
22105
|
-
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
22106
|
-
}
|
|
22107
22340
|
const isFailStale = result.adapterFailure?.outcome === "fail-stale";
|
|
22108
|
-
const
|
|
22109
|
-
|
|
22110
|
-
|
|
22341
|
+
const retryState = {
|
|
22342
|
+
staleRetryAttempts,
|
|
22343
|
+
timeoutRetryAttempts,
|
|
22344
|
+
adapterErrorRetries,
|
|
22345
|
+
currentRunOptions
|
|
22346
|
+
};
|
|
22347
|
+
const retryDecision = trySameAgentRetry(result, retryState, {
|
|
22348
|
+
config: this._config,
|
|
22349
|
+
requestRunOptions: request.runOptions,
|
|
22350
|
+
signal: request.signal
|
|
22351
|
+
});
|
|
22352
|
+
if (retryDecision) {
|
|
22353
|
+
staleRetryAttempts = retryDecision.outcome === "stale-retry" ? retryDecision.staleRetryAttempts : staleRetryAttempts;
|
|
22354
|
+
timeoutRetryAttempts = retryDecision.outcome === "timeout-retry" ? retryDecision.timeoutRetryAttempts : timeoutRetryAttempts;
|
|
22355
|
+
adapterErrorRetries = retryDecision.outcome === "adapter-error" ? retryDecision.adapterErrorRetries : adapterErrorRetries;
|
|
22356
|
+
currentRunOptions = retryDecision.outcome === "timeout-retry" ? retryDecision.currentRunOptions : currentRunOptions;
|
|
22111
22357
|
const retryHop = {
|
|
22112
22358
|
storyId: request.runOptions.storyId,
|
|
22113
22359
|
priorAgent: currentAgent,
|
|
22114
22360
|
newAgent: currentAgent,
|
|
22115
|
-
hop:
|
|
22116
|
-
outcome:
|
|
22117
|
-
category:
|
|
22361
|
+
hop: retryDecision.kind.attempt,
|
|
22362
|
+
outcome: retryDecision.fallbackRecord.outcome,
|
|
22363
|
+
category: retryDecision.fallbackRecord.category,
|
|
22118
22364
|
timestamp: new Date().toISOString(),
|
|
22119
|
-
costUsd:
|
|
22365
|
+
costUsd: retryDecision.fallbackRecord.costUsd
|
|
22120
22366
|
};
|
|
22121
|
-
|
|
22122
|
-
|
|
22123
|
-
|
|
22124
|
-
|
|
22125
|
-
|
|
22126
|
-
|
|
22127
|
-
|
|
22128
|
-
}
|
|
22129
|
-
|
|
22367
|
+
const logEvent = describeRetryLogEvent(retryDecision, request.runOptions.storyId, currentAgent);
|
|
22368
|
+
if (logEvent.recordFallback) {
|
|
22369
|
+
fallbacks.push(retryHop);
|
|
22370
|
+
this._emitter.emit("onSwapAttempt", retryHop);
|
|
22371
|
+
}
|
|
22372
|
+
if (logEvent.level === "warn") {
|
|
22373
|
+
logger?.warn("agent-manager", logEvent.message, logEvent.fields);
|
|
22374
|
+
} else {
|
|
22375
|
+
logger?.info("agent-manager", logEvent.message, logEvent.fields);
|
|
22376
|
+
}
|
|
22377
|
+
currentHopKind = retryDecision.kind;
|
|
22130
22378
|
continue;
|
|
22131
22379
|
}
|
|
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
|
-
}
|
|
22380
|
+
if (request.noFallback) {
|
|
22381
|
+
_finalStatus = "error";
|
|
22382
|
+
return { result, fallbacks, finalBundle: updatedBundle, finalPrompt, finalAgent: currentAgent };
|
|
22148
22383
|
}
|
|
22149
22384
|
const hasBundleForSwap = !!bundleForSwapCheck || isFailStale;
|
|
22150
22385
|
if (!this.shouldSwap(result.adapterFailure, hopsSoFar, hasBundleForSwap)) {
|
|
@@ -22558,6 +22793,7 @@ var init_manager = __esm(() => {
|
|
|
22558
22793
|
init_bun_deps();
|
|
22559
22794
|
init_registry();
|
|
22560
22795
|
init_default_strategy();
|
|
22796
|
+
init_hop_retry_policy();
|
|
22561
22797
|
_agentManagerDeps = {
|
|
22562
22798
|
sleep: (ms, signal) => cancellableDelay(ms, signal)
|
|
22563
22799
|
};
|
|
@@ -22835,6 +23071,7 @@ var init_retry = __esm(() => {
|
|
|
22835
23071
|
init_compose();
|
|
22836
23072
|
init_parse_retry();
|
|
22837
23073
|
init_tiered_parse_retry();
|
|
23074
|
+
init_hop_retry_policy();
|
|
22838
23075
|
});
|
|
22839
23076
|
|
|
22840
23077
|
// src/agents/index.ts
|
|
@@ -25288,7 +25525,7 @@ async function getGitRoot(workdir) {
|
|
|
25288
25525
|
return null;
|
|
25289
25526
|
}
|
|
25290
25527
|
}
|
|
25291
|
-
async function gitWithTimeout(args, workdir) {
|
|
25528
|
+
async function gitWithTimeout(args, workdir, timeoutMs = GIT_TIMEOUT_MS) {
|
|
25292
25529
|
const proc = _gitDeps.spawn(["git", ...args], {
|
|
25293
25530
|
cwd: workdir,
|
|
25294
25531
|
stdout: "pipe",
|
|
@@ -25300,7 +25537,7 @@ async function gitWithTimeout(args, workdir) {
|
|
|
25300
25537
|
try {
|
|
25301
25538
|
proc.kill("SIGKILL");
|
|
25302
25539
|
} catch {}
|
|
25303
|
-
},
|
|
25540
|
+
}, timeoutMs);
|
|
25304
25541
|
const exitCode = await proc.exited;
|
|
25305
25542
|
clearTimeout(timerId);
|
|
25306
25543
|
if (timedOut) {
|
|
@@ -25432,6 +25669,38 @@ async function captureOutputFiles(workdir, baseRef, scopePrefix) {
|
|
|
25432
25669
|
return [];
|
|
25433
25670
|
}
|
|
25434
25671
|
}
|
|
25672
|
+
async function captureWorkingTreeChanges(workdir, baseRef, scopePrefix) {
|
|
25673
|
+
if (!baseRef)
|
|
25674
|
+
return [];
|
|
25675
|
+
const runDiff = async (args) => {
|
|
25676
|
+
const fullArgs = scopePrefix ? [...args, "--", `${scopePrefix}/`] : args;
|
|
25677
|
+
const { stdout, exitCode } = await gitWithTimeout(fullArgs, workdir, TIMEOUT_RETRY_GIT_TIMEOUT_MS);
|
|
25678
|
+
if (exitCode !== 0)
|
|
25679
|
+
return [];
|
|
25680
|
+
return stdout.trim().split(`
|
|
25681
|
+
`).filter(Boolean);
|
|
25682
|
+
};
|
|
25683
|
+
try {
|
|
25684
|
+
const [committed, uncommitted, untracked] = await Promise.all([
|
|
25685
|
+
runDiff(["diff", "--name-only", `${baseRef}..HEAD`]),
|
|
25686
|
+
runDiff(["diff", "--name-only", "HEAD"]),
|
|
25687
|
+
runDiff(["ls-files", "--others", "--exclude-standard"])
|
|
25688
|
+
]);
|
|
25689
|
+
const seen = new Set;
|
|
25690
|
+
const merged = [];
|
|
25691
|
+
for (const list of [committed, uncommitted, untracked]) {
|
|
25692
|
+
for (const file3 of list) {
|
|
25693
|
+
if (!seen.has(file3)) {
|
|
25694
|
+
seen.add(file3);
|
|
25695
|
+
merged.push(file3);
|
|
25696
|
+
}
|
|
25697
|
+
}
|
|
25698
|
+
}
|
|
25699
|
+
return merged;
|
|
25700
|
+
} catch {
|
|
25701
|
+
return [];
|
|
25702
|
+
}
|
|
25703
|
+
}
|
|
25435
25704
|
async function captureDiffSummary(workdir, baseRef, scopePrefix) {
|
|
25436
25705
|
if (!baseRef)
|
|
25437
25706
|
return "";
|
|
@@ -25454,7 +25723,7 @@ async function captureDiffSummary(workdir, baseRef, scopePrefix) {
|
|
|
25454
25723
|
return "";
|
|
25455
25724
|
}
|
|
25456
25725
|
}
|
|
25457
|
-
var _gitDeps, GIT_TIMEOUT_MS = 1e4;
|
|
25726
|
+
var _gitDeps, GIT_TIMEOUT_MS = 1e4, TIMEOUT_RETRY_GIT_TIMEOUT_MS = 3000;
|
|
25458
25727
|
var init_git = __esm(() => {
|
|
25459
25728
|
init_logger2();
|
|
25460
25729
|
init_bun_deps();
|
|
@@ -28697,81 +28966,12 @@ function generateHumanHaltSummary(prd) {
|
|
|
28697
28966
|
`);
|
|
28698
28967
|
}
|
|
28699
28968
|
|
|
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
28969
|
// src/prd/spec-drift.ts
|
|
28770
|
-
function
|
|
28771
|
-
return ac.match(
|
|
28970
|
+
function leadingTagGroup(ac) {
|
|
28971
|
+
return ac.match(LEADING_TAG_GROUP)?.[1] ?? null;
|
|
28772
28972
|
}
|
|
28773
28973
|
function hasDeprecatedTag(ac) {
|
|
28774
|
-
const tags =
|
|
28974
|
+
const tags = leadingTagGroup(ac);
|
|
28775
28975
|
return tags !== null && DEPRECATED_TAG.test(tags);
|
|
28776
28976
|
}
|
|
28777
28977
|
function hasShellPattern(ac) {
|
|
@@ -28791,9 +28991,9 @@ function findSpecDriftViolations(prd) {
|
|
|
28791
28991
|
}
|
|
28792
28992
|
return violations;
|
|
28793
28993
|
}
|
|
28794
|
-
var
|
|
28994
|
+
var LEADING_TAG_GROUP, DEPRECATED_TAG, SHELL_PIPE, SHELL_WC, SHELL_GREP_FLAG;
|
|
28795
28995
|
var init_spec_drift = __esm(() => {
|
|
28796
|
-
|
|
28996
|
+
LEADING_TAG_GROUP = /^\s*(?:[-*]|\d+\.)?\s*((?:\[[a-z][a-z-]*\]\s*)+)/i;
|
|
28797
28997
|
DEPRECATED_TAG = /\[(grep|file|verbatim)\]/i;
|
|
28798
28998
|
SHELL_PIPE = /`[^`]*\b(grep|find|wc|awk|sed|sort|head|tail|xargs|cut|uniq)\b[^`]*\|[^`]*`/i;
|
|
28799
28999
|
SHELL_WC = /`[^`]*\bwc\b[^`]*`/;
|
|
@@ -29221,9 +29421,7 @@ __export(exports_prd, {
|
|
|
29221
29421
|
getContextFiles: () => getContextFiles,
|
|
29222
29422
|
generateHumanHaltSummary: () => generateHumanHaltSummary,
|
|
29223
29423
|
findSpecDriftViolations: () => findSpecDriftViolations,
|
|
29224
|
-
findMissingVerbatimAcs: () => findMissingVerbatimAcs,
|
|
29225
29424
|
findMissingOutOfScope: () => findMissingOutOfScope,
|
|
29226
|
-
extractVerbatimAcs: () => extractVerbatimAcs,
|
|
29227
29425
|
extractSpecOutOfScope: () => extractSpecOutOfScope,
|
|
29228
29426
|
deriveNextStoryId: () => deriveNextStoryId,
|
|
29229
29427
|
countStories: () => countStories,
|
|
@@ -29421,7 +29619,6 @@ var init_prd = __esm(() => {
|
|
|
29421
29619
|
init_errors();
|
|
29422
29620
|
init_json_file();
|
|
29423
29621
|
init_out_of_scope();
|
|
29424
|
-
init_verbatim_fidelity();
|
|
29425
29622
|
init_spec_drift();
|
|
29426
29623
|
init_out_of_scope();
|
|
29427
29624
|
init_inject();
|
|
@@ -29878,6 +30075,13 @@ var init_plugin_loader = __esm(() => {
|
|
|
29878
30075
|
});
|
|
29879
30076
|
|
|
29880
30077
|
// src/context/engine/providers/plugin-cache.ts
|
|
30078
|
+
function withDisposeDeadline(p, deadlineMs) {
|
|
30079
|
+
let handle;
|
|
30080
|
+
const deadline = new Promise((resolve8) => {
|
|
30081
|
+
handle = setTimeout(resolve8, deadlineMs);
|
|
30082
|
+
});
|
|
30083
|
+
return Promise.race([p, deadline]).finally(() => clearTimeout(handle));
|
|
30084
|
+
}
|
|
29881
30085
|
function stableCacheKey(configs, workdir) {
|
|
29882
30086
|
const sorted = [...configs].sort((a, b) => a.module.localeCompare(b.module));
|
|
29883
30087
|
return `${workdir}:${JSON.stringify(sorted)}`;
|
|
@@ -29908,31 +30112,32 @@ class PluginProviderCache {
|
|
|
29908
30112
|
return;
|
|
29909
30113
|
this.disposed = true;
|
|
29910
30114
|
const logger = getLogger();
|
|
30115
|
+
const disposals = [];
|
|
29911
30116
|
for (const providers of this.cache.values()) {
|
|
29912
30117
|
for (const provider of providers) {
|
|
29913
30118
|
const initialisable = provider;
|
|
29914
30119
|
if (typeof initialisable.dispose !== "function")
|
|
29915
30120
|
continue;
|
|
29916
|
-
|
|
29917
|
-
await Promise.race([initialisable.dispose(), Bun.sleep(DISPOSE_TIMEOUT_MS)]);
|
|
29918
|
-
} catch (err) {
|
|
30121
|
+
disposals.push(withDisposeDeadline(Promise.resolve().then(() => initialisable.dispose?.()).then(() => {}), _pluginCacheDeps.disposeTimeoutMs).catch((err) => {
|
|
29919
30122
|
logger.warn("context-engine", "Plugin provider dispose() threw \u2014 continuing teardown", {
|
|
29920
30123
|
providerId: provider.id,
|
|
29921
30124
|
error: err instanceof Error ? err.message : String(err)
|
|
29922
30125
|
});
|
|
29923
|
-
}
|
|
30126
|
+
}));
|
|
29924
30127
|
}
|
|
29925
30128
|
}
|
|
30129
|
+
await Promise.all(disposals);
|
|
29926
30130
|
this.cache.clear();
|
|
29927
30131
|
}
|
|
29928
30132
|
}
|
|
29929
|
-
var
|
|
30133
|
+
var DISPOSE_TIMEOUT_MS = 5000, _pluginCacheDeps;
|
|
29930
30134
|
var init_plugin_cache = __esm(() => {
|
|
29931
30135
|
init_errors();
|
|
29932
30136
|
init_logger2();
|
|
29933
30137
|
init_plugin_loader();
|
|
29934
30138
|
_pluginCacheDeps = {
|
|
29935
|
-
loadProviders: loadPluginProviders
|
|
30139
|
+
loadProviders: loadPluginProviders,
|
|
30140
|
+
disposeTimeoutMs: DISPOSE_TIMEOUT_MS
|
|
29936
30141
|
};
|
|
29937
30142
|
});
|
|
29938
30143
|
|
|
@@ -33450,7 +33655,7 @@ var init_acceptance_builder = __esm(() => {
|
|
|
33450
33655
|
});
|
|
33451
33656
|
|
|
33452
33657
|
// src/review/ac-quote-validator.ts
|
|
33453
|
-
function
|
|
33658
|
+
function normalizeWs(s) {
|
|
33454
33659
|
return s.replace(/\s+/g, " ").trim();
|
|
33455
33660
|
}
|
|
33456
33661
|
function stripMarkdownInline(s) {
|
|
@@ -33485,8 +33690,8 @@ function validateAcQuote(finding, acceptanceCriteria) {
|
|
|
33485
33690
|
if (typeof acIndex !== "number" || acIndex < 1 || acIndex > acceptanceCriteria.length) {
|
|
33486
33691
|
return { valid: false, code: "ac_index_out_of_range" };
|
|
33487
33692
|
}
|
|
33488
|
-
const acText =
|
|
33489
|
-
const normalizedQuote =
|
|
33693
|
+
const acText = normalizeWs(stripMarkdownInline(acceptanceCriteria[acIndex - 1]));
|
|
33694
|
+
const normalizedQuote = normalizeWs(stripMarkdownInline(acQuote));
|
|
33490
33695
|
if (!acText.toLowerCase().includes(normalizedQuote.toLowerCase())) {
|
|
33491
33696
|
return { valid: false, code: "ac_quote_not_substring" };
|
|
33492
33697
|
}
|
|
@@ -33560,8 +33765,8 @@ function validateScopeQuote(finding, outOfScope) {
|
|
|
33560
33765
|
if (typeof scopeIndex !== "number" || scopeIndex < 1 || scopeIndex > outOfScope.length) {
|
|
33561
33766
|
return { valid: false, code: "scope_index_out_of_range" };
|
|
33562
33767
|
}
|
|
33563
|
-
const entry =
|
|
33564
|
-
const quote =
|
|
33768
|
+
const entry = normalizeWs(stripMarkdownInline(outOfScope[scopeIndex - 1]));
|
|
33769
|
+
const quote = normalizeWs(stripMarkdownInline(scopeQuote));
|
|
33565
33770
|
if (!entry.toLowerCase().includes(quote.toLowerCase())) {
|
|
33566
33771
|
return { valid: false, code: "scope_quote_not_substring" };
|
|
33567
33772
|
}
|
|
@@ -33780,13 +33985,31 @@ var init_adapters = __esm(() => {
|
|
|
33780
33985
|
init_typecheck();
|
|
33781
33986
|
});
|
|
33782
33987
|
|
|
33783
|
-
// src/operations/
|
|
33784
|
-
function
|
|
33785
|
-
|
|
33786
|
-
|
|
33787
|
-
|
|
33988
|
+
// src/operations/turn-failure-classification.ts
|
|
33989
|
+
function classifyEmptyOutputFailure(turn) {
|
|
33990
|
+
if (turn.adapterFailure)
|
|
33991
|
+
return turn.adapterFailure;
|
|
33992
|
+
if (turn.output && turn.output.trim().length > 0)
|
|
33993
|
+
return null;
|
|
33994
|
+
if (turn.timedOut) {
|
|
33995
|
+
return {
|
|
33996
|
+
category: "quality",
|
|
33997
|
+
outcome: "fail-timeout",
|
|
33998
|
+
retriable: true,
|
|
33999
|
+
message: "[callOp] agent timed out before producing output",
|
|
34000
|
+
reason: "wall-clock-timeout"
|
|
34001
|
+
};
|
|
33788
34002
|
}
|
|
34003
|
+
return {
|
|
34004
|
+
category: "availability",
|
|
34005
|
+
outcome: "fail-stale",
|
|
34006
|
+
retriable: true,
|
|
34007
|
+
message: "[callOp] agent returned no output",
|
|
34008
|
+
reason: "empty-output"
|
|
34009
|
+
};
|
|
33789
34010
|
}
|
|
34011
|
+
|
|
34012
|
+
// src/operations/plan-fidelity.ts
|
|
33790
34013
|
function backfillOutOfScope(prd, specContent, featureName) {
|
|
33791
34014
|
const missing = findMissingOutOfScope(specContent, prd);
|
|
33792
34015
|
if (missing.length === 0)
|
|
@@ -33808,7 +34031,7 @@ function warnOnSpecDrift(prd, featureName) {
|
|
|
33808
34031
|
});
|
|
33809
34032
|
}
|
|
33810
34033
|
}
|
|
33811
|
-
var
|
|
34034
|
+
var init_plan_fidelity = __esm(() => {
|
|
33812
34035
|
init_logger2();
|
|
33813
34036
|
init_prd();
|
|
33814
34037
|
});
|
|
@@ -33820,7 +34043,7 @@ var init_plan = __esm(() => {
|
|
|
33820
34043
|
init_config();
|
|
33821
34044
|
init_schema2();
|
|
33822
34045
|
init_prompts();
|
|
33823
|
-
|
|
34046
|
+
init_plan_fidelity();
|
|
33824
34047
|
planInteractiveOp = {
|
|
33825
34048
|
kind: "run",
|
|
33826
34049
|
name: "plan-interactive",
|
|
@@ -33865,7 +34088,6 @@ ${outputFormat}`, overridable: false }
|
|
|
33865
34088
|
verify: async (parsed, input, _ctx) => {
|
|
33866
34089
|
if (!parsed.userStories || parsed.userStories.length === 0)
|
|
33867
34090
|
return null;
|
|
33868
|
-
warnOnDroppedVerbatimAcs(parsed, input.specContent, input.featureName);
|
|
33869
34091
|
return backfillOutOfScope(parsed, input.specContent, input.featureName);
|
|
33870
34092
|
},
|
|
33871
34093
|
recover: async (input, ctx) => {
|
|
@@ -33951,20 +34173,6 @@ function validateRefinedPrd(prd) {
|
|
|
33951
34173
|
validateRefinedStory(story);
|
|
33952
34174
|
return prd;
|
|
33953
34175
|
}
|
|
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
34176
|
async function readSpecDriftViolations(input) {
|
|
33969
34177
|
const content = await _planRefineDeps.readFile(input.outputPath);
|
|
33970
34178
|
if (!content)
|
|
@@ -34053,17 +34261,6 @@ async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
|
|
|
34053
34261
|
return prd;
|
|
34054
34262
|
return { ...prd, userStories: results.map((r) => r.story) };
|
|
34055
34263
|
}
|
|
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
34264
|
async function readMissingOutOfScope(input) {
|
|
34068
34265
|
const content = await _planRefineDeps.readFile(input.outputPath);
|
|
34069
34266
|
if (!content)
|
|
@@ -34110,8 +34307,8 @@ var init_plan_refine = __esm(() => {
|
|
|
34110
34307
|
init_prd();
|
|
34111
34308
|
init_schema2();
|
|
34112
34309
|
init_prompts();
|
|
34310
|
+
init_plan_fidelity();
|
|
34113
34311
|
init_self_heal();
|
|
34114
|
-
init_verbatim_warn();
|
|
34115
34312
|
_planRefineDeps = {
|
|
34116
34313
|
readFile: async (path3) => {
|
|
34117
34314
|
try {
|
|
@@ -34195,7 +34392,6 @@ ${outputFormat}`,
|
|
|
34195
34392
|
estimatedCostUsd: (turn1.estimatedCostUsd ?? 0) + (turn2.estimatedCostUsd ?? 0)
|
|
34196
34393
|
};
|
|
34197
34394
|
const steps = [
|
|
34198
|
-
verbatimSelfHealStep(builder),
|
|
34199
34395
|
outOfScopeSelfHealStep(builder),
|
|
34200
34396
|
...specGuard ? [specDriftSelfHealStep(builder)] : []
|
|
34201
34397
|
];
|
|
@@ -34206,7 +34402,6 @@ ${outputFormat}`,
|
|
|
34206
34402
|
},
|
|
34207
34403
|
verify: async (parsed, input, ctx) => {
|
|
34208
34404
|
const validated = validateRefinedPrd(parsed);
|
|
34209
|
-
warnOnDroppedVerbatimAcs(validated, input.specContent, input.featureName);
|
|
34210
34405
|
if (ctx.config.plan.specGuard) {
|
|
34211
34406
|
warnOnSpecDrift(validated, input.featureName);
|
|
34212
34407
|
}
|
|
@@ -35638,6 +35833,11 @@ function categoryToFixTarget(category) {
|
|
|
35638
35833
|
return "source";
|
|
35639
35834
|
return category != null && BLOCKING_CATEGORIES.has(category) ? "source" : "test";
|
|
35640
35835
|
}
|
|
35836
|
+
function resolveFixTarget({ base, file: file3, isTestFile: isTestFile3 }) {
|
|
35837
|
+
if (file3 && isTestFile3?.(file3))
|
|
35838
|
+
return "test";
|
|
35839
|
+
return base;
|
|
35840
|
+
}
|
|
35641
35841
|
var init_category_fix_target = __esm(() => {
|
|
35642
35842
|
init_ac_structural_counterfactual();
|
|
35643
35843
|
});
|
|
@@ -35665,7 +35865,7 @@ function normalizeSeverity(sev) {
|
|
|
35665
35865
|
return sev;
|
|
35666
35866
|
return "info";
|
|
35667
35867
|
}
|
|
35668
|
-
function toAdversarialReviewFindings(findings) {
|
|
35868
|
+
function toAdversarialReviewFindings(findings, opts = {}) {
|
|
35669
35869
|
return findings.map((f) => {
|
|
35670
35870
|
const metaExtras = {};
|
|
35671
35871
|
if (f.acQuote)
|
|
@@ -35686,7 +35886,7 @@ function toAdversarialReviewFindings(findings) {
|
|
|
35686
35886
|
line: f.line,
|
|
35687
35887
|
message: f.issue,
|
|
35688
35888
|
suggestion: f.suggestion,
|
|
35689
|
-
fixTarget: categoryToFixTarget(f.category),
|
|
35889
|
+
fixTarget: resolveFixTarget({ base: categoryToFixTarget(f.category), file: f.file, isTestFile: opts.isTestFile }),
|
|
35690
35890
|
meta: Object.keys(metaExtras).length > 0 ? metaExtras : undefined
|
|
35691
35891
|
};
|
|
35692
35892
|
});
|
|
@@ -35750,7 +35950,7 @@ function downgradeToUnverifiable(finding) {
|
|
|
35750
35950
|
severity: "unverifiable"
|
|
35751
35951
|
};
|
|
35752
35952
|
}
|
|
35753
|
-
function llmFindingToFinding(f) {
|
|
35953
|
+
function llmFindingToFinding(f, opts = {}) {
|
|
35754
35954
|
const metaExtras = {};
|
|
35755
35955
|
if (f.verifiedBy)
|
|
35756
35956
|
metaExtras.verifiedBy = f.verifiedBy;
|
|
@@ -35766,15 +35966,16 @@ function llmFindingToFinding(f) {
|
|
|
35766
35966
|
line: f.line,
|
|
35767
35967
|
message: f.issue,
|
|
35768
35968
|
suggestion: f.suggestion ?? undefined,
|
|
35769
|
-
fixTarget: "source",
|
|
35969
|
+
fixTarget: resolveFixTarget({ base: "source", file: f.file, isTestFile: opts.isTestFile }),
|
|
35770
35970
|
meta: Object.keys(metaExtras).length > 0 ? metaExtras : undefined
|
|
35771
35971
|
};
|
|
35772
35972
|
}
|
|
35773
|
-
function toReviewFindings(findings) {
|
|
35774
|
-
return findings.map(llmFindingToFinding);
|
|
35973
|
+
function toReviewFindings(findings, opts = {}) {
|
|
35974
|
+
return findings.map((f) => llmFindingToFinding(f, opts));
|
|
35775
35975
|
}
|
|
35776
35976
|
var UNVERIFIED_FINDING_PATTERNS;
|
|
35777
35977
|
var init_semantic_helpers = __esm(() => {
|
|
35978
|
+
init_category_fix_target();
|
|
35778
35979
|
init_severity();
|
|
35779
35980
|
UNVERIFIED_FINDING_PATTERNS = [
|
|
35780
35981
|
"cannot verify",
|
|
@@ -35918,9 +36119,9 @@ function parseRequoteResponse(output) {
|
|
|
35918
36119
|
const parsed = tryParseLLMJson(output);
|
|
35919
36120
|
if (!isRecord(parsed))
|
|
35920
36121
|
return null;
|
|
35921
|
-
const
|
|
35922
|
-
if (
|
|
35923
|
-
return
|
|
36122
|
+
const canonical2 = extractCanonical(parsed);
|
|
36123
|
+
if (canonical2)
|
|
36124
|
+
return canonical2;
|
|
35924
36125
|
const findings = parsed.findings;
|
|
35925
36126
|
if (!Array.isArray(findings) || findings.length !== 1)
|
|
35926
36127
|
return null;
|
|
@@ -35967,6 +36168,10 @@ function withRepromptMarker(output, info) {
|
|
|
35967
36168
|
return output;
|
|
35968
36169
|
return JSON.stringify({ ...parsed, _repromptInfo: info });
|
|
35969
36170
|
}
|
|
36171
|
+
function semanticTestFileMatch(input) {
|
|
36172
|
+
const patterns = input.resolvedTestPatterns?.regex ?? [];
|
|
36173
|
+
return (file3) => patterns.some((re) => re.test(file3));
|
|
36174
|
+
}
|
|
35970
36175
|
function extractRepromptInfo(raw) {
|
|
35971
36176
|
if (!raw || typeof raw !== "object")
|
|
35972
36177
|
return;
|
|
@@ -36258,7 +36463,7 @@ var init_semantic_review = __esm(() => {
|
|
|
36258
36463
|
...parsed,
|
|
36259
36464
|
passed,
|
|
36260
36465
|
findings: accepted,
|
|
36261
|
-
normalizedFindings: toReviewFindings(blocking),
|
|
36466
|
+
normalizedFindings: toReviewFindings(blocking, { isTestFile: semanticTestFileMatch(input) }),
|
|
36262
36467
|
acDropped: dropped
|
|
36263
36468
|
};
|
|
36264
36469
|
}
|
|
@@ -36674,10 +36879,10 @@ var init_adversarial_review = __esm(() => {
|
|
|
36674
36879
|
...parsed,
|
|
36675
36880
|
passed,
|
|
36676
36881
|
findings: accepted,
|
|
36677
|
-
normalizedFindings: toAdversarialReviewFindings(blocking),
|
|
36882
|
+
normalizedFindings: toAdversarialReviewFindings(blocking, { isTestFile: testFileMatch }),
|
|
36678
36883
|
advisoryFindings: [
|
|
36679
|
-
...toAdversarialReviewFindings(advisory),
|
|
36680
|
-
...tagCoverageGap(toAdversarialReviewFindings(demoted))
|
|
36884
|
+
...toAdversarialReviewFindings(advisory, { isTestFile: testFileMatch }),
|
|
36885
|
+
...tagCoverageGap(toAdversarialReviewFindings(demoted, { isTestFile: testFileMatch }))
|
|
36681
36886
|
],
|
|
36682
36887
|
acDropped: dropped
|
|
36683
36888
|
};
|
|
@@ -39063,14 +39268,9 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
|
|
|
39063
39268
|
const pid = proc.pid;
|
|
39064
39269
|
killProcessGroup(pid, "SIGTERM");
|
|
39065
39270
|
let exitedDuringGrace = false;
|
|
39066
|
-
await
|
|
39067
|
-
|
|
39068
|
-
|
|
39069
|
-
}),
|
|
39070
|
-
new Promise((resolve11) => {
|
|
39071
|
-
setTimeout(resolve11, gracePeriodMs);
|
|
39072
|
-
})
|
|
39073
|
-
]);
|
|
39271
|
+
await raceWithDeadline(proc.exited.then(() => {
|
|
39272
|
+
exitedDuringGrace = true;
|
|
39273
|
+
}).catch(() => {}), gracePeriodMs);
|
|
39074
39274
|
if (!exitedDuringGrace) {
|
|
39075
39275
|
killProcessGroup(pid, "SIGKILL");
|
|
39076
39276
|
}
|
|
@@ -39946,7 +40146,7 @@ async function runQualityCommand(opts) {
|
|
|
39946
40146
|
let sigkillTimer;
|
|
39947
40147
|
proc.exited.then(() => {
|
|
39948
40148
|
exitedBeforeSigkill = true;
|
|
39949
|
-
});
|
|
40149
|
+
}).catch(() => {});
|
|
39950
40150
|
const killTimer = setTimeout(() => {
|
|
39951
40151
|
timedOut = true;
|
|
39952
40152
|
killProcessGroup(proc.pid, "SIGTERM");
|
|
@@ -41378,7 +41578,7 @@ var init_operations = __esm(() => {
|
|
|
41378
41578
|
init_plan();
|
|
41379
41579
|
init_plan_refine();
|
|
41380
41580
|
init_self_heal();
|
|
41381
|
-
|
|
41581
|
+
init_plan_fidelity();
|
|
41382
41582
|
init_decompose2();
|
|
41383
41583
|
init_build_hop_callback();
|
|
41384
41584
|
init_classify_route();
|
|
@@ -41495,11 +41695,15 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41495
41695
|
const storyId = ctx.storyId;
|
|
41496
41696
|
const packageDir = ctx.packageDir;
|
|
41497
41697
|
let totalCostUsd = 0;
|
|
41698
|
+
const spentStrategies = new Set;
|
|
41699
|
+
let unresolvedDetail;
|
|
41700
|
+
const finish = (result) => unresolvedDetail !== undefined && result.unresolvedDetail === undefined ? { ...result, unresolvedDetail } : result;
|
|
41498
41701
|
for (;; ) {
|
|
41499
41702
|
if (cycle.findings.length === 0 && cycle.verdict === undefined) {
|
|
41500
41703
|
return { iterations: cycle.iterations, finalFindings: [], exitReason: "resolved", costUsd: totalCostUsd };
|
|
41501
41704
|
}
|
|
41502
|
-
const
|
|
41705
|
+
const selectable = cycle.strategies.filter((s) => !spentStrategies.has(s.name));
|
|
41706
|
+
const active = selectActiveStrategies(selectable, cycle.findings, cycle.verdict);
|
|
41503
41707
|
if (active.length === 0) {
|
|
41504
41708
|
const orphanSources = [...new Set(cycle.findings.map((f) => f.source))];
|
|
41505
41709
|
logger?.warn("findings.cycle", "cycle exited \u2014 no matching strategy (orphaned findings)", {
|
|
@@ -41508,14 +41712,15 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41508
41712
|
cycleName,
|
|
41509
41713
|
reason: "no-strategy",
|
|
41510
41714
|
findingsCount: cycle.findings.length,
|
|
41511
|
-
orphanSources
|
|
41715
|
+
orphanSources,
|
|
41716
|
+
...spentStrategies.size > 0 ? { retiredStrategies: [...spentStrategies] } : {}
|
|
41512
41717
|
});
|
|
41513
|
-
return {
|
|
41718
|
+
return finish({
|
|
41514
41719
|
iterations: cycle.iterations,
|
|
41515
41720
|
finalFindings: cycle.findings,
|
|
41516
41721
|
exitReason: "no-strategy",
|
|
41517
41722
|
costUsd: totalCostUsd
|
|
41518
|
-
};
|
|
41723
|
+
});
|
|
41519
41724
|
}
|
|
41520
41725
|
const uncappedActive = active.filter((s) => countStrategyAttempts(cycle.iterations, s.name) < s.maxAttempts);
|
|
41521
41726
|
if (uncappedActive.length === 0) {
|
|
@@ -41527,13 +41732,13 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41527
41732
|
reason: "max-attempts-per-strategy",
|
|
41528
41733
|
exhaustedStrategy: exhaustedStrategy?.name
|
|
41529
41734
|
});
|
|
41530
|
-
return {
|
|
41735
|
+
return finish({
|
|
41531
41736
|
iterations: cycle.iterations,
|
|
41532
41737
|
finalFindings: cycle.findings,
|
|
41533
41738
|
exitReason: "max-attempts-per-strategy",
|
|
41534
41739
|
exhaustedStrategy: exhaustedStrategy?.name,
|
|
41535
41740
|
costUsd: totalCostUsd
|
|
41536
|
-
};
|
|
41741
|
+
});
|
|
41537
41742
|
}
|
|
41538
41743
|
const totalAttempts = countTotalAttempts(cycle.iterations);
|
|
41539
41744
|
if (totalAttempts >= cycle.config.maxAttemptsTotal) {
|
|
@@ -41545,12 +41750,12 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41545
41750
|
totalAttempts,
|
|
41546
41751
|
maxAttemptsTotal: cycle.config.maxAttemptsTotal
|
|
41547
41752
|
});
|
|
41548
|
-
return {
|
|
41753
|
+
return finish({
|
|
41549
41754
|
iterations: cycle.iterations,
|
|
41550
41755
|
finalFindings: cycle.findings,
|
|
41551
41756
|
exitReason: "max-attempts-total",
|
|
41552
41757
|
costUsd: totalCostUsd
|
|
41553
|
-
};
|
|
41758
|
+
});
|
|
41554
41759
|
}
|
|
41555
41760
|
for (const strategy of uncappedActive) {
|
|
41556
41761
|
const bailReason = strategy.bailWhen?.(cycle.iterations) ?? null;
|
|
@@ -41563,13 +41768,13 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41563
41768
|
strategyName: strategy.name,
|
|
41564
41769
|
bailDetail: bailReason
|
|
41565
41770
|
});
|
|
41566
|
-
return {
|
|
41771
|
+
return finish({
|
|
41567
41772
|
iterations: cycle.iterations,
|
|
41568
41773
|
finalFindings: cycle.findings,
|
|
41569
41774
|
exitReason: "bail-when",
|
|
41570
41775
|
bailDetail: bailReason,
|
|
41571
41776
|
costUsd: totalCostUsd
|
|
41572
|
-
};
|
|
41777
|
+
});
|
|
41573
41778
|
}
|
|
41574
41779
|
}
|
|
41575
41780
|
const group = selectExecutionGroup(uncappedActive);
|
|
@@ -41590,33 +41795,49 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41590
41795
|
costUsd: extracted.costUsd
|
|
41591
41796
|
});
|
|
41592
41797
|
}
|
|
41593
|
-
const
|
|
41594
|
-
if (
|
|
41595
|
-
const
|
|
41596
|
-
|
|
41597
|
-
|
|
41598
|
-
|
|
41599
|
-
|
|
41600
|
-
|
|
41601
|
-
|
|
41602
|
-
|
|
41603
|
-
|
|
41604
|
-
|
|
41605
|
-
|
|
41798
|
+
const unresolvedFas = fixesApplied.filter((fa) => fa.unresolved);
|
|
41799
|
+
if (unresolvedFas.length > 0) {
|
|
41800
|
+
const firstUnresolved = unresolvedFas[0];
|
|
41801
|
+
unresolvedDetail = firstUnresolved.unresolved;
|
|
41802
|
+
for (const fa of unresolvedFas)
|
|
41803
|
+
spentStrategies.add(fa.strategyName);
|
|
41804
|
+
const allGaveUp = unresolvedFas.length === fixesApplied.length;
|
|
41805
|
+
if (allGaveUp) {
|
|
41806
|
+
const finishedAt2 = now();
|
|
41807
|
+
cycle.iterations.push({
|
|
41808
|
+
iterationNum: cycle.iterations.length + 1,
|
|
41809
|
+
findingsBefore,
|
|
41810
|
+
fixesApplied,
|
|
41811
|
+
findingsAfter: cycle.findings,
|
|
41812
|
+
outcome: "unchanged",
|
|
41813
|
+
startedAt,
|
|
41814
|
+
finishedAt: finishedAt2
|
|
41815
|
+
});
|
|
41816
|
+
totalCostUsd += fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
|
|
41817
|
+
logger?.info("findings.cycle", "cycle exited \u2014 agent gave up", {
|
|
41818
|
+
storyId,
|
|
41819
|
+
packageDir,
|
|
41820
|
+
cycleName,
|
|
41821
|
+
reason: "agent-gave-up",
|
|
41822
|
+
strategyName: firstUnresolved.strategyName,
|
|
41823
|
+
unresolvedDetail: firstUnresolved.unresolved
|
|
41824
|
+
});
|
|
41825
|
+
return finish({
|
|
41826
|
+
iterations: cycle.iterations,
|
|
41827
|
+
finalFindings: cycle.findings,
|
|
41828
|
+
exitReason: "agent-gave-up",
|
|
41829
|
+
unresolvedDetail: firstUnresolved.unresolved,
|
|
41830
|
+
costUsd: totalCostUsd
|
|
41831
|
+
});
|
|
41832
|
+
}
|
|
41833
|
+
logger?.info("findings.cycle", "strategy gave up \u2014 retired, continuing with co-run siblings", {
|
|
41606
41834
|
storyId,
|
|
41607
41835
|
packageDir,
|
|
41608
41836
|
cycleName,
|
|
41609
|
-
|
|
41610
|
-
|
|
41611
|
-
|
|
41837
|
+
strategyName: firstUnresolved.strategyName,
|
|
41838
|
+
unresolvedDetail: firstUnresolved.unresolved,
|
|
41839
|
+
ranWithoutGivingUp: fixesApplied.filter((fa) => !fa.unresolved).map((fa) => fa.strategyName)
|
|
41612
41840
|
});
|
|
41613
|
-
return {
|
|
41614
|
-
iterations: cycle.iterations,
|
|
41615
|
-
finalFindings: cycle.findings,
|
|
41616
|
-
exitReason: "agent-gave-up",
|
|
41617
|
-
unresolvedDetail: unresolvedFa.unresolved,
|
|
41618
|
-
costUsd: totalCostUsd
|
|
41619
|
-
};
|
|
41620
41841
|
}
|
|
41621
41842
|
const allExhausted = group.every((s) => {
|
|
41622
41843
|
const prior = countStrategyAttempts(cycle.iterations, s.name);
|
|
@@ -41624,6 +41845,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41624
41845
|
return prior + current >= s.maxAttempts;
|
|
41625
41846
|
});
|
|
41626
41847
|
if (allExhausted) {
|
|
41848
|
+
totalCostUsd += fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
|
|
41627
41849
|
let liteFindingsAfter;
|
|
41628
41850
|
let liteShortCircuited = false;
|
|
41629
41851
|
try {
|
|
@@ -41648,13 +41870,13 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41648
41870
|
cycleName,
|
|
41649
41871
|
error: errorMessage(err)
|
|
41650
41872
|
});
|
|
41651
|
-
return {
|
|
41873
|
+
return finish({
|
|
41652
41874
|
iterations: cycle.iterations,
|
|
41653
41875
|
finalFindings: cycle.findings,
|
|
41654
41876
|
exitReason: "max-attempts-per-strategy",
|
|
41655
41877
|
exhaustedStrategy: group[0]?.name,
|
|
41656
41878
|
costUsd: totalCostUsd
|
|
41657
|
-
};
|
|
41879
|
+
});
|
|
41658
41880
|
}
|
|
41659
41881
|
const outcome2 = classifyOutcome(findingsBefore, liteFindingsAfter);
|
|
41660
41882
|
const finishedAt2 = now();
|
|
@@ -41675,18 +41897,16 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41675
41897
|
cycleName,
|
|
41676
41898
|
reason: "resolved"
|
|
41677
41899
|
});
|
|
41678
|
-
return {
|
|
41900
|
+
return finish({
|
|
41679
41901
|
iterations: cycle.iterations,
|
|
41680
41902
|
finalFindings: [],
|
|
41681
41903
|
exitReason: "resolved",
|
|
41682
41904
|
costUsd: totalCostUsd
|
|
41683
|
-
};
|
|
41905
|
+
});
|
|
41684
41906
|
}
|
|
41685
41907
|
if (liteShortCircuited) {
|
|
41686
41908
|
const companions = uncappedActive.filter((s) => !group.includes(s));
|
|
41687
41909
|
if (companions.length > 0) {
|
|
41688
|
-
const iterCostUsd = fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
|
|
41689
|
-
totalCostUsd += iterCostUsd;
|
|
41690
41910
|
logger?.info("findings.cycle", "exclusive strategy exhausted \u2014 continuing to companion strategies", {
|
|
41691
41911
|
storyId,
|
|
41692
41912
|
packageDir,
|
|
@@ -41703,12 +41923,12 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41703
41923
|
reason: "validate-short-circuit",
|
|
41704
41924
|
liteFindingsAfterCount: liteFindingsAfter.length
|
|
41705
41925
|
});
|
|
41706
|
-
return {
|
|
41926
|
+
return finish({
|
|
41707
41927
|
iterations: cycle.iterations,
|
|
41708
41928
|
finalFindings: liteFindingsAfter,
|
|
41709
41929
|
exitReason: "validate-short-circuit",
|
|
41710
41930
|
costUsd: totalCostUsd
|
|
41711
|
-
};
|
|
41931
|
+
});
|
|
41712
41932
|
}
|
|
41713
41933
|
logger?.info("findings.cycle", "cycle exited \u2014 strategy attempt cap reached (lite validate)", {
|
|
41714
41934
|
storyId,
|
|
@@ -41718,13 +41938,13 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41718
41938
|
exhaustedStrategy: group[0]?.name,
|
|
41719
41939
|
liteFindingsAfterCount: liteFindingsAfter.length
|
|
41720
41940
|
});
|
|
41721
|
-
return {
|
|
41941
|
+
return finish({
|
|
41722
41942
|
iterations: cycle.iterations,
|
|
41723
41943
|
finalFindings: liteFindingsAfter,
|
|
41724
41944
|
exitReason: "max-attempts-per-strategy",
|
|
41725
41945
|
exhaustedStrategy: group[0]?.name,
|
|
41726
41946
|
costUsd: totalCostUsd
|
|
41727
|
-
};
|
|
41947
|
+
});
|
|
41728
41948
|
}
|
|
41729
41949
|
let findingsAfter;
|
|
41730
41950
|
let validatorAttempt = 0;
|
|
@@ -41742,12 +41962,12 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41742
41962
|
reason: "validator-error",
|
|
41743
41963
|
error: errorMessage(err)
|
|
41744
41964
|
});
|
|
41745
|
-
return {
|
|
41965
|
+
return finish({
|
|
41746
41966
|
iterations: cycle.iterations,
|
|
41747
41967
|
finalFindings: cycle.findings,
|
|
41748
41968
|
exitReason: "validator-error",
|
|
41749
41969
|
costUsd: totalCostUsd
|
|
41750
|
-
};
|
|
41970
|
+
});
|
|
41751
41971
|
}
|
|
41752
41972
|
logger?.warn("findings.cycle", "validator retry", {
|
|
41753
41973
|
storyId,
|
|
@@ -42084,11 +42304,9 @@ function buildMeta(f, originalSeverity) {
|
|
|
42084
42304
|
function findingCategory(f) {
|
|
42085
42305
|
return "category" in f && f.category ? f.category : undefined;
|
|
42086
42306
|
}
|
|
42087
|
-
function deriveFixTargetForReviewFinding(category, source) {
|
|
42088
|
-
|
|
42089
|
-
|
|
42090
|
-
}
|
|
42091
|
-
return categoryToFixTarget(category);
|
|
42307
|
+
function deriveFixTargetForReviewFinding(category, source, file3, isTestFile3) {
|
|
42308
|
+
const base = source === "semantic-review" || source === "semantic-debate-review" ? "source" : categoryToFixTarget(category);
|
|
42309
|
+
return resolveFixTarget({ base, file: file3, isTestFile: isTestFile3 });
|
|
42092
42310
|
}
|
|
42093
42311
|
function llmFindingToReviewFinding(f, opts = {}) {
|
|
42094
42312
|
const category = findingCategory(f);
|
|
@@ -42105,7 +42323,7 @@ function llmFindingToReviewFinding(f, opts = {}) {
|
|
|
42105
42323
|
result.category = category;
|
|
42106
42324
|
if (source)
|
|
42107
42325
|
result.source = source;
|
|
42108
|
-
result.fixTarget = deriveFixTargetForReviewFinding(category, source);
|
|
42326
|
+
result.fixTarget = deriveFixTargetForReviewFinding(category, source, f.file, opts.isTestFile);
|
|
42109
42327
|
const meta3 = buildMeta(f, f.severity !== narrowed ? f.severity : undefined);
|
|
42110
42328
|
if (meta3)
|
|
42111
42329
|
result.meta = meta3;
|
|
@@ -42275,7 +42493,7 @@ var package_default;
|
|
|
42275
42493
|
var init_package = __esm(() => {
|
|
42276
42494
|
package_default = {
|
|
42277
42495
|
name: "@nathapp/nax",
|
|
42278
|
-
version: "0.
|
|
42496
|
+
version: "0.75.1",
|
|
42279
42497
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
42280
42498
|
type: "module",
|
|
42281
42499
|
bin: {
|
|
@@ -42378,8 +42596,8 @@ var init_version = __esm(() => {
|
|
|
42378
42596
|
NAX_VERSION = package_default.version;
|
|
42379
42597
|
NAX_COMMIT = (() => {
|
|
42380
42598
|
try {
|
|
42381
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
42382
|
-
return "
|
|
42599
|
+
if (/^[0-9a-f]{6,10}$/.test("928c0354"))
|
|
42600
|
+
return "928c0354";
|
|
42383
42601
|
} catch {}
|
|
42384
42602
|
try {
|
|
42385
42603
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -42785,12 +43003,12 @@ async function runAdversarialReview(opts) {
|
|
|
42785
43003
|
} = classifyRecurrence(allFindings, priorAdversarialIterations ?? [], recurrenceCfg, testFileMatch, threshold);
|
|
42786
43004
|
const advisoryFindings = [...advisoryOnly, ...demoted];
|
|
42787
43005
|
const advisoryReviewFindings = [
|
|
42788
|
-
...llmFindingsToReviewFindings(advisoryOnly, { source: "adversarial-review" }),
|
|
42789
|
-
...tagCoverageGap(llmFindingsToReviewFindings(demoted, { source: "adversarial-review" }))
|
|
43006
|
+
...llmFindingsToReviewFindings(advisoryOnly, { source: "adversarial-review", isTestFile: testFileMatch }),
|
|
43007
|
+
...tagCoverageGap(llmFindingsToReviewFindings(demoted, { source: "adversarial-review", isTestFile: testFileMatch }))
|
|
42790
43008
|
];
|
|
42791
43009
|
const advisoryFindingsAsFindings = [
|
|
42792
|
-
...toAdversarialReviewFindings(advisoryOnly),
|
|
42793
|
-
...tagCoverageGap(toAdversarialReviewFindings(demoted))
|
|
43010
|
+
...toAdversarialReviewFindings(advisoryOnly, { isTestFile: testFileMatch }),
|
|
43011
|
+
...tagCoverageGap(toAdversarialReviewFindings(demoted, { isTestFile: testFileMatch }))
|
|
42794
43012
|
];
|
|
42795
43013
|
const acDropped = opResult.acDropped ?? [];
|
|
42796
43014
|
let diffFiles;
|
|
@@ -42870,7 +43088,7 @@ async function runAdversarialReview(opts) {
|
|
|
42870
43088
|
blockingThreshold: threshold,
|
|
42871
43089
|
result: {
|
|
42872
43090
|
passed: false,
|
|
42873
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review" })
|
|
43091
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review", isTestFile: testFileMatch })
|
|
42874
43092
|
},
|
|
42875
43093
|
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
42876
43094
|
diffAvailable,
|
|
@@ -42887,7 +43105,7 @@ ${formatFindings(blockingFindings)}` : "Adversarial review failed (no findings)"
|
|
|
42887
43105
|
exitCode: 1,
|
|
42888
43106
|
output,
|
|
42889
43107
|
durationMs,
|
|
42890
|
-
findings: blockingFindings.length > 0 ? toAdversarialReviewFindings(blockingFindings) : undefined,
|
|
43108
|
+
findings: blockingFindings.length > 0 ? toAdversarialReviewFindings(blockingFindings, { isTestFile: testFileMatch }) : undefined,
|
|
42891
43109
|
advisoryFindings: advisoryFindings.length > 0 ? advisoryFindingsAsFindings : undefined,
|
|
42892
43110
|
cost: llmCost
|
|
42893
43111
|
};
|
|
@@ -42895,7 +43113,7 @@ ${formatFindings(blockingFindings)}` : "Adversarial review failed (no findings)"
|
|
|
42895
43113
|
if (!opResult.passed && acDropped.length > 0) {
|
|
42896
43114
|
const allHallucinated = acDropped.every((d) => d.code === "ac_quote_not_substring");
|
|
42897
43115
|
if (allHallucinated) {
|
|
42898
|
-
const demotedFindings = toAdversarialReviewFindings(acDropped.map((d) => ({ ...d.finding, severity: "warning", acQuote: undefined, acIndex: undefined })));
|
|
43116
|
+
const demotedFindings = toAdversarialReviewFindings(acDropped.map((d) => ({ ...d.finding, severity: "warning", acQuote: undefined, acIndex: undefined })), { isTestFile: testFileMatch });
|
|
42899
43117
|
const existingAdvisory = advisoryFindings.length > 0 ? advisoryFindingsAsFindings : [];
|
|
42900
43118
|
const allAdvisory = [...existingAdvisory, ...demotedFindings];
|
|
42901
43119
|
logger?.warn("review", "Adversarial review passed: all blocking findings discarded as hallucinated AC quotes", {
|
|
@@ -42983,7 +43201,7 @@ ${dropSummary}`,
|
|
|
42983
43201
|
blockingThreshold: threshold,
|
|
42984
43202
|
result: {
|
|
42985
43203
|
passed: true,
|
|
42986
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review" })
|
|
43204
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review", isTestFile: testFileMatch })
|
|
42987
43205
|
},
|
|
42988
43206
|
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
42989
43207
|
diffAvailable,
|
|
@@ -43488,7 +43706,8 @@ async function runSemanticDebate(opts) {
|
|
|
43488
43706
|
prompt,
|
|
43489
43707
|
productionExcludePatterns,
|
|
43490
43708
|
blockingThreshold,
|
|
43491
|
-
createDebateRunner
|
|
43709
|
+
createDebateRunner,
|
|
43710
|
+
isTestFile: isTestFile3
|
|
43492
43711
|
} = opts;
|
|
43493
43712
|
const logger = getSafeLogger();
|
|
43494
43713
|
const configuredStageConfig = naxConfig.debate?.stages.review;
|
|
@@ -43564,9 +43783,9 @@ async function runSemanticDebate(opts) {
|
|
|
43564
43783
|
blockingThreshold: debateThreshold,
|
|
43565
43784
|
result: {
|
|
43566
43785
|
passed: false,
|
|
43567
|
-
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review" })
|
|
43786
|
+
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review", isTestFile: isTestFile3 })
|
|
43568
43787
|
},
|
|
43569
|
-
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review" }) : undefined
|
|
43788
|
+
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review", isTestFile: isTestFile3 }) : undefined
|
|
43570
43789
|
});
|
|
43571
43790
|
return {
|
|
43572
43791
|
check: "semantic",
|
|
@@ -43577,8 +43796,8 @@ async function runSemanticDebate(opts) {
|
|
|
43577
43796
|
|
|
43578
43797
|
${formatFindings2(debateBlocking)}`,
|
|
43579
43798
|
durationMs,
|
|
43580
|
-
findings: toReviewFindings(debateBlocking),
|
|
43581
|
-
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory) : undefined,
|
|
43799
|
+
findings: toReviewFindings(debateBlocking, { isTestFile: isTestFile3 }),
|
|
43800
|
+
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory, { isTestFile: isTestFile3 }) : undefined,
|
|
43582
43801
|
cost: debateCost
|
|
43583
43802
|
};
|
|
43584
43803
|
}
|
|
@@ -43596,9 +43815,9 @@ ${formatFindings2(debateBlocking)}`,
|
|
|
43596
43815
|
blockingThreshold: debateThreshold,
|
|
43597
43816
|
result: {
|
|
43598
43817
|
passed: true,
|
|
43599
|
-
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review" })
|
|
43818
|
+
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review", isTestFile: isTestFile3 })
|
|
43600
43819
|
},
|
|
43601
|
-
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review" }) : undefined
|
|
43820
|
+
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review", isTestFile: isTestFile3 }) : undefined
|
|
43602
43821
|
});
|
|
43603
43822
|
return {
|
|
43604
43823
|
check: "semantic",
|
|
@@ -43607,7 +43826,7 @@ ${formatFindings2(debateBlocking)}`,
|
|
|
43607
43826
|
exitCode: 0,
|
|
43608
43827
|
output: "Semantic review passed (debate, all findings were advisory \u2014 below blocking threshold)",
|
|
43609
43828
|
durationMs,
|
|
43610
|
-
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory) : undefined,
|
|
43829
|
+
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory, { isTestFile: isTestFile3 }) : undefined,
|
|
43611
43830
|
cost: debateCost
|
|
43612
43831
|
};
|
|
43613
43832
|
}
|
|
@@ -43622,9 +43841,9 @@ ${formatFindings2(debateBlocking)}`,
|
|
|
43622
43841
|
blockingThreshold: debateThreshold,
|
|
43623
43842
|
result: {
|
|
43624
43843
|
passed: true,
|
|
43625
|
-
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review" })
|
|
43844
|
+
findings: llmFindingsToReviewFindings(debateFindings, { source: "semantic-debate-review", isTestFile: isTestFile3 })
|
|
43626
43845
|
},
|
|
43627
|
-
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review" }) : undefined
|
|
43846
|
+
advisoryFindings: debateAdvisory.length > 0 ? llmFindingsToReviewFindings(debateAdvisory, { source: "semantic-debate-review", isTestFile: isTestFile3 }) : undefined
|
|
43628
43847
|
});
|
|
43629
43848
|
return {
|
|
43630
43849
|
check: "semantic",
|
|
@@ -43633,7 +43852,7 @@ ${formatFindings2(debateBlocking)}`,
|
|
|
43633
43852
|
exitCode: 0,
|
|
43634
43853
|
output: "Semantic review passed",
|
|
43635
43854
|
durationMs,
|
|
43636
|
-
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory) : undefined,
|
|
43855
|
+
advisoryFindings: debateAdvisory.length > 0 ? toReviewFindings(debateAdvisory, { isTestFile: isTestFile3 }) : undefined,
|
|
43637
43856
|
cost: debateCost
|
|
43638
43857
|
};
|
|
43639
43858
|
}
|
|
@@ -43679,10 +43898,13 @@ async function runSemanticReview(opts) {
|
|
|
43679
43898
|
contextBundle,
|
|
43680
43899
|
projectDir,
|
|
43681
43900
|
naxIgnoreIndex,
|
|
43682
|
-
runtime
|
|
43901
|
+
runtime,
|
|
43902
|
+
resolvedTestPatterns
|
|
43683
43903
|
} = opts;
|
|
43684
43904
|
const startTime = Date.now();
|
|
43685
43905
|
const logger = getSafeLogger();
|
|
43906
|
+
const testFilePatterns = resolvedTestPatterns?.regex ?? [];
|
|
43907
|
+
const testFileMatch = (file3) => testFilePatterns.some((re) => re.test(file3));
|
|
43686
43908
|
if (featureName === undefined) {
|
|
43687
43909
|
logger?.debug("semantic", "featureName missing \u2014 semantic session name will not include feature", {
|
|
43688
43910
|
storyId: story.id
|
|
@@ -43815,6 +44037,7 @@ async function runSemanticReview(opts) {
|
|
|
43815
44037
|
prompt,
|
|
43816
44038
|
productionExcludePatterns: excludePatterns,
|
|
43817
44039
|
blockingThreshold,
|
|
44040
|
+
isTestFile: testFileMatch,
|
|
43818
44041
|
createDebateRunner: _semanticDeps.createDebateRunner
|
|
43819
44042
|
});
|
|
43820
44043
|
}
|
|
@@ -43974,9 +44197,9 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
43974
44197
|
blockingThreshold: threshold,
|
|
43975
44198
|
result: {
|
|
43976
44199
|
passed: false,
|
|
43977
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review" })
|
|
44200
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review", isTestFile: testFileMatch })
|
|
43978
44201
|
},
|
|
43979
|
-
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review" }) : undefined
|
|
44202
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
43980
44203
|
});
|
|
43981
44204
|
return {
|
|
43982
44205
|
check: "semantic",
|
|
@@ -43985,8 +44208,8 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
43985
44208
|
exitCode: 1,
|
|
43986
44209
|
output,
|
|
43987
44210
|
durationMs,
|
|
43988
|
-
findings: toReviewFindings(blockingFindings),
|
|
43989
|
-
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings) : undefined,
|
|
44211
|
+
findings: toReviewFindings(blockingFindings, { isTestFile: testFileMatch }),
|
|
44212
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
43990
44213
|
cost: llmCost
|
|
43991
44214
|
};
|
|
43992
44215
|
}
|
|
@@ -44006,7 +44229,7 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
44006
44229
|
passed: false,
|
|
44007
44230
|
blockingThreshold: threshold,
|
|
44008
44231
|
result: { passed: false, findings: [] },
|
|
44009
|
-
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review" }) : undefined
|
|
44232
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
44010
44233
|
});
|
|
44011
44234
|
return {
|
|
44012
44235
|
check: "semantic",
|
|
@@ -44015,7 +44238,7 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
44015
44238
|
exitCode: 1,
|
|
44016
44239
|
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
44240
|
durationMs,
|
|
44018
|
-
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings) : undefined,
|
|
44241
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
44019
44242
|
cost: llmCost
|
|
44020
44243
|
};
|
|
44021
44244
|
}
|
|
@@ -44032,9 +44255,9 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
44032
44255
|
blockingThreshold: threshold,
|
|
44033
44256
|
result: {
|
|
44034
44257
|
passed: true,
|
|
44035
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review" })
|
|
44258
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review", isTestFile: testFileMatch })
|
|
44036
44259
|
},
|
|
44037
|
-
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review" }) : undefined
|
|
44260
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
44038
44261
|
});
|
|
44039
44262
|
return {
|
|
44040
44263
|
check: "semantic",
|
|
@@ -44043,7 +44266,7 @@ ${formatFindings2(blockingFindings)}`;
|
|
|
44043
44266
|
exitCode: 0,
|
|
44044
44267
|
output: allFindings.length === 0 ? "Semantic review passed" : "Semantic review passed (all findings were advisory \u2014 below blocking threshold)",
|
|
44045
44268
|
durationMs,
|
|
44046
|
-
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings) : undefined,
|
|
44269
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
44047
44270
|
cost: llmCost
|
|
44048
44271
|
};
|
|
44049
44272
|
}
|
|
@@ -45339,6 +45562,43 @@ var init_rectifier_builder = __esm(() => {
|
|
|
45339
45562
|
];
|
|
45340
45563
|
});
|
|
45341
45564
|
|
|
45565
|
+
// src/prompts/builders/timeout-retry-builder.ts
|
|
45566
|
+
function formatDuration2(ms) {
|
|
45567
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
45568
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
45569
|
+
const seconds = totalSeconds % 60;
|
|
45570
|
+
if (minutes === 0)
|
|
45571
|
+
return `${seconds}s`;
|
|
45572
|
+
return `${minutes}m ${seconds}s`;
|
|
45573
|
+
}
|
|
45574
|
+
function timeoutRetry(input) {
|
|
45575
|
+
const { prompt, changedFiles, elapsedMs, attempt } = input;
|
|
45576
|
+
const duration3 = formatDuration2(elapsedMs);
|
|
45577
|
+
const attemptNumber = attempt + 1;
|
|
45578
|
+
if (changedFiles.length === 0) {
|
|
45579
|
+
return `The previous attempt hit a timeout after ${elapsedMs}ms (${duration3}) with no file changes on disk.
|
|
45580
|
+
This is attempt ${attemptNumber} of the same story \u2014 the previous attempt left nothing behind, so the approach was wrong.
|
|
45581
|
+
Change your approach: pick a narrower scope, fewer file edits, or a different angle on the acceptance criteria.
|
|
45582
|
+
|
|
45583
|
+
---
|
|
45584
|
+
|
|
45585
|
+
${prompt}`;
|
|
45586
|
+
}
|
|
45587
|
+
const fileList = changedFiles.map((p) => `- ${p}`).join(`
|
|
45588
|
+
`);
|
|
45589
|
+
return `The previous attempt hit a timeout after ${elapsedMs}ms (${duration3}), but left these files on disk:
|
|
45590
|
+
|
|
45591
|
+
${fileList}
|
|
45592
|
+
|
|
45593
|
+
This is attempt ${attemptNumber} of the same story \u2014 continue from the existing state above.
|
|
45594
|
+
Read the files listed, pick up where the previous attempt stopped, and finish the story.
|
|
45595
|
+
Do NOT delete or revert the existing work; treat the working tree as the starting point.
|
|
45596
|
+
|
|
45597
|
+
---
|
|
45598
|
+
|
|
45599
|
+
${prompt}`;
|
|
45600
|
+
}
|
|
45601
|
+
|
|
45342
45602
|
// src/prompts/builders/one-shot-builder.ts
|
|
45343
45603
|
class OneShotPromptBuilder {
|
|
45344
45604
|
acc = new SectionAccumulator;
|
|
@@ -45501,7 +45761,7 @@ No acceptance criterion may use a deprecated verification tag (\`[grep]\`, \`[fi
|
|
|
45501
45761
|
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
45762
|
|
|
45503
45763
|
#### 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.
|
|
45764
|
+
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
45765
|
|
|
45506
45766
|
#### ac-testable
|
|
45507
45767
|
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 +45813,6 @@ For each one:
|
|
|
45553
45813
|
- Remove any shell-command patterns (\`grep -\`, \`wc\`, pipe \`|\` inside backticks). Express the same invariant as an assertion on the runtime value.
|
|
45554
45814
|
- Do not remove or weaken acceptance criteria that are already correct.
|
|
45555
45815
|
|
|
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
45816
|
Write the corrected PRD to this file path: ${outputFilePath}
|
|
45574
45817
|
Do not output the PRD in chat. After writing the file, reply with a brief text confirmation only.`;
|
|
45575
45818
|
}
|
|
@@ -46310,10 +46553,17 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46310
46553
|
hopBodyInput
|
|
46311
46554
|
} = ctx;
|
|
46312
46555
|
const stage = pipelineStage ?? "run";
|
|
46556
|
+
let preAttemptGitRefPromise;
|
|
46557
|
+
let priorHopStartedAt;
|
|
46313
46558
|
return async (agentName, hopBundle, hopKind, resolvedRunOptions) => {
|
|
46314
46559
|
const logger = getLogger();
|
|
46315
46560
|
let workingBundle = hopBundle;
|
|
46316
46561
|
let prompt = resolvedRunOptions.prompt;
|
|
46562
|
+
const elapsedSincePriorHop = priorHopStartedAt ? Date.now() - priorHopStartedAt : 0;
|
|
46563
|
+
priorHopStartedAt = Date.now();
|
|
46564
|
+
if (hopKind.kind === "primary" && !preAttemptGitRefPromise) {
|
|
46565
|
+
preAttemptGitRefPromise = _buildHopCallbackDeps.captureGitRef(workdir);
|
|
46566
|
+
}
|
|
46317
46567
|
if (hopKind.kind === "swap" && hopBundle) {
|
|
46318
46568
|
workingBundle = _buildHopCallbackDeps.rebuildForAgent(hopBundle, agentName, hopKind.failure, story.id);
|
|
46319
46569
|
if (projectDir && featureName && workingBundle.manifest.rebuildInfo) {
|
|
@@ -46342,6 +46592,17 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46342
46592
|
if (hopKind.kind === "swap" && sessionId) {
|
|
46343
46593
|
sessionManager.handoff?.(sessionId, agentName, hopKind.failure.outcome);
|
|
46344
46594
|
}
|
|
46595
|
+
if (hopKind.kind === "timeout-retry") {
|
|
46596
|
+
const preAttemptGitRef = preAttemptGitRefPromise ? await preAttemptGitRefPromise : undefined;
|
|
46597
|
+
const changedFiles = preAttemptGitRef ? await _buildHopCallbackDeps.captureWorkingTreeChanges(workdir, preAttemptGitRef) : [];
|
|
46598
|
+
const elapsedMs = elapsedSincePriorHop;
|
|
46599
|
+
prompt = _buildHopCallbackDeps.timeoutRetry({
|
|
46600
|
+
prompt: resolvedRunOptions.prompt,
|
|
46601
|
+
changedFiles,
|
|
46602
|
+
elapsedMs,
|
|
46603
|
+
attempt: hopKind.attempt
|
|
46604
|
+
});
|
|
46605
|
+
}
|
|
46345
46606
|
const contextToolRuntime = workingBundle ? _buildHopCallbackDeps.createContextToolRuntime({
|
|
46346
46607
|
bundle: workingBundle,
|
|
46347
46608
|
story,
|
|
@@ -46400,6 +46661,7 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46400
46661
|
signal: resolvedRunOptions.abortSignal
|
|
46401
46662
|
});
|
|
46402
46663
|
}
|
|
46664
|
+
let timedOut = false;
|
|
46403
46665
|
try {
|
|
46404
46666
|
const send = (turnPrompt) => agentManager.runAsSession(agentName, handle, turnPrompt, {
|
|
46405
46667
|
storyId: story.id,
|
|
@@ -46417,9 +46679,12 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46417
46679
|
...maxInteractionTurns !== undefined ? { maxTurns: maxInteractionTurns } : {}
|
|
46418
46680
|
});
|
|
46419
46681
|
const turnResult = hopBody ? await hopBody(prompt, { send, input: hopBodyInput }) : await send(prompt);
|
|
46682
|
+
if (turnResult.timedOut)
|
|
46683
|
+
timedOut = true;
|
|
46420
46684
|
return { result: turnResultToAgentResult(turnResult), bundle: workingBundle, prompt };
|
|
46421
46685
|
} catch (err) {
|
|
46422
46686
|
const sessionFailure = err instanceof SessionFailureError ? err.adapterFailure : undefined;
|
|
46687
|
+
timedOut = sessionFailure?.outcome === "fail-timeout";
|
|
46423
46688
|
const turnError = err instanceof SessionTurnError ? err : undefined;
|
|
46424
46689
|
const errMessage = err instanceof Error ? err.message : String(err);
|
|
46425
46690
|
return {
|
|
@@ -46441,7 +46706,7 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
46441
46706
|
prompt
|
|
46442
46707
|
};
|
|
46443
46708
|
} finally {
|
|
46444
|
-
if (hopKind.kind !== "stale-retry" && !resolvedRunOptions.keepOpen) {
|
|
46709
|
+
if (hopKind.kind !== "stale-retry" && (!resolvedRunOptions.keepOpen || timedOut)) {
|
|
46445
46710
|
await sessionManager.closeSession(handle);
|
|
46446
46711
|
}
|
|
46447
46712
|
}
|
|
@@ -46456,10 +46721,14 @@ var init_build_hop_callback = __esm(() => {
|
|
|
46456
46721
|
init_manifest_store();
|
|
46457
46722
|
init_logger2();
|
|
46458
46723
|
init_prompts();
|
|
46724
|
+
init_git();
|
|
46459
46725
|
_buildHopCallbackDeps = {
|
|
46460
46726
|
rebuildForAgent: (prior, newAgentId, failure, storyId) => new ContextOrchestrator([]).rebuildForAgent(prior, { newAgentId, failure, storyId }),
|
|
46461
46727
|
writeRebuildManifest,
|
|
46462
|
-
createContextToolRuntime
|
|
46728
|
+
createContextToolRuntime,
|
|
46729
|
+
captureGitRef,
|
|
46730
|
+
captureWorkingTreeChanges,
|
|
46731
|
+
timeoutRetry: (input) => timeoutRetry(input)
|
|
46463
46732
|
};
|
|
46464
46733
|
});
|
|
46465
46734
|
|
|
@@ -46664,17 +46933,10 @@ async function callOp(ctx, op, input) {
|
|
|
46664
46933
|
effective = { ...turn, output: fileContent };
|
|
46665
46934
|
}
|
|
46666
46935
|
}
|
|
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
|
-
};
|
|
46936
|
+
if (!effective.output?.trim()) {
|
|
46937
|
+
const failure = classifyEmptyOutputFailure(effective);
|
|
46938
|
+
if (failure)
|
|
46939
|
+
return { ...effective, adapterFailure: failure };
|
|
46678
46940
|
}
|
|
46679
46941
|
return effective;
|
|
46680
46942
|
};
|
|
@@ -52249,11 +52511,142 @@ var init_cli = __esm(() => {
|
|
|
52249
52511
|
CLIConfigSchema = exports_external.object({}).passthrough();
|
|
52250
52512
|
});
|
|
52251
52513
|
|
|
52514
|
+
// src/interaction/plugins/telegram-format.ts
|
|
52515
|
+
function buildHeader(request) {
|
|
52516
|
+
const emoji3 = getStageEmoji(request.stage);
|
|
52517
|
+
let text = `${emoji3} *${request.stage.toUpperCase()}*
|
|
52518
|
+
`;
|
|
52519
|
+
text += `*Feature:* ${request.featureName}
|
|
52520
|
+
`;
|
|
52521
|
+
if (request.storyId) {
|
|
52522
|
+
text += `*Story:* ${request.storyId}
|
|
52523
|
+
`;
|
|
52524
|
+
}
|
|
52525
|
+
text += `
|
|
52526
|
+
`;
|
|
52527
|
+
return text;
|
|
52528
|
+
}
|
|
52529
|
+
function buildBody(request) {
|
|
52530
|
+
let text = `${sanitizeMarkdown(request.summary)}
|
|
52531
|
+
`;
|
|
52532
|
+
if (request.detail) {
|
|
52533
|
+
text += `
|
|
52534
|
+
${sanitizeMarkdown(request.detail)}
|
|
52535
|
+
`;
|
|
52536
|
+
}
|
|
52537
|
+
if (request.options && request.options.length > 0) {
|
|
52538
|
+
text += `
|
|
52539
|
+
*Options:*
|
|
52540
|
+
`;
|
|
52541
|
+
for (const opt of request.options) {
|
|
52542
|
+
const desc = opt.description ? ` - ${sanitizeMarkdown(opt.description)}` : "";
|
|
52543
|
+
text += ` - ${opt.label}${desc}
|
|
52544
|
+
`;
|
|
52545
|
+
}
|
|
52546
|
+
}
|
|
52547
|
+
if (request.timeout) {
|
|
52548
|
+
const timeoutSec = Math.floor(request.timeout / 1000);
|
|
52549
|
+
text += `
|
|
52550
|
+
\u23F1 Timeout: ${timeoutSec}s | Fallback: ${request.fallback}`;
|
|
52551
|
+
}
|
|
52552
|
+
return text;
|
|
52553
|
+
}
|
|
52554
|
+
function sanitizeMarkdown(text) {
|
|
52555
|
+
return text.replace(/\\(?=[_*`\[])/g, "\\\\").replace(/_/g, "\\_").replace(/`/g, "\\`").replace(/\*/g, "\\*").replace(/\[/g, "\\[");
|
|
52556
|
+
}
|
|
52557
|
+
function splitText(text, maxChars) {
|
|
52558
|
+
if (text.length <= maxChars)
|
|
52559
|
+
return [text];
|
|
52560
|
+
const chunks = [];
|
|
52561
|
+
let remaining = text;
|
|
52562
|
+
while (remaining.length > maxChars) {
|
|
52563
|
+
const slice = remaining.slice(0, maxChars);
|
|
52564
|
+
const lastNewline = slice.lastIndexOf(`
|
|
52565
|
+
`);
|
|
52566
|
+
if (lastNewline > maxChars * 0.5) {
|
|
52567
|
+
chunks.push(remaining.slice(0, lastNewline));
|
|
52568
|
+
remaining = remaining.slice(lastNewline + 1);
|
|
52569
|
+
} else {
|
|
52570
|
+
chunks.push(slice);
|
|
52571
|
+
remaining = remaining.slice(maxChars);
|
|
52572
|
+
}
|
|
52573
|
+
}
|
|
52574
|
+
if (remaining.length > 0)
|
|
52575
|
+
chunks.push(remaining);
|
|
52576
|
+
return chunks;
|
|
52577
|
+
}
|
|
52578
|
+
function buildKeyboard(request) {
|
|
52579
|
+
switch (request.type) {
|
|
52580
|
+
case "confirm":
|
|
52581
|
+
return [
|
|
52582
|
+
[
|
|
52583
|
+
{ text: "\u2705 Approve", callback_data: `${request.id}:approve` },
|
|
52584
|
+
{ text: "\u274C Reject", callback_data: `${request.id}:reject` }
|
|
52585
|
+
],
|
|
52586
|
+
[
|
|
52587
|
+
{ text: "\u23ED Skip", callback_data: `${request.id}:skip` },
|
|
52588
|
+
{ text: "\uD83D\uDED1 Abort", callback_data: `${request.id}:abort` }
|
|
52589
|
+
]
|
|
52590
|
+
];
|
|
52591
|
+
case "choose": {
|
|
52592
|
+
if (!request.options || request.options.length === 0)
|
|
52593
|
+
return null;
|
|
52594
|
+
const rows = [];
|
|
52595
|
+
for (const opt of request.options) {
|
|
52596
|
+
rows.push([{ text: opt.label, callback_data: `${request.id}:choose:${opt.key}` }]);
|
|
52597
|
+
}
|
|
52598
|
+
rows.push([
|
|
52599
|
+
{ text: "\u23ED Skip", callback_data: `${request.id}:skip` },
|
|
52600
|
+
{ text: "\uD83D\uDED1 Abort", callback_data: `${request.id}:abort` }
|
|
52601
|
+
]);
|
|
52602
|
+
return rows;
|
|
52603
|
+
}
|
|
52604
|
+
case "review":
|
|
52605
|
+
return [
|
|
52606
|
+
[
|
|
52607
|
+
{ text: "\u2705 Approve", callback_data: `${request.id}:approve` },
|
|
52608
|
+
{ text: "\u274C Reject", callback_data: `${request.id}:reject` }
|
|
52609
|
+
],
|
|
52610
|
+
[
|
|
52611
|
+
{ text: "\u23ED Skip", callback_data: `${request.id}:skip` },
|
|
52612
|
+
{ text: "\uD83D\uDED1 Abort", callback_data: `${request.id}:abort` }
|
|
52613
|
+
]
|
|
52614
|
+
];
|
|
52615
|
+
default:
|
|
52616
|
+
return null;
|
|
52617
|
+
}
|
|
52618
|
+
}
|
|
52619
|
+
function getStageEmoji(stage) {
|
|
52620
|
+
switch (stage) {
|
|
52621
|
+
case "pre-flight":
|
|
52622
|
+
return "\uD83D\uDE80";
|
|
52623
|
+
case "execution":
|
|
52624
|
+
return "\u2699\uFE0F";
|
|
52625
|
+
case "review":
|
|
52626
|
+
return "\uD83D\uDD0D";
|
|
52627
|
+
case "merge":
|
|
52628
|
+
return "\uD83D\uDD00";
|
|
52629
|
+
case "cost":
|
|
52630
|
+
return "\uD83D\uDCB0";
|
|
52631
|
+
default:
|
|
52632
|
+
return "\uD83D\uDCCC";
|
|
52633
|
+
}
|
|
52634
|
+
}
|
|
52635
|
+
var MAX_MESSAGE_CHARS = 4000;
|
|
52636
|
+
|
|
52252
52637
|
// src/interaction/plugins/telegram.ts
|
|
52253
|
-
|
|
52638
|
+
function normalizeChatId(raw) {
|
|
52639
|
+
const chatId = raw.trim();
|
|
52640
|
+
return { chatId, unmatchable: !NUMERIC_CHAT_ID.test(chatId) };
|
|
52641
|
+
}
|
|
52642
|
+
var _telegramPluginDeps, CALLBACK_API_TIMEOUT_MS = 4000, NUMERIC_CHAT_ID, TelegramConfigSchema, TelegramInteractionPlugin;
|
|
52254
52643
|
var init_telegram = __esm(() => {
|
|
52255
52644
|
init_zod();
|
|
52256
52645
|
init_logger2();
|
|
52646
|
+
_telegramPluginDeps = {
|
|
52647
|
+
fetch: globalThis.fetch.bind(globalThis)
|
|
52648
|
+
};
|
|
52649
|
+
NUMERIC_CHAT_ID = /^-?\d+$/;
|
|
52257
52650
|
TelegramConfigSchema = exports_external.object({
|
|
52258
52651
|
botToken: exports_external.string().optional(),
|
|
52259
52652
|
chatId: exports_external.string().optional()
|
|
@@ -52267,6 +52660,7 @@ var init_telegram = __esm(() => {
|
|
|
52267
52660
|
lastUpdateId = 0;
|
|
52268
52661
|
backoffMs = 1000;
|
|
52269
52662
|
maxBackoffMs = 30000;
|
|
52663
|
+
static MAX_DRAIN_PAGES = 10;
|
|
52270
52664
|
static INTERACTIVE_REQUEST_TYPES = new Set([
|
|
52271
52665
|
"confirm",
|
|
52272
52666
|
"choose",
|
|
@@ -52276,10 +52670,29 @@ var init_telegram = __esm(() => {
|
|
|
52276
52670
|
async init(config2) {
|
|
52277
52671
|
const cfg = TelegramConfigSchema.parse(config2);
|
|
52278
52672
|
this.botToken = cfg.botToken ?? process.env.NAX_TELEGRAM_TOKEN ?? process.env.TELEGRAM_BOT_TOKEN ?? null;
|
|
52279
|
-
|
|
52673
|
+
const rawChatId = cfg.chatId ?? process.env.NAX_TELEGRAM_CHAT_ID ?? null;
|
|
52674
|
+
const normalized = rawChatId === null ? null : normalizeChatId(rawChatId);
|
|
52675
|
+
this.chatId = normalized?.chatId || null;
|
|
52280
52676
|
if (!this.botToken || !this.chatId) {
|
|
52281
52677
|
throw new Error("Telegram plugin requires botToken and chatId (env: NAX_TELEGRAM_TOKEN or TELEGRAM_BOT_TOKEN, NAX_TELEGRAM_CHAT_ID)");
|
|
52282
52678
|
}
|
|
52679
|
+
if (normalized?.unmatchable) {
|
|
52680
|
+
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 });
|
|
52681
|
+
}
|
|
52682
|
+
}
|
|
52683
|
+
async drainBacklog() {
|
|
52684
|
+
for (let page = 0;page < TelegramInteractionPlugin.MAX_DRAIN_PAGES; page++) {
|
|
52685
|
+
const result = await this.fetchUpdates();
|
|
52686
|
+
if (!result.ok) {
|
|
52687
|
+
this.logger?.warn("interaction", "Telegram backlog drain failed \u2014 stale updates may be misread as a response");
|
|
52688
|
+
return;
|
|
52689
|
+
}
|
|
52690
|
+
if (result.rawCount === 0)
|
|
52691
|
+
return;
|
|
52692
|
+
}
|
|
52693
|
+
this.logger?.warn("interaction", "Telegram backlog drain hit page cap \u2014 stale updates may remain", {
|
|
52694
|
+
pages: TelegramInteractionPlugin.MAX_DRAIN_PAGES
|
|
52695
|
+
});
|
|
52283
52696
|
}
|
|
52284
52697
|
async destroy() {
|
|
52285
52698
|
this.pendingMessages.clear();
|
|
@@ -52288,10 +52701,13 @@ var init_telegram = __esm(() => {
|
|
|
52288
52701
|
if (!this.botToken || !this.chatId) {
|
|
52289
52702
|
throw new Error("Telegram plugin not initialized");
|
|
52290
52703
|
}
|
|
52291
|
-
|
|
52292
|
-
|
|
52293
|
-
|
|
52294
|
-
const
|
|
52704
|
+
if (TelegramInteractionPlugin.INTERACTIVE_REQUEST_TYPES.has(request.type)) {
|
|
52705
|
+
await this.drainBacklog();
|
|
52706
|
+
}
|
|
52707
|
+
const header = buildHeader(request);
|
|
52708
|
+
const keyboard = buildKeyboard(request);
|
|
52709
|
+
const body = buildBody(request);
|
|
52710
|
+
const chunks = splitText(body, MAX_MESSAGE_CHARS - header.length - 10);
|
|
52295
52711
|
try {
|
|
52296
52712
|
const sentIds = [];
|
|
52297
52713
|
for (let i = 0;i < chunks.length; i++) {
|
|
@@ -52299,7 +52715,7 @@ var init_telegram = __esm(() => {
|
|
|
52299
52715
|
const partLabel = chunks.length > 1 ? `[${i + 1}/${chunks.length}] ` : "";
|
|
52300
52716
|
const text = `${header}
|
|
52301
52717
|
${partLabel}${chunks[i]}`;
|
|
52302
|
-
const response = await fetch(`https://api.telegram.org/bot${this.botToken}/sendMessage`, {
|
|
52718
|
+
const response = await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/sendMessage`, {
|
|
52303
52719
|
method: "POST",
|
|
52304
52720
|
headers: { "Content-Type": "application/json" },
|
|
52305
52721
|
body: JSON.stringify({
|
|
@@ -52320,7 +52736,7 @@ ${partLabel}${chunks[i]}`;
|
|
|
52320
52736
|
sentIds.push(data.result.message_id);
|
|
52321
52737
|
}
|
|
52322
52738
|
if (TelegramInteractionPlugin.INTERACTIVE_REQUEST_TYPES.has(request.type)) {
|
|
52323
|
-
this.pendingMessages.set(request.id, sentIds);
|
|
52739
|
+
this.pendingMessages.set(request.id, { type: request.type, ids: sentIds });
|
|
52324
52740
|
}
|
|
52325
52741
|
} catch (err) {
|
|
52326
52742
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -52380,140 +52796,25 @@ ${partLabel}${chunks[i]}`;
|
|
|
52380
52796
|
await this.sendTimeoutMessage(requestId);
|
|
52381
52797
|
this.pendingMessages.delete(requestId);
|
|
52382
52798
|
}
|
|
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
52799
|
async getUpdates() {
|
|
52800
|
+
const result = await this.fetchUpdates();
|
|
52801
|
+
return result.updates;
|
|
52802
|
+
}
|
|
52803
|
+
async fetchUpdates() {
|
|
52504
52804
|
if (!this.botToken)
|
|
52505
|
-
return [];
|
|
52805
|
+
return { ok: true, updates: [], rawCount: 0 };
|
|
52506
52806
|
try {
|
|
52507
52807
|
const controller = new AbortController;
|
|
52508
52808
|
const timer = setTimeout(() => controller.abort(), 8000);
|
|
52509
52809
|
let response;
|
|
52510
52810
|
try {
|
|
52511
|
-
response = await fetch(`https://api.telegram.org/bot${this.botToken}/getUpdates`, {
|
|
52811
|
+
response = await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/getUpdates`, {
|
|
52512
52812
|
method: "POST",
|
|
52513
52813
|
headers: { "Content-Type": "application/json" },
|
|
52514
52814
|
body: JSON.stringify({
|
|
52515
52815
|
offset: this.lastUpdateId + 1,
|
|
52516
|
-
timeout: 1
|
|
52816
|
+
timeout: 1,
|
|
52817
|
+
limit: 100
|
|
52517
52818
|
}),
|
|
52518
52819
|
signal: controller.signal
|
|
52519
52820
|
});
|
|
@@ -52528,17 +52829,31 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52528
52829
|
if (!data.ok || !data.result) {
|
|
52529
52830
|
throw new Error("Telegram API returned ok=false or missing result");
|
|
52530
52831
|
}
|
|
52531
|
-
const
|
|
52532
|
-
if (
|
|
52533
|
-
this.lastUpdateId = Math.max(...
|
|
52832
|
+
const raw = data.result;
|
|
52833
|
+
if (raw.length > 0) {
|
|
52834
|
+
this.lastUpdateId = Math.max(...raw.map((u) => u.update_id));
|
|
52835
|
+
}
|
|
52836
|
+
const updates = raw.filter((u) => this.isFromConfiguredChat(u));
|
|
52837
|
+
if (updates.length !== raw.length) {
|
|
52838
|
+
this.logger?.debug("interaction", "Telegram updates rejected -- not from the configured chat", {
|
|
52839
|
+
rejected: raw.length - updates.length
|
|
52840
|
+
});
|
|
52534
52841
|
}
|
|
52535
52842
|
this.backoffMs = 1000;
|
|
52536
|
-
return updates;
|
|
52843
|
+
return { ok: true, updates, rawCount: raw.length };
|
|
52537
52844
|
} catch (err) {
|
|
52538
52845
|
this.backoffMs = Math.min(this.backoffMs * 2, this.maxBackoffMs);
|
|
52539
|
-
|
|
52846
|
+
this.logger?.debug("interaction", "Telegram getUpdates failed \u2014 retrying with backoff", {
|
|
52847
|
+
error: err instanceof Error ? err.message : String(err),
|
|
52848
|
+
backoffMs: this.backoffMs
|
|
52849
|
+
});
|
|
52850
|
+
return { ok: false, updates: [], rawCount: 0 };
|
|
52540
52851
|
}
|
|
52541
52852
|
}
|
|
52853
|
+
isFromConfiguredChat(update) {
|
|
52854
|
+
const chatId = update.callback_query?.message?.chat?.id ?? update.message?.chat?.id;
|
|
52855
|
+
return chatId !== undefined && String(chatId) === this.chatId;
|
|
52856
|
+
}
|
|
52542
52857
|
parseUpdate(requestId, update) {
|
|
52543
52858
|
if (update.callback_query) {
|
|
52544
52859
|
const data = update.callback_query.data;
|
|
@@ -52558,11 +52873,11 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52558
52873
|
};
|
|
52559
52874
|
}
|
|
52560
52875
|
if (update.message?.text) {
|
|
52561
|
-
const
|
|
52562
|
-
if (!
|
|
52876
|
+
const pending = this.pendingMessages.get(requestId);
|
|
52877
|
+
if (!pending || pending.type !== "input")
|
|
52563
52878
|
return null;
|
|
52564
52879
|
const replyToId = update.message.reply_to_message?.message_id;
|
|
52565
|
-
if (replyToId !== undefined && !
|
|
52880
|
+
if (replyToId !== undefined && !pending.ids.includes(replyToId))
|
|
52566
52881
|
return null;
|
|
52567
52882
|
return {
|
|
52568
52883
|
requestId,
|
|
@@ -52581,7 +52896,7 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52581
52896
|
const controller = new AbortController;
|
|
52582
52897
|
const timer = setTimeout(() => controller.abort(), CALLBACK_API_TIMEOUT_MS);
|
|
52583
52898
|
try {
|
|
52584
|
-
await fetch(`https://api.telegram.org/bot${this.botToken}/answerCallbackQuery`, {
|
|
52899
|
+
await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/answerCallbackQuery`, {
|
|
52585
52900
|
method: "POST",
|
|
52586
52901
|
headers: { "Content-Type": "application/json" },
|
|
52587
52902
|
body: JSON.stringify({
|
|
@@ -52601,7 +52916,7 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52601
52916
|
const controller = new AbortController;
|
|
52602
52917
|
const timer = setTimeout(() => controller.abort(), CALLBACK_API_TIMEOUT_MS);
|
|
52603
52918
|
try {
|
|
52604
|
-
await fetch(`https://api.telegram.org/bot${this.botToken}/editMessageReplyMarkup`, {
|
|
52919
|
+
await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/editMessageReplyMarkup`, {
|
|
52605
52920
|
method: "POST",
|
|
52606
52921
|
headers: { "Content-Type": "application/json" },
|
|
52607
52922
|
body: JSON.stringify({
|
|
@@ -52617,14 +52932,14 @@ ${this.sanitizeMarkdown(request.detail)}
|
|
|
52617
52932
|
} catch {}
|
|
52618
52933
|
}
|
|
52619
52934
|
async sendTimeoutMessage(requestId) {
|
|
52620
|
-
const
|
|
52621
|
-
if (!
|
|
52935
|
+
const pending = this.pendingMessages.get(requestId);
|
|
52936
|
+
if (!pending || !this.botToken || !this.chatId) {
|
|
52622
52937
|
this.pendingMessages.delete(requestId);
|
|
52623
52938
|
return;
|
|
52624
52939
|
}
|
|
52625
|
-
const lastId =
|
|
52940
|
+
const lastId = pending.ids[pending.ids.length - 1];
|
|
52626
52941
|
try {
|
|
52627
|
-
await fetch(`https://api.telegram.org/bot${this.botToken}/editMessageText`, {
|
|
52942
|
+
await _telegramPluginDeps.fetch(`https://api.telegram.org/bot${this.botToken}/editMessageText`, {
|
|
52628
52943
|
method: "POST",
|
|
52629
52944
|
headers: { "Content-Type": "application/json" },
|
|
52630
52945
|
body: JSON.stringify({
|
|
@@ -58708,7 +59023,8 @@ async function refreshReviewInputForDispatch(opName, input) {
|
|
|
58708
59023
|
stat: fresh2.stat,
|
|
58709
59024
|
diff: fresh2.diff,
|
|
58710
59025
|
excludePatterns: fresh2.excludePatterns,
|
|
58711
|
-
storyGitRef: fresh2.effectiveRef ?? semInput.storyGitRef
|
|
59026
|
+
storyGitRef: fresh2.effectiveRef ?? semInput.storyGitRef,
|
|
59027
|
+
resolvedTestPatterns: _refresh.resolvedTestPatterns
|
|
58712
59028
|
};
|
|
58713
59029
|
}
|
|
58714
59030
|
const { _refresh: __, ...advInput } = input;
|
|
@@ -59755,6 +60071,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
59755
60071
|
excludePatterns: prepared.excludePatterns,
|
|
59756
60072
|
featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-semantic"),
|
|
59757
60073
|
priorSemanticIterations: ctx.priorSemanticIterations,
|
|
60074
|
+
resolvedTestPatterns,
|
|
59758
60075
|
blockingThreshold: ctx.config.review.blockingThreshold,
|
|
59759
60076
|
_refresh: {
|
|
59760
60077
|
projectDir: ctx.projectDir,
|
|
@@ -59792,6 +60109,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
59792
60109
|
refExcludePatterns: prepared.refExcludePatterns,
|
|
59793
60110
|
featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-adversarial"),
|
|
59794
60111
|
priorAdversarialIterations: ctx.priorAdversarialIterations,
|
|
60112
|
+
resolvedTestPatterns,
|
|
59795
60113
|
blockingThreshold: ctx.config.review.blockingThreshold,
|
|
59796
60114
|
_refresh: {
|
|
59797
60115
|
projectDir: ctx.projectDir,
|
|
@@ -62784,7 +63102,7 @@ var init_forge = __esm(() => {
|
|
|
62784
63102
|
function buildTitle(ctx) {
|
|
62785
63103
|
return `feat: ${ctx.feature}`;
|
|
62786
63104
|
}
|
|
62787
|
-
function
|
|
63105
|
+
function formatDuration3(totalMs) {
|
|
62788
63106
|
const clampedMs = Math.max(0, Math.round(totalMs));
|
|
62789
63107
|
const totalSeconds = Math.floor(clampedMs / MS_PER_SECOND);
|
|
62790
63108
|
const minutes = Math.floor(totalSeconds / SECONDS_PER_MINUTE);
|
|
@@ -62800,7 +63118,7 @@ function buildSummaryLines(ctx) {
|
|
|
62800
63118
|
"## Run summary",
|
|
62801
63119
|
`- Feature: ${ctx.feature}`,
|
|
62802
63120
|
`- Stories: ${passed} / ${failed} / ${skipped}`,
|
|
62803
|
-
`- Duration: ${
|
|
63121
|
+
`- Duration: ${formatDuration3(ctx.totalDurationMs)}`,
|
|
62804
63122
|
`- PRD: ${ctx.prdPath}`,
|
|
62805
63123
|
""
|
|
62806
63124
|
];
|
|
@@ -62821,7 +63139,7 @@ function buildStoryTable(stories) {
|
|
|
62821
63139
|
lines.push("");
|
|
62822
63140
|
return lines;
|
|
62823
63141
|
}
|
|
62824
|
-
function
|
|
63142
|
+
function buildBody2(ctx, template) {
|
|
62825
63143
|
const blocks = [];
|
|
62826
63144
|
blocks.push("> Auto-opened by nax \u2014 review pending. Run nax-finish before merge.");
|
|
62827
63145
|
blocks.push("");
|
|
@@ -62996,7 +63314,7 @@ var init_auto_pr = __esm(() => {
|
|
|
62996
63314
|
});
|
|
62997
63315
|
const prCtx = toPrBodyContext(context);
|
|
62998
63316
|
const title = buildTitle(prCtx);
|
|
62999
|
-
const body =
|
|
63317
|
+
const body = buildBody2(prCtx, template);
|
|
63000
63318
|
return await _autoPrDeps.openDraft(forge, { title, body, branch: context.branch, draft: cfg.draft }, { run: _autoPrDeps.run, readText: _autoPrDeps.readText }, context.workdir);
|
|
63001
63319
|
} catch (err) {
|
|
63002
63320
|
context.logger.warn("Auto-PR execute failed", { error: String(err) });
|
|
@@ -68780,7 +69098,7 @@ function extractQuoteTriples(reason) {
|
|
|
68780
69098
|
}
|
|
68781
69099
|
return triples;
|
|
68782
69100
|
}
|
|
68783
|
-
function
|
|
69101
|
+
function normalizeWs2(s) {
|
|
68784
69102
|
return s.replace(/\s+/g, " ").trim();
|
|
68785
69103
|
}
|
|
68786
69104
|
async function verifyQuoteTriple(triple, workdir, deps = _quoteIntegrityDeps) {
|
|
@@ -68794,7 +69112,7 @@ async function verifyQuoteTriple(triple, workdir, deps = _quoteIntegrityDeps) {
|
|
|
68794
69112
|
const end = Math.min(lines.length, triple.line + CONTEXT_LINES);
|
|
68795
69113
|
const window2 = lines.slice(start, end).join(`
|
|
68796
69114
|
`);
|
|
68797
|
-
return
|
|
69115
|
+
return normalizeWs2(window2).toLowerCase().includes(normalizeWs2(triple.quote).toLowerCase());
|
|
68798
69116
|
}
|
|
68799
69117
|
async function verifyEscalationQuotes(reason, workdir, storyId, deps = _quoteIntegrityDeps) {
|
|
68800
69118
|
const triples = extractQuoteTriples(reason);
|
|
@@ -69792,6 +70110,12 @@ async function executeParallelBatch(stories, _projectRoot, config2, context, wor
|
|
|
69792
70110
|
error: result.error
|
|
69793
70111
|
});
|
|
69794
70112
|
}
|
|
70113
|
+
}).catch((error48) => {
|
|
70114
|
+
results.failed.push({ story, error: errorMessage(error48) });
|
|
70115
|
+
logger?.error("parallel", "Story execution threw", {
|
|
70116
|
+
storyId: story.id,
|
|
70117
|
+
error: errorMessage(error48)
|
|
70118
|
+
});
|
|
69795
70119
|
}).finally(() => {
|
|
69796
70120
|
executing.delete(executePromise);
|
|
69797
70121
|
});
|
|
@@ -69800,7 +70124,7 @@ async function executeParallelBatch(stories, _projectRoot, config2, context, wor
|
|
|
69800
70124
|
await Promise.race(executing);
|
|
69801
70125
|
}
|
|
69802
70126
|
}
|
|
69803
|
-
await Promise.
|
|
70127
|
+
await Promise.allSettled(executing);
|
|
69804
70128
|
return results;
|
|
69805
70129
|
}
|
|
69806
70130
|
var _parallelWorkerDeps;
|
|
@@ -102438,7 +102762,6 @@ class DebatePlanStrategy {
|
|
|
102438
102762
|
});
|
|
102439
102763
|
if (debateResult.outcome !== "failed" && debateResult.output) {
|
|
102440
102764
|
const prd2 = validatePlanOutput(debateResult.output, ctx.options.feature, ctx.branchName);
|
|
102441
|
-
warnOnDroppedVerbatimAcs(prd2, ctx.specContent, ctx.options.feature);
|
|
102442
102765
|
const scoped2 = backfillOutOfScope(prd2, ctx.specContent, ctx.options.feature);
|
|
102443
102766
|
const withProject2 = { ...scoped2, project: ctx.projectName };
|
|
102444
102767
|
return _debatePlanDeps.writeOrRecoverPrd(ctx, withProject2);
|
|
@@ -103247,9 +103570,6 @@ var FIELD_DESCRIPTIONS = {
|
|
|
103247
103570
|
"execution.regressionGate.timeoutSeconds": "Timeout for regression run in seconds",
|
|
103248
103571
|
"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
103572
|
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
103573
|
"quality.commands": "Custom quality commands",
|
|
103254
103574
|
"quality.commands.typecheck": "Custom typecheck command",
|
|
103255
103575
|
"quality.commands.lint": "Custom lint command",
|
|
@@ -105478,7 +105798,7 @@ var DEFAULT_LIMIT = 20;
|
|
|
105478
105798
|
var _runsCmdDeps = {
|
|
105479
105799
|
getRunsDir
|
|
105480
105800
|
};
|
|
105481
|
-
function
|
|
105801
|
+
function formatDuration4(ms) {
|
|
105482
105802
|
if (ms <= 0)
|
|
105483
105803
|
return "-";
|
|
105484
105804
|
const minutes = Math.floor(ms / 60000);
|
|
@@ -105593,7 +105913,7 @@ async function runsCommand(options = {}) {
|
|
|
105593
105913
|
pad3(row.feature, COL.feature),
|
|
105594
105914
|
pad3(colored, COL.status + (colored.length - visibleLength(colored))),
|
|
105595
105915
|
pad3(`${row.passed}/${row.total}`, COL.stories),
|
|
105596
|
-
pad3(
|
|
105916
|
+
pad3(formatDuration4(row.durationMs), COL.duration),
|
|
105597
105917
|
formatDate(row.registeredAt)
|
|
105598
105918
|
].join(" ");
|
|
105599
105919
|
console.log(line);
|
|
@@ -112874,7 +113194,7 @@ function useAgentStreamEvents(bus) {
|
|
|
112874
113194
|
if (!bus)
|
|
112875
113195
|
return;
|
|
112876
113196
|
const unsubscribe = bus.onAgentStream((event) => {
|
|
112877
|
-
const next =
|
|
113197
|
+
const next = activeCallsRef.current;
|
|
112878
113198
|
switch (event.kind) {
|
|
112879
113199
|
case "agent.call_started": {
|
|
112880
113200
|
next.set(event.callId, {
|
|
@@ -112958,10 +113278,13 @@ function useAgentStreamEvents(bus) {
|
|
|
112958
113278
|
default:
|
|
112959
113279
|
break;
|
|
112960
113280
|
}
|
|
112961
|
-
activeCallsRef.current = next;
|
|
112962
113281
|
dirtyRef.current = true;
|
|
112963
113282
|
});
|
|
112964
|
-
return
|
|
113283
|
+
return () => {
|
|
113284
|
+
unsubscribe();
|
|
113285
|
+
activeCallsRef.current.clear();
|
|
113286
|
+
lastTokensRef.current.clear();
|
|
113287
|
+
};
|
|
112965
113288
|
}, [bus]);
|
|
112966
113289
|
import_react31.useEffect(() => {
|
|
112967
113290
|
const interval = setInterval(() => {
|