ralphctl 0.8.0 → 0.8.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/cli.mjs
CHANGED
|
@@ -457,14 +457,32 @@ var CodexFlowRowSchema = z.object({
|
|
|
457
457
|
effort: CodexEffortSchema.optional()
|
|
458
458
|
});
|
|
459
459
|
var FlowRowSchema = z.discriminatedUnion("provider", [ClaudeFlowRowSchema, CopilotFlowRowSchema, CodexFlowRowSchema]);
|
|
460
|
-
var
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
plan: FlowRowSchema,
|
|
464
|
-
implement: FlowRowSchema,
|
|
465
|
-
readiness: FlowRowSchema,
|
|
466
|
-
ideate: FlowRowSchema
|
|
460
|
+
var AiImplementSchema = z.object({
|
|
461
|
+
generator: FlowRowSchema,
|
|
462
|
+
evaluator: FlowRowSchema
|
|
467
463
|
});
|
|
464
|
+
var promoteLegacyImplementRow = (ai) => {
|
|
465
|
+
if (typeof ai !== "object" || ai === null) return ai;
|
|
466
|
+
const aiObj = ai;
|
|
467
|
+
const implement = aiObj["implement"];
|
|
468
|
+
if (typeof implement !== "object" || implement === null) return ai;
|
|
469
|
+
const implObj = implement;
|
|
470
|
+
if ("generator" in implObj || "evaluator" in implObj) return ai;
|
|
471
|
+
if (!("provider" in implObj)) return ai;
|
|
472
|
+
const promoted = { generator: implObj, evaluator: implObj };
|
|
473
|
+
return { ...aiObj, implement: promoted };
|
|
474
|
+
};
|
|
475
|
+
var AiSettingsSchema = z.preprocess(
|
|
476
|
+
promoteLegacyImplementRow,
|
|
477
|
+
z.object({
|
|
478
|
+
effort: GlobalEffortSchema.optional(),
|
|
479
|
+
refine: FlowRowSchema,
|
|
480
|
+
plan: FlowRowSchema,
|
|
481
|
+
implement: AiImplementSchema,
|
|
482
|
+
readiness: FlowRowSchema,
|
|
483
|
+
ideate: FlowRowSchema
|
|
484
|
+
})
|
|
485
|
+
);
|
|
468
486
|
var SettingsSchema = z.object({
|
|
469
487
|
/**
|
|
470
488
|
* On-disk format version. Omitted in the very first format (treated as v1 by the load path
|
|
@@ -487,7 +505,22 @@ var SettingsSchema = z.object({
|
|
|
487
505
|
* (score improvement / commit-message change / critique-prose shift) that can soften or
|
|
488
506
|
* skip the plateau even when the threshold is met.
|
|
489
507
|
*/
|
|
490
|
-
plateauThreshold: z.number().int().min(2).max(5).default(2)
|
|
508
|
+
plateauThreshold: z.number().int().min(2).max(5).default(2),
|
|
509
|
+
/**
|
|
510
|
+
* When the gen-eval loop exits on a plateau, escalate the generator's model one rung up
|
|
511
|
+
* the ladder defined by {@link escalationMap} (merged with the built-in
|
|
512
|
+
* `DEFAULT_ESCALATION_MAP`) and reissue the attempt instead of transitioning the task
|
|
513
|
+
* straight to `blocked`. Defaults `false` — the runtime wiring lands in a follow-up task.
|
|
514
|
+
*/
|
|
515
|
+
escalateOnPlateau: z.boolean().default(false),
|
|
516
|
+
/**
|
|
517
|
+
* User overrides for the built-in `DEFAULT_ESCALATION_MAP` (in
|
|
518
|
+
* `business/task/escalation-map.ts`). Keys are the current model id, values the model id
|
|
519
|
+
* to escalate to. Empty by default; merged at read time with user keys winning on
|
|
520
|
+
* conflict and extending the default ladder. Non-string entries fail schema validation
|
|
521
|
+
* with a typed Zod error naming the offending field.
|
|
522
|
+
*/
|
|
523
|
+
escalationMap: z.record(z.string(), z.string()).default({})
|
|
491
524
|
}),
|
|
492
525
|
logging: z.object({
|
|
493
526
|
level: LogLevelSchema
|
|
@@ -522,6 +555,10 @@ var SettingsSchema = z.object({
|
|
|
522
555
|
showEvaluatorFailureUI: z.boolean().default(false)
|
|
523
556
|
}).default({ showEvaluatorFailureUI: false })
|
|
524
557
|
});
|
|
558
|
+
var primaryFlowRow = (ai, flow) => {
|
|
559
|
+
if (flow === "implement") return ai.implement.generator;
|
|
560
|
+
return ai[flow];
|
|
561
|
+
};
|
|
525
562
|
|
|
526
563
|
// src/business/settings/defaults.ts
|
|
527
564
|
var DEFAULT_MODELS_BY_PROVIDER = {
|
|
@@ -549,18 +586,32 @@ var DEFAULT_MODELS_BY_PROVIDER = {
|
|
|
549
586
|
};
|
|
550
587
|
var defaultAiSettingsForProvider = (provider) => {
|
|
551
588
|
const models = DEFAULT_MODELS_BY_PROVIDER[provider];
|
|
589
|
+
const implementRow = { provider, model: models.implement };
|
|
552
590
|
return {
|
|
553
591
|
refine: { provider, model: models.refine },
|
|
554
592
|
plan: { provider, model: models.plan },
|
|
555
|
-
implement: {
|
|
593
|
+
implement: { generator: implementRow, evaluator: implementRow },
|
|
556
594
|
readiness: { provider, model: models.readiness },
|
|
557
595
|
ideate: { provider, model: models.ideate }
|
|
558
596
|
};
|
|
559
597
|
};
|
|
560
598
|
var DEFAULT_SETTINGS = {
|
|
561
599
|
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
562
|
-
ai:
|
|
563
|
-
|
|
600
|
+
ai: {
|
|
601
|
+
...defaultAiSettingsForProvider("claude-code"),
|
|
602
|
+
implement: {
|
|
603
|
+
generator: { provider: "claude-code", model: "claude-opus-4-7" },
|
|
604
|
+
evaluator: { provider: "openai-codex", model: "gpt-5.5" }
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
harness: {
|
|
608
|
+
maxTurns: 5,
|
|
609
|
+
maxAttempts: 3,
|
|
610
|
+
rateLimitRetries: 3,
|
|
611
|
+
plateauThreshold: 2,
|
|
612
|
+
escalateOnPlateau: false,
|
|
613
|
+
escalationMap: {}
|
|
614
|
+
},
|
|
564
615
|
logging: { level: "info" },
|
|
565
616
|
concurrency: { maxParallelTasks: 1 },
|
|
566
617
|
ui: { notifications: { enabled: true } },
|
|
@@ -793,9 +844,20 @@ var createShellScriptRunner = (deps = {}) => {
|
|
|
793
844
|
// `node_modules/` (lockfile / store mismatch). The same setting is also exposed
|
|
794
845
|
// as `.npmrc:confirm-modules-purge=false` and `--config.confirm-modules-purge=false`.
|
|
795
846
|
//
|
|
847
|
+
// NO_COLOR=1 — the well-defined cross-tool convention (https://no-color.org)
|
|
848
|
+
// suppresses ANSI colour codes in tool output. The harness persists script
|
|
849
|
+
// output verbatim to `<sprintDir>/logs/{setup,verify}/...` plain-text files;
|
|
850
|
+
// without this default the logs fill with `^[[1m^[[30m…` escape sequences that
|
|
851
|
+
// render as garbage in editors. Honoured by Node / Python / Rust / Go / Ruby
|
|
852
|
+
// and modern CLI tools; tools that don't recognise it ignore it harmlessly.
|
|
853
|
+
// Exception: JVM tools (Maven / Gradle / sbt) do NOT respect `NO_COLOR` — those
|
|
854
|
+
// are handled at script-authoring time by the `detect-scripts` prompt suggesting
|
|
855
|
+
// `mvn -B`, `gradle --console=plain`, `sbt -no-colors`.
|
|
856
|
+
//
|
|
796
857
|
// Add more entries here as we hit narrow per-tool prompts; do NOT reach for `CI=true`.
|
|
797
|
-
//
|
|
798
|
-
|
|
858
|
+
// Defaults sit BEFORE `...process.env` so a user who exports `NO_COLOR=` (empty) or
|
|
859
|
+
// `FORCE_COLOR=1` can override; caller-supplied `opts.env` wins last.
|
|
860
|
+
env: { npm_config_confirm_modules_purge: "false", NO_COLOR: "1", ...process.env, ...opts.env }
|
|
799
861
|
});
|
|
800
862
|
} catch (cause) {
|
|
801
863
|
resolve(
|
|
@@ -1411,7 +1473,14 @@ var SprintExecutionSchema = z7.object({
|
|
|
1411
1473
|
sprintId: SprintIdSchema,
|
|
1412
1474
|
branch: z7.union([z7.string(), z7.null()]),
|
|
1413
1475
|
pullRequestUrl: z7.union([HttpUrlSchema, z7.null()]),
|
|
1414
|
-
setupRanAt: z7.array(SetupRunSchema).readonly()
|
|
1476
|
+
setupRanAt: z7.array(SetupRunSchema).readonly(),
|
|
1477
|
+
/**
|
|
1478
|
+
* Optional. Stamped `'proceed'` by `pre-task-verify` when the operator opts to continue on a
|
|
1479
|
+
* red baseline; cleared back to undefined on the next green pre-verify. Files written by
|
|
1480
|
+
* ralphctl ≤ 0.7.0 simply lack the field — Zod accepts the absence as `undefined`, no
|
|
1481
|
+
* `schemaVersion` bump or migration step needed.
|
|
1482
|
+
*/
|
|
1483
|
+
baselineBrokenPolicy: z7.literal("proceed").optional()
|
|
1415
1484
|
});
|
|
1416
1485
|
var fromJsonSprintExecution = (input, filePath = "execution.json") => runMigrations(
|
|
1417
1486
|
input,
|
|
@@ -1829,7 +1898,9 @@ var TaskBaseShape = {
|
|
|
1829
1898
|
attempts: z13.array(AttemptSchema).readonly(),
|
|
1830
1899
|
maxAttempts: z13.number().optional(),
|
|
1831
1900
|
extraDimensions: z13.array(z13.string()).readonly().optional(),
|
|
1832
|
-
externalRefs: z13.array(z13.string()).readonly().optional()
|
|
1901
|
+
externalRefs: z13.array(z13.string()).readonly().optional(),
|
|
1902
|
+
escalatedFromModel: z13.string().optional(),
|
|
1903
|
+
escalatedToModel: z13.string().optional()
|
|
1833
1904
|
};
|
|
1834
1905
|
var TodoTaskSchema = z13.object({ ...TaskBaseShape, status: z13.literal("todo") });
|
|
1835
1906
|
var InProgressTaskSchema = z13.object({ ...TaskBaseShape, status: z13.literal("in_progress") });
|
|
@@ -2398,6 +2469,83 @@ var contextWindowFor = (model) => {
|
|
|
2398
2469
|
return CONTEXT_WINDOW[model];
|
|
2399
2470
|
};
|
|
2400
2471
|
|
|
2472
|
+
// src/domain/value/error/abort-error.ts
|
|
2473
|
+
var AbortError = class extends Error {
|
|
2474
|
+
code = ErrorCode.Aborted;
|
|
2475
|
+
elementName;
|
|
2476
|
+
reason;
|
|
2477
|
+
constructor(opts) {
|
|
2478
|
+
super(opts.reason ?? `operation aborted at step '${opts.elementName}'`);
|
|
2479
|
+
this.name = "AbortError";
|
|
2480
|
+
this.elementName = opts.elementName;
|
|
2481
|
+
if (opts.reason !== void 0) {
|
|
2482
|
+
this.reason = opts.reason;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
};
|
|
2486
|
+
|
|
2487
|
+
// src/integration/ai/providers/_engine/classify-spawn-exit.ts
|
|
2488
|
+
var classifySpawnExit = async (input) => {
|
|
2489
|
+
const { session, exit, stderr, rateLimitRe, capturedSessionId, providerName, eventBus, watchdogBannerId, onSuccess } = input;
|
|
2490
|
+
if (session.abortSignal?.aborted === true) {
|
|
2491
|
+
return {
|
|
2492
|
+
kind: "error",
|
|
2493
|
+
error: new AbortError({
|
|
2494
|
+
elementName: providerName,
|
|
2495
|
+
reason: `${providerName}: aborted by caller`
|
|
2496
|
+
})
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
if (exit.code === 0) {
|
|
2500
|
+
return await onSuccess();
|
|
2501
|
+
}
|
|
2502
|
+
if (rateLimitRe.test(stderr)) {
|
|
2503
|
+
return {
|
|
2504
|
+
kind: "rate-limit",
|
|
2505
|
+
error: new RateLimitError({
|
|
2506
|
+
subCode: "spawn-stderr",
|
|
2507
|
+
message: `${providerName}: rate-limit detected in stderr (exit ${String(exit.code)})`,
|
|
2508
|
+
...capturedSessionId !== void 0 ? { sessionId: capturedSessionId } : {}
|
|
2509
|
+
})
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
const exists = await pathExists(String(session.signalsFile));
|
|
2513
|
+
if (exists.ok && exists.value) {
|
|
2514
|
+
eventBus.publish({
|
|
2515
|
+
type: "log",
|
|
2516
|
+
level: "warn",
|
|
2517
|
+
message: `${providerName}: non-zero exit (code=${String(exit.code)}, signal=${String(exit.signal ?? "null")}) but signals.json captured \u2014 preserving work`,
|
|
2518
|
+
meta: { code: exit.code, signal: exit.signal, providerName },
|
|
2519
|
+
at: IsoTimestamp.now()
|
|
2520
|
+
});
|
|
2521
|
+
eventBus.publish({
|
|
2522
|
+
type: "banner-clear",
|
|
2523
|
+
id: watchdogBannerId,
|
|
2524
|
+
at: IsoTimestamp.now()
|
|
2525
|
+
});
|
|
2526
|
+
const outcome = await onSuccess();
|
|
2527
|
+
if (outcome.kind === "success") {
|
|
2528
|
+
return {
|
|
2529
|
+
kind: "success",
|
|
2530
|
+
output: {
|
|
2531
|
+
...outcome.output,
|
|
2532
|
+
recoveredFromExit: { code: exit.code, signal: exit.signal }
|
|
2533
|
+
}
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
return outcome;
|
|
2537
|
+
}
|
|
2538
|
+
return {
|
|
2539
|
+
kind: "error",
|
|
2540
|
+
error: new InvalidStateError({
|
|
2541
|
+
entity: providerName,
|
|
2542
|
+
currentState: `exit-${String(exit.code ?? "null")}`,
|
|
2543
|
+
attemptedAction: "complete generation",
|
|
2544
|
+
message: `${providerName}: process exited with code ${String(exit.code)}${exit.signal !== null ? ` (signal=${exit.signal})` : ""}: ${stderr.trim() || "<empty stderr>"}`
|
|
2545
|
+
})
|
|
2546
|
+
};
|
|
2547
|
+
};
|
|
2548
|
+
|
|
2401
2549
|
// src/integration/ai/providers/claude/headless.ts
|
|
2402
2550
|
var RATE_LIMIT_RE = /rate.?limit/i;
|
|
2403
2551
|
var TOOL_EDIT = ["Edit", "MultiEdit", "NotebookEdit"];
|
|
@@ -2517,6 +2665,7 @@ var spawnAttempt = async (input) => {
|
|
|
2517
2665
|
const onLine = (line) => {
|
|
2518
2666
|
parser.ingest(line);
|
|
2519
2667
|
};
|
|
2668
|
+
const watchdogBannerId = `watchdog-claude-${String(child.pid ?? "unknown")}`;
|
|
2520
2669
|
const { code, signal } = await runHeadlessSpawn({
|
|
2521
2670
|
child,
|
|
2522
2671
|
onStdout: (chunk) => parser.feed(chunk, onLine),
|
|
@@ -2538,7 +2687,7 @@ var spawnAttempt = async (input) => {
|
|
|
2538
2687
|
});
|
|
2539
2688
|
deps.eventBus.publish({
|
|
2540
2689
|
type: "banner-show",
|
|
2541
|
-
id:
|
|
2690
|
+
id: watchdogBannerId,
|
|
2542
2691
|
tier: "warn",
|
|
2543
2692
|
message: `Watchdog killed stuck claude process${idleMs !== void 0 ? ` (${String(Math.round(idleMs / 1e3))}s idle)` : ""}`,
|
|
2544
2693
|
at: IsoTimestamp.now()
|
|
@@ -2546,19 +2695,8 @@ var spawnAttempt = async (input) => {
|
|
|
2546
2695
|
}
|
|
2547
2696
|
});
|
|
2548
2697
|
parser.flush(onLine);
|
|
2549
|
-
if (signal === "SIGTERM") {
|
|
2550
|
-
return {
|
|
2551
|
-
kind: "error",
|
|
2552
|
-
error: new InvalidStateError({
|
|
2553
|
-
entity: "claude-provider",
|
|
2554
|
-
currentState: "terminated",
|
|
2555
|
-
attemptedAction: "complete generation",
|
|
2556
|
-
message: "claude-provider: process terminated via SIGTERM"
|
|
2557
|
-
})
|
|
2558
|
-
};
|
|
2559
|
-
}
|
|
2560
2698
|
const envelope = parser.snapshot();
|
|
2561
|
-
|
|
2699
|
+
const onSuccess = async () => {
|
|
2562
2700
|
if (envelope.sessionId !== void 0) {
|
|
2563
2701
|
deps.eventBus.publish({
|
|
2564
2702
|
type: "log",
|
|
@@ -2578,6 +2716,7 @@ var spawnAttempt = async (input) => {
|
|
|
2578
2716
|
...envelope.usage.cacheReadTokens !== void 0 ? { cacheReadTokens: envelope.usage.cacheReadTokens } : {},
|
|
2579
2717
|
...envelope.usage.cacheCreationTokens !== void 0 ? { cacheCreationTokens: envelope.usage.cacheCreationTokens } : {},
|
|
2580
2718
|
...window !== void 0 ? { contextWindow: window } : {},
|
|
2719
|
+
...session.role !== void 0 ? { role: session.role } : {},
|
|
2581
2720
|
at: IsoTimestamp.now()
|
|
2582
2721
|
});
|
|
2583
2722
|
}
|
|
@@ -2607,30 +2746,22 @@ var spawnAttempt = async (input) => {
|
|
|
2607
2746
|
kind: "success",
|
|
2608
2747
|
output: {
|
|
2609
2748
|
signalsFile: session.signalsFile,
|
|
2610
|
-
exitCode: code,
|
|
2749
|
+
exitCode: code ?? 0,
|
|
2611
2750
|
...envelope.sessionId !== void 0 ? { sessionId: envelope.sessionId } : {}
|
|
2612
2751
|
}
|
|
2613
2752
|
};
|
|
2614
|
-
}
|
|
2615
|
-
if (RATE_LIMIT_RE.test(stderrBuf)) {
|
|
2616
|
-
return {
|
|
2617
|
-
kind: "rate-limit",
|
|
2618
|
-
error: new RateLimitError({
|
|
2619
|
-
subCode: "spawn-stderr",
|
|
2620
|
-
message: `claude-provider: rate-limit detected in stderr (exit ${String(code)})`,
|
|
2621
|
-
...envelope.sessionId !== void 0 ? { sessionId: envelope.sessionId } : {}
|
|
2622
|
-
})
|
|
2623
|
-
};
|
|
2624
|
-
}
|
|
2625
|
-
return {
|
|
2626
|
-
kind: "error",
|
|
2627
|
-
error: new InvalidStateError({
|
|
2628
|
-
entity: "claude-provider",
|
|
2629
|
-
currentState: `exit-${String(code)}`,
|
|
2630
|
-
attemptedAction: "complete generation",
|
|
2631
|
-
message: `claude-provider: process exited with code ${String(code)}: ${stderrBuf.trim() || "<empty stderr>"}`
|
|
2632
|
-
})
|
|
2633
2753
|
};
|
|
2754
|
+
return classifySpawnExit({
|
|
2755
|
+
session,
|
|
2756
|
+
exit: { code, signal },
|
|
2757
|
+
stderr: stderrBuf,
|
|
2758
|
+
rateLimitRe: RATE_LIMIT_RE,
|
|
2759
|
+
...envelope.sessionId !== void 0 ? { capturedSessionId: envelope.sessionId } : {},
|
|
2760
|
+
providerName: "claude-provider",
|
|
2761
|
+
eventBus: deps.eventBus,
|
|
2762
|
+
watchdogBannerId,
|
|
2763
|
+
onSuccess
|
|
2764
|
+
});
|
|
2634
2765
|
};
|
|
2635
2766
|
var defaultSpawn3 = (command, args, options) => nodeSpawn3(command, [...args], {
|
|
2636
2767
|
stdio: [...options.stdio],
|
|
@@ -2811,6 +2942,7 @@ var spawnAttempt2 = async (input) => {
|
|
|
2811
2942
|
let stderrBuf = "";
|
|
2812
2943
|
let sessionId2;
|
|
2813
2944
|
let stdoutLineBuf = "";
|
|
2945
|
+
const watchdogBannerId = `watchdog-codex-${String(child.pid ?? "unknown")}`;
|
|
2814
2946
|
let model;
|
|
2815
2947
|
let inputTokens;
|
|
2816
2948
|
let outputTokens;
|
|
@@ -2853,25 +2985,14 @@ var spawnAttempt2 = async (input) => {
|
|
|
2853
2985
|
});
|
|
2854
2986
|
deps.eventBus.publish({
|
|
2855
2987
|
type: "banner-show",
|
|
2856
|
-
id:
|
|
2988
|
+
id: watchdogBannerId,
|
|
2857
2989
|
tier: "warn",
|
|
2858
2990
|
message: `Watchdog killed stuck codex process${idleMs !== void 0 ? ` (${String(Math.round(idleMs / 1e3))}s idle)` : ""}`,
|
|
2859
2991
|
at: IsoTimestamp.now()
|
|
2860
2992
|
});
|
|
2861
2993
|
}
|
|
2862
2994
|
});
|
|
2863
|
-
|
|
2864
|
-
return {
|
|
2865
|
-
kind: "error",
|
|
2866
|
-
error: new InvalidStateError({
|
|
2867
|
-
entity: "codex-provider",
|
|
2868
|
-
currentState: "terminated",
|
|
2869
|
-
attemptedAction: "complete generation",
|
|
2870
|
-
message: "codex-provider: process terminated via SIGTERM"
|
|
2871
|
-
})
|
|
2872
|
-
};
|
|
2873
|
-
}
|
|
2874
|
-
if (code === 0) {
|
|
2995
|
+
const onSuccess = async () => {
|
|
2875
2996
|
let body;
|
|
2876
2997
|
try {
|
|
2877
2998
|
body = await readFile2(outputFile);
|
|
@@ -2918,6 +3039,7 @@ var spawnAttempt2 = async (input) => {
|
|
|
2918
3039
|
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
2919
3040
|
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
2920
3041
|
...window !== void 0 ? { contextWindow: window } : {},
|
|
3042
|
+
...session.role !== void 0 ? { role: session.role } : {},
|
|
2921
3043
|
at: IsoTimestamp.now()
|
|
2922
3044
|
});
|
|
2923
3045
|
}
|
|
@@ -2925,30 +3047,22 @@ var spawnAttempt2 = async (input) => {
|
|
|
2925
3047
|
kind: "success",
|
|
2926
3048
|
output: {
|
|
2927
3049
|
signalsFile: session.signalsFile,
|
|
2928
|
-
exitCode: code,
|
|
3050
|
+
exitCode: code ?? 0,
|
|
2929
3051
|
...sessionId2 !== void 0 ? { sessionId: sessionId2 } : {}
|
|
2930
3052
|
}
|
|
2931
3053
|
};
|
|
2932
|
-
}
|
|
2933
|
-
if (RATE_LIMIT_RE2.test(stderrBuf)) {
|
|
2934
|
-
return {
|
|
2935
|
-
kind: "rate-limit",
|
|
2936
|
-
error: new RateLimitError({
|
|
2937
|
-
subCode: "spawn-stderr",
|
|
2938
|
-
message: `codex-provider: rate-limit detected in stderr (exit ${String(code)})`,
|
|
2939
|
-
...sessionId2 !== void 0 ? { sessionId: sessionId2 } : {}
|
|
2940
|
-
})
|
|
2941
|
-
};
|
|
2942
|
-
}
|
|
2943
|
-
return {
|
|
2944
|
-
kind: "error",
|
|
2945
|
-
error: new InvalidStateError({
|
|
2946
|
-
entity: "codex-provider",
|
|
2947
|
-
currentState: `exit-${String(code)}`,
|
|
2948
|
-
attemptedAction: "complete generation",
|
|
2949
|
-
message: `codex-provider: process exited with code ${String(code)}: ${stderrBuf.trim() || "<empty stderr>"}`
|
|
2950
|
-
})
|
|
2951
3054
|
};
|
|
3055
|
+
return classifySpawnExit({
|
|
3056
|
+
session,
|
|
3057
|
+
exit: { code, signal },
|
|
3058
|
+
stderr: stderrBuf,
|
|
3059
|
+
rateLimitRe: RATE_LIMIT_RE2,
|
|
3060
|
+
...sessionId2 !== void 0 ? { capturedSessionId: sessionId2 } : {},
|
|
3061
|
+
providerName: "codex-provider",
|
|
3062
|
+
eventBus: deps.eventBus,
|
|
3063
|
+
watchdogBannerId,
|
|
3064
|
+
onSuccess
|
|
3065
|
+
});
|
|
2952
3066
|
};
|
|
2953
3067
|
var defaultSpawn4 = (command, args, options) => nodeSpawn4(command, [...args], {
|
|
2954
3068
|
stdio: [...options.stdio],
|
|
@@ -3177,6 +3291,7 @@ var spawnAttempt3 = async (input) => {
|
|
|
3177
3291
|
let model;
|
|
3178
3292
|
let usage = {};
|
|
3179
3293
|
let stderrBuf = "";
|
|
3294
|
+
const watchdogBannerId = `watchdog-copilot-${String(child.pid ?? "unknown")}`;
|
|
3180
3295
|
const onLine = (line) => {
|
|
3181
3296
|
if (line.json !== void 0) {
|
|
3182
3297
|
if (line.sessionId !== void 0 && sessionId2 === void 0) {
|
|
@@ -3231,7 +3346,7 @@ var spawnAttempt3 = async (input) => {
|
|
|
3231
3346
|
});
|
|
3232
3347
|
deps.eventBus.publish({
|
|
3233
3348
|
type: "banner-show",
|
|
3234
|
-
id:
|
|
3349
|
+
id: watchdogBannerId,
|
|
3235
3350
|
tier: "warn",
|
|
3236
3351
|
message: `Watchdog killed stuck copilot process${idleMs !== void 0 ? ` (${String(Math.round(idleMs / 1e3))}s idle)` : ""}`,
|
|
3237
3352
|
at: IsoTimestamp.now()
|
|
@@ -3239,18 +3354,7 @@ var spawnAttempt3 = async (input) => {
|
|
|
3239
3354
|
}
|
|
3240
3355
|
});
|
|
3241
3356
|
parser.flush(onLine);
|
|
3242
|
-
|
|
3243
|
-
return {
|
|
3244
|
-
kind: "error",
|
|
3245
|
-
error: new InvalidStateError({
|
|
3246
|
-
entity: "copilot-provider",
|
|
3247
|
-
currentState: "terminated",
|
|
3248
|
-
attemptedAction: "complete generation",
|
|
3249
|
-
message: "copilot-provider: process terminated via SIGTERM"
|
|
3250
|
-
})
|
|
3251
|
-
};
|
|
3252
|
-
}
|
|
3253
|
-
if (code === 0) {
|
|
3357
|
+
const onSuccess = async () => {
|
|
3254
3358
|
const forensicBody = events.map((e) => e.text).join("\n");
|
|
3255
3359
|
if (session.bodyFile !== void 0) {
|
|
3256
3360
|
const bodyWrote = await writeTextAtomic(String(session.bodyFile), forensicBody);
|
|
@@ -3274,6 +3378,7 @@ var spawnAttempt3 = async (input) => {
|
|
|
3274
3378
|
...usage.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
|
|
3275
3379
|
...usage.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
|
|
3276
3380
|
...window !== void 0 ? { contextWindow: window } : {},
|
|
3381
|
+
...session.role !== void 0 ? { role: session.role } : {},
|
|
3277
3382
|
at: IsoTimestamp.now()
|
|
3278
3383
|
});
|
|
3279
3384
|
}
|
|
@@ -3291,30 +3396,22 @@ var spawnAttempt3 = async (input) => {
|
|
|
3291
3396
|
kind: "success",
|
|
3292
3397
|
output: {
|
|
3293
3398
|
signalsFile: session.signalsFile,
|
|
3294
|
-
exitCode: code,
|
|
3399
|
+
exitCode: code ?? 0,
|
|
3295
3400
|
...sessionId2 !== void 0 ? { sessionId: sessionId2 } : {}
|
|
3296
3401
|
}
|
|
3297
3402
|
};
|
|
3298
|
-
}
|
|
3299
|
-
if (RATE_LIMIT_RE3.test(stderrBuf)) {
|
|
3300
|
-
return {
|
|
3301
|
-
kind: "rate-limit",
|
|
3302
|
-
error: new RateLimitError({
|
|
3303
|
-
subCode: "spawn-stderr",
|
|
3304
|
-
message: `copilot-provider: rate-limit detected in stderr (exit ${String(code)})`,
|
|
3305
|
-
...sessionId2 !== void 0 ? { sessionId: sessionId2 } : {}
|
|
3306
|
-
})
|
|
3307
|
-
};
|
|
3308
|
-
}
|
|
3309
|
-
return {
|
|
3310
|
-
kind: "error",
|
|
3311
|
-
error: new InvalidStateError({
|
|
3312
|
-
entity: "copilot-provider",
|
|
3313
|
-
currentState: `exit-${String(code)}`,
|
|
3314
|
-
attemptedAction: "complete generation",
|
|
3315
|
-
message: `copilot-provider: process exited with code ${String(code)}: ${stderrBuf.trim() || "<empty stderr>"}`
|
|
3316
|
-
})
|
|
3317
3403
|
};
|
|
3404
|
+
return classifySpawnExit({
|
|
3405
|
+
session,
|
|
3406
|
+
exit: { code, signal },
|
|
3407
|
+
stderr: stderrBuf,
|
|
3408
|
+
rateLimitRe: RATE_LIMIT_RE3,
|
|
3409
|
+
...sessionId2 !== void 0 ? { capturedSessionId: sessionId2 } : {},
|
|
3410
|
+
providerName: "copilot-provider",
|
|
3411
|
+
eventBus: deps.eventBus,
|
|
3412
|
+
watchdogBannerId,
|
|
3413
|
+
onSuccess
|
|
3414
|
+
});
|
|
3318
3415
|
};
|
|
3319
3416
|
var defaultSpawn5 = (command, args, options) => nodeSpawn5(command, [...args], {
|
|
3320
3417
|
stdio: [...options.stdio],
|
|
@@ -3322,8 +3419,12 @@ var defaultSpawn5 = (command, args, options) => nodeSpawn5(command, [...args], {
|
|
|
3322
3419
|
});
|
|
3323
3420
|
|
|
3324
3421
|
// src/application/bootstrap/provider-factory.ts
|
|
3422
|
+
var resolveRow = (deps) => {
|
|
3423
|
+
if ("row" in deps) return deps.row;
|
|
3424
|
+
return primaryFlowRow(deps.ai, deps.flow);
|
|
3425
|
+
};
|
|
3325
3426
|
var createAiProvider = (deps) => {
|
|
3326
|
-
const row = deps
|
|
3427
|
+
const row = resolveRow(deps);
|
|
3327
3428
|
switch (row.provider) {
|
|
3328
3429
|
case "claude-code":
|
|
3329
3430
|
return createClaudeProvider({
|
|
@@ -3614,7 +3715,7 @@ var stringifyError6 = (cause) => cause instanceof Error ? cause.message : String
|
|
|
3614
3715
|
|
|
3615
3716
|
// src/application/bootstrap/interactive-provider-factory.ts
|
|
3616
3717
|
var createInteractiveAiProvider = (deps) => {
|
|
3617
|
-
const row = deps.ai
|
|
3718
|
+
const row = primaryFlowRow(deps.ai, deps.flow);
|
|
3618
3719
|
switch (row.provider) {
|
|
3619
3720
|
case "claude-code":
|
|
3620
3721
|
return createInteractiveClaudeProvider({ eventBus: deps.eventBus });
|
|
@@ -4360,9 +4461,9 @@ var claudeProbe = {
|
|
|
4360
4461
|
};
|
|
4361
4462
|
var readHooks = async (settingsRefs) => {
|
|
4362
4463
|
const hooks = [];
|
|
4363
|
-
for (const
|
|
4364
|
-
if (
|
|
4365
|
-
const text = await readFileSafely(
|
|
4464
|
+
for (const ref3 of settingsRefs) {
|
|
4465
|
+
if (ref3 === void 0) continue;
|
|
4466
|
+
const text = await readFileSafely(ref3.path);
|
|
4366
4467
|
if (!text.ok) return Result.error(text.error);
|
|
4367
4468
|
if (text.value === void 0) continue;
|
|
4368
4469
|
let parsed;
|
|
@@ -4370,7 +4471,7 @@ var readHooks = async (settingsRefs) => {
|
|
|
4370
4471
|
parsed = JSON.parse(text.value);
|
|
4371
4472
|
} catch (cause) {
|
|
4372
4473
|
return Result.error(
|
|
4373
|
-
new ProbeError({ subCode: "malformed", message: `${
|
|
4474
|
+
new ProbeError({ subCode: "malformed", message: `${ref3.path} is not valid JSON`, path: ref3.path, cause })
|
|
4374
4475
|
);
|
|
4375
4476
|
}
|
|
4376
4477
|
extractHooks(parsed, hooks);
|
|
@@ -4583,7 +4684,7 @@ var createNpmVersionChecker = (deps) => {
|
|
|
4583
4684
|
// package.json
|
|
4584
4685
|
var package_default = {
|
|
4585
4686
|
name: "ralphctl",
|
|
4586
|
-
version: "0.8.
|
|
4687
|
+
version: "0.8.1",
|
|
4587
4688
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code, GitHub Copilot, and OpenAI Codex across repositories",
|
|
4588
4689
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
4589
4690
|
type: "module",
|
|
@@ -4682,6 +4783,27 @@ var CLI_METADATA = {
|
|
|
4682
4783
|
currentVersion: package_default.version
|
|
4683
4784
|
};
|
|
4684
4785
|
|
|
4786
|
+
// src/business/task/escalation-map.ts
|
|
4787
|
+
var DEFAULT_ESCALATION_MAP = {
|
|
4788
|
+
// Claude — Sonnet escalates to Opus; Haiku escalates to Sonnet.
|
|
4789
|
+
"claude-haiku-4-5": "claude-sonnet-4-6",
|
|
4790
|
+
"claude-sonnet-4-6": "claude-opus-4-7",
|
|
4791
|
+
// Copilot/Codex — mini variants step up to their full-tier frontier.
|
|
4792
|
+
"gpt-5-mini": "gpt-5.5",
|
|
4793
|
+
"gpt-5.4-mini": "gpt-5.5"
|
|
4794
|
+
};
|
|
4795
|
+
var mergeEscalationMap = (user) => ({
|
|
4796
|
+
...DEFAULT_ESCALATION_MAP,
|
|
4797
|
+
...user
|
|
4798
|
+
});
|
|
4799
|
+
var warnEscalationMapSelfLoops = (escalationMap, logger) => {
|
|
4800
|
+
for (const [from, to] of Object.entries(escalationMap)) {
|
|
4801
|
+
if (from === to) {
|
|
4802
|
+
logger.warn(`escalationMap: '${from}' maps to itself \u2014 entry has no effect`, { from, to });
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
};
|
|
4806
|
+
|
|
4685
4807
|
// src/integration/ai/skills/_engine/filesystem-skills-adapter.ts
|
|
4686
4808
|
import { existsSync } from "fs";
|
|
4687
4809
|
import { mkdir, rm, rmdir, writeFile } from "fs/promises";
|
|
@@ -4928,11 +5050,36 @@ var SkillFrontmatterSchema = z16.object({
|
|
|
4928
5050
|
|
|
4929
5051
|
// src/integration/ai/skills/_engine/registry.ts
|
|
4930
5052
|
var FLOW_SKILLS = {
|
|
4931
|
-
refine: [
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
5053
|
+
refine: [
|
|
5054
|
+
"ralphctl-alignment",
|
|
5055
|
+
"ralphctl-iterative-review",
|
|
5056
|
+
"ralphctl-abstraction-first",
|
|
5057
|
+
"ralphctl-minimal-scaffolding"
|
|
5058
|
+
],
|
|
5059
|
+
plan: [
|
|
5060
|
+
"ralphctl-alignment",
|
|
5061
|
+
"ralphctl-iterative-review",
|
|
5062
|
+
"ralphctl-abstraction-first",
|
|
5063
|
+
"ralphctl-minimal-scaffolding"
|
|
5064
|
+
],
|
|
5065
|
+
implement: [
|
|
5066
|
+
"ralphctl-alignment",
|
|
5067
|
+
"ralphctl-iterative-review",
|
|
5068
|
+
"ralphctl-abstraction-first",
|
|
5069
|
+
"ralphctl-minimal-scaffolding"
|
|
5070
|
+
],
|
|
5071
|
+
readiness: [
|
|
5072
|
+
"ralphctl-alignment",
|
|
5073
|
+
"ralphctl-iterative-review",
|
|
5074
|
+
"ralphctl-abstraction-first",
|
|
5075
|
+
"ralphctl-minimal-scaffolding"
|
|
5076
|
+
],
|
|
5077
|
+
ideate: [
|
|
5078
|
+
"ralphctl-alignment",
|
|
5079
|
+
"ralphctl-iterative-review",
|
|
5080
|
+
"ralphctl-abstraction-first",
|
|
5081
|
+
"ralphctl-minimal-scaffolding"
|
|
5082
|
+
]
|
|
4936
5083
|
};
|
|
4937
5084
|
var skillsForFlow = (flowId) => FLOW_SKILLS[flowId];
|
|
4938
5085
|
|
|
@@ -5170,6 +5317,7 @@ var wire = (opts) => {
|
|
|
5170
5317
|
const chainLogSink = debugTrace ? (launchDeps) => startFileLogSink({ ...launchDeps, appendFile }) : () => NOOP_CHAIN_LOG_SINK;
|
|
5171
5318
|
const eventBus = createInMemoryEventBus();
|
|
5172
5319
|
const logger = createEventBusLogger({ eventBus, clock: IsoTimestamp.now });
|
|
5320
|
+
warnEscalationMapSelfLoops(opts.settings.harness.escalationMap, logger);
|
|
5173
5321
|
const notificationDispatcher = opts.notificationDispatcher ?? noopNotificationDispatcher;
|
|
5174
5322
|
const fileLocker = createFileLocker({
|
|
5175
5323
|
// Surface stale `.lock` files via the application logger. The locker is intentionally
|
|
@@ -5223,7 +5371,7 @@ var wire = (opts) => {
|
|
|
5223
5371
|
// Wire-time seed — the per-launch launcher rebuilds skillsAdapter from the dispatched
|
|
5224
5372
|
// flow's provider. Tests / one-shot CLI paths that read `app.skillsAdapter` before any
|
|
5225
5373
|
// flow launches get the implement row's provider as the default.
|
|
5226
|
-
skillsAdapter: createSkillsAdapter({ provider: opts.settings.ai.implement.provider, logger }),
|
|
5374
|
+
skillsAdapter: createSkillsAdapter({ provider: opts.settings.ai.implement.generator.provider, logger }),
|
|
5227
5375
|
skillSource: createBundledSkillSource(),
|
|
5228
5376
|
notificationDispatcher,
|
|
5229
5377
|
chainLogSink
|
|
@@ -5340,7 +5488,9 @@ var createSessionManager = (opts) => {
|
|
|
5340
5488
|
plannedLeaves,
|
|
5341
5489
|
planLabelByName,
|
|
5342
5490
|
terminalSubstepName,
|
|
5343
|
-
taskRecovering
|
|
5491
|
+
taskRecovering,
|
|
5492
|
+
generatorModel,
|
|
5493
|
+
evaluatorModel
|
|
5344
5494
|
}) {
|
|
5345
5495
|
evict(clock());
|
|
5346
5496
|
const descriptor = {
|
|
@@ -5355,7 +5505,9 @@ var createSessionManager = (opts) => {
|
|
|
5355
5505
|
...plannedLeaves !== void 0 ? { plannedLeaves } : {},
|
|
5356
5506
|
...planLabelByName !== void 0 ? { planLabelByName } : {},
|
|
5357
5507
|
...terminalSubstepName !== void 0 ? { terminalSubstepName } : {},
|
|
5358
|
-
...taskRecovering !== void 0 ? { taskRecovering } : {}
|
|
5508
|
+
...taskRecovering !== void 0 ? { taskRecovering } : {},
|
|
5509
|
+
...generatorModel !== void 0 ? { generatorModel } : {},
|
|
5510
|
+
...evaluatorModel !== void 0 ? { evaluatorModel } : {}
|
|
5359
5511
|
};
|
|
5360
5512
|
const record = { descriptor, runner };
|
|
5361
5513
|
records.set(runner.id, record);
|
|
@@ -5491,21 +5643,6 @@ var createPromptQueue = () => {
|
|
|
5491
5643
|
};
|
|
5492
5644
|
};
|
|
5493
5645
|
|
|
5494
|
-
// src/domain/value/error/abort-error.ts
|
|
5495
|
-
var AbortError = class extends Error {
|
|
5496
|
-
code = ErrorCode.Aborted;
|
|
5497
|
-
elementName;
|
|
5498
|
-
reason;
|
|
5499
|
-
constructor(opts) {
|
|
5500
|
-
super(opts.reason ?? `operation aborted at step '${opts.elementName}'`);
|
|
5501
|
-
this.name = "AbortError";
|
|
5502
|
-
this.elementName = opts.elementName;
|
|
5503
|
-
if (opts.reason !== void 0) {
|
|
5504
|
-
this.reason = opts.reason;
|
|
5505
|
-
}
|
|
5506
|
-
}
|
|
5507
|
-
};
|
|
5508
|
-
|
|
5509
5646
|
// src/application/ui/tui/prompts/ink-interactive-prompt.ts
|
|
5510
5647
|
var wrapError = (err, elementName) => new AbortError({ elementName, reason: err instanceof Error ? err.message : "prompt cancelled" });
|
|
5511
5648
|
var createInkInteractivePrompt = (queue) => ({
|
|
@@ -5636,6 +5773,13 @@ var setRunInTerminal = (next) => {
|
|
|
5636
5773
|
};
|
|
5637
5774
|
var getRunInTerminal = () => (fn) => ref.current(fn);
|
|
5638
5775
|
|
|
5776
|
+
// src/application/ui/tui/runtime/implement-role-overrides.ts
|
|
5777
|
+
var ref2 = { current: void 0 };
|
|
5778
|
+
var setImplementRoleOverrides = (next) => {
|
|
5779
|
+
ref2.current = next;
|
|
5780
|
+
};
|
|
5781
|
+
var getImplementRoleOverrides = () => ref2.current;
|
|
5782
|
+
|
|
5639
5783
|
// src/application/ui/tui/App.tsx
|
|
5640
5784
|
import "react";
|
|
5641
5785
|
import { Box as Box59 } from "ink";
|
|
@@ -6412,11 +6556,14 @@ var createDoctorFlow = (deps) => leaf("doctor", {
|
|
|
6412
6556
|
);
|
|
6413
6557
|
}
|
|
6414
6558
|
const settings = await deps.settingsRepo.load();
|
|
6415
|
-
const configuredProviders = settings.ok ? new Set(
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6559
|
+
const configuredProviders = settings.ok ? /* @__PURE__ */ new Set([
|
|
6560
|
+
settings.value.ai.refine.provider,
|
|
6561
|
+
settings.value.ai.plan.provider,
|
|
6562
|
+
settings.value.ai.implement.generator.provider,
|
|
6563
|
+
settings.value.ai.implement.evaluator.provider,
|
|
6564
|
+
settings.value.ai.readiness.provider,
|
|
6565
|
+
settings.value.ai.ideate.provider
|
|
6566
|
+
]) : /* @__PURE__ */ new Set();
|
|
6420
6567
|
let codexInstalled = false;
|
|
6421
6568
|
for (const provider of Object.keys(PROVIDER_BINARY)) {
|
|
6422
6569
|
const binary = PROVIDER_BINARY[provider];
|
|
@@ -6679,7 +6826,7 @@ var LogLevelProvider = ({
|
|
|
6679
6826
|
import "react";
|
|
6680
6827
|
|
|
6681
6828
|
// src/application/ui/tui/views/home-view.tsx
|
|
6682
|
-
import { useCallback as useCallback8, useEffect as
|
|
6829
|
+
import { useCallback as useCallback8, useEffect as useEffect15, useMemo as useMemo8, useRef as useRef8, useState as useState23 } from "react";
|
|
6683
6830
|
import { Box as Box23, Text as Text24, useInput as useInput12 } from "ink";
|
|
6684
6831
|
|
|
6685
6832
|
// src/application/ui/tui/components/view-shell.tsx
|
|
@@ -7359,10 +7506,12 @@ var ScrollRegion = ({ children, disabled = false }) => {
|
|
|
7359
7506
|
);
|
|
7360
7507
|
useEffect8(() => {
|
|
7361
7508
|
if (!isRawModeSupported || !stdin || !stdout || !stdout.isTTY) return void 0;
|
|
7509
|
+
if (disabled) return void 0;
|
|
7362
7510
|
const enable = "\x1B[?1000h\x1B[?1006h";
|
|
7363
7511
|
const disableSeq = "\x1B[?1006l\x1B[?1000l";
|
|
7364
7512
|
stdout.write(enable);
|
|
7365
7513
|
const onData = (chunk) => {
|
|
7514
|
+
if (disabled) return;
|
|
7366
7515
|
const str = chunk.toString("utf8");
|
|
7367
7516
|
const re = /\x1b\[<(\d+);\d+;\d+([Mm])/g;
|
|
7368
7517
|
let match;
|
|
@@ -7381,7 +7530,7 @@ var ScrollRegion = ({ children, disabled = false }) => {
|
|
|
7381
7530
|
stdin.off("data", onData);
|
|
7382
7531
|
stdout.write(disableSeq);
|
|
7383
7532
|
};
|
|
7384
|
-
}, [stdin, stdout, isRawModeSupported]);
|
|
7533
|
+
}, [stdin, stdout, isRawModeSupported, disabled]);
|
|
7385
7534
|
return (
|
|
7386
7535
|
// Viewport: takes all remaining vertical space (flexGrow=1) AND clips overflow so an
|
|
7387
7536
|
// oversized inner box can't push the status bar off-screen.
|
|
@@ -7824,8 +7973,33 @@ import { Box as Box13, Text as Text14, useInput as useInput7 } from "ink";
|
|
|
7824
7973
|
import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
7825
7974
|
var VISIBLE_ROWS = 8;
|
|
7826
7975
|
var clamp = (n, min, max) => Math.max(min, Math.min(max, n));
|
|
7827
|
-
var
|
|
7828
|
-
|
|
7976
|
+
var isEnabled = (opt) => opt !== void 0 && opt.disabled !== true;
|
|
7977
|
+
var nextEnabledIndex = (options, from, direction) => {
|
|
7978
|
+
for (let i = from + direction; i >= 0 && i < options.length; i += direction) {
|
|
7979
|
+
if (isEnabled(options[i])) return i;
|
|
7980
|
+
}
|
|
7981
|
+
return from;
|
|
7982
|
+
};
|
|
7983
|
+
var firstEnabledIndex = (options) => {
|
|
7984
|
+
for (let i = 0; i < options.length; i += 1) {
|
|
7985
|
+
if (isEnabled(options[i])) return i;
|
|
7986
|
+
}
|
|
7987
|
+
return 0;
|
|
7988
|
+
};
|
|
7989
|
+
var lastEnabledIndex = (options) => {
|
|
7990
|
+
for (let i = options.length - 1; i >= 0; i -= 1) {
|
|
7991
|
+
if (isEnabled(options[i])) return i;
|
|
7992
|
+
}
|
|
7993
|
+
return Math.max(0, options.length - 1);
|
|
7994
|
+
};
|
|
7995
|
+
var SelectPrompt = ({
|
|
7996
|
+
message,
|
|
7997
|
+
options,
|
|
7998
|
+
onSubmit,
|
|
7999
|
+
onCancel,
|
|
8000
|
+
footer
|
|
8001
|
+
}) => {
|
|
8002
|
+
const [cursor, setCursor] = useState16(() => firstEnabledIndex(options));
|
|
7829
8003
|
useInput7((input, key) => {
|
|
7830
8004
|
if (key.escape) {
|
|
7831
8005
|
onCancel();
|
|
@@ -7833,13 +8007,14 @@ var SelectPrompt = ({ message, options, onSubmit, onCancel }) => {
|
|
|
7833
8007
|
}
|
|
7834
8008
|
if (key.return || input === " ") {
|
|
7835
8009
|
const opt = options[cursor];
|
|
7836
|
-
if (opt !== void 0) onSubmit(opt.value);
|
|
8010
|
+
if (opt !== void 0 && opt.disabled !== true) onSubmit(opt.value);
|
|
7837
8011
|
return;
|
|
7838
8012
|
}
|
|
7839
|
-
if (key.upArrow || input === "k") setCursor((c) => clamp(c -
|
|
7840
|
-
else if (key.downArrow || input === "j")
|
|
7841
|
-
|
|
7842
|
-
else if (input === "
|
|
8013
|
+
if (key.upArrow || input === "k") setCursor((c) => clamp(nextEnabledIndex(options, c, -1), 0, options.length - 1));
|
|
8014
|
+
else if (key.downArrow || input === "j")
|
|
8015
|
+
setCursor((c) => clamp(nextEnabledIndex(options, c, 1), 0, options.length - 1));
|
|
8016
|
+
else if (input === "g") setCursor(firstEnabledIndex(options));
|
|
8017
|
+
else if (input === "G") setCursor(lastEnabledIndex(options));
|
|
7843
8018
|
});
|
|
7844
8019
|
const half = Math.floor(VISIBLE_ROWS / 2);
|
|
7845
8020
|
const start = clamp(cursor - half, 0, Math.max(0, options.length - VISIBLE_ROWS));
|
|
@@ -7849,12 +8024,13 @@ var SelectPrompt = ({ message, options, onSubmit, onCancel }) => {
|
|
|
7849
8024
|
/* @__PURE__ */ jsx24(Box13, { flexDirection: "column", marginTop: 1, children: options.slice(start, end).map((opt, localIdx) => {
|
|
7850
8025
|
const i = start + localIdx;
|
|
7851
8026
|
const focused = i === cursor;
|
|
8027
|
+
const disabled = opt.disabled === true;
|
|
7852
8028
|
return /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
7853
|
-
/* @__PURE__ */ jsxs13(Text14, { color: focused ? inkColors.primary : inkColors.muted, children: [
|
|
7854
|
-
focused ? glyphs.actionCursor : " ",
|
|
8029
|
+
/* @__PURE__ */ jsxs13(Text14, { color: focused && !disabled ? inkColors.primary : inkColors.muted, children: [
|
|
8030
|
+
focused && !disabled ? glyphs.actionCursor : " ",
|
|
7855
8031
|
" "
|
|
7856
8032
|
] }),
|
|
7857
|
-
/* @__PURE__ */ jsx24(Text14, { bold: focused, children: opt.label }),
|
|
8033
|
+
/* @__PURE__ */ jsx24(Text14, { bold: focused && !disabled, dimColor: disabled, children: opt.label }),
|
|
7858
8034
|
opt.description !== void 0 && /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
|
|
7859
8035
|
" ",
|
|
7860
8036
|
glyphs.emDash,
|
|
@@ -7868,6 +8044,7 @@ var SelectPrompt = ({ message, options, onSubmit, onCancel }) => {
|
|
|
7868
8044
|
" of ",
|
|
7869
8045
|
String(options.length)
|
|
7870
8046
|
] }),
|
|
8047
|
+
footer !== void 0 && /* @__PURE__ */ jsx24(Text14, { dimColor: true, children: footer }),
|
|
7871
8048
|
/* @__PURE__ */ jsx24(Text14, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 \u21B5 submit \xB7 esc cancel" })
|
|
7872
8049
|
] });
|
|
7873
8050
|
};
|
|
@@ -8116,11 +8293,11 @@ var Card = ({ title, tone = "rule", dim, right, children }) => {
|
|
|
8116
8293
|
import { useEffect as useEffect12, useState as useState19 } from "react";
|
|
8117
8294
|
import { Box as Box18, Text as Text18, useInput as useInput9 } from "ink";
|
|
8118
8295
|
import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
8119
|
-
var
|
|
8296
|
+
var isEnabled2 = (item) => item !== void 0 && item.disabledReason === void 0;
|
|
8120
8297
|
var findFirstEnabled = (items, from, dir) => {
|
|
8121
8298
|
let i = from;
|
|
8122
8299
|
while (i >= 0 && i < items.length) {
|
|
8123
|
-
if (
|
|
8300
|
+
if (isEnabled2(items[i])) return i;
|
|
8124
8301
|
i += dir;
|
|
8125
8302
|
}
|
|
8126
8303
|
return null;
|
|
@@ -8133,7 +8310,7 @@ var ActionMenu = ({ items, initialIndex = 0, active = true }) => {
|
|
|
8133
8310
|
useEffect12(() => {
|
|
8134
8311
|
if (cursor >= items.length) {
|
|
8135
8312
|
setCursor(items.length === 0 ? 0 : items.length - 1);
|
|
8136
|
-
} else if (!
|
|
8313
|
+
} else if (!isEnabled2(items[cursor])) {
|
|
8137
8314
|
const next = findFirstEnabled(items, cursor, 1) ?? findFirstEnabled(items, cursor, -1);
|
|
8138
8315
|
if (next !== null) setCursor(next);
|
|
8139
8316
|
}
|
|
@@ -8163,11 +8340,11 @@ var ActionMenu = ({ items, initialIndex = 0, active = true }) => {
|
|
|
8163
8340
|
}
|
|
8164
8341
|
if (key.return || input === " ") {
|
|
8165
8342
|
const item = items[cursor];
|
|
8166
|
-
if (item &&
|
|
8343
|
+
if (item && isEnabled2(item)) item.onSelect();
|
|
8167
8344
|
return;
|
|
8168
8345
|
}
|
|
8169
8346
|
if (input.length > 0) {
|
|
8170
|
-
const hit = items.findIndex((it) => it.hotkey === input && it.globalHotkey !== true &&
|
|
8347
|
+
const hit = items.findIndex((it) => it.hotkey === input && it.globalHotkey !== true && isEnabled2(it));
|
|
8171
8348
|
if (hit !== -1) {
|
|
8172
8349
|
setCursor(hit);
|
|
8173
8350
|
const item = items[hit];
|
|
@@ -8183,7 +8360,7 @@ var ActionMenu = ({ items, initialIndex = 0, active = true }) => {
|
|
|
8183
8360
|
let lastSection;
|
|
8184
8361
|
return /* @__PURE__ */ jsx29(Box18, { flexDirection: "column", children: items.map((it, i) => {
|
|
8185
8362
|
const focused = i === cursor;
|
|
8186
|
-
const enabled =
|
|
8363
|
+
const enabled = isEnabled2(it);
|
|
8187
8364
|
const showHeader = it.section !== void 0 && it.section !== lastSection;
|
|
8188
8365
|
if (it.section !== void 0) lastSection = it.section;
|
|
8189
8366
|
return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
|
|
@@ -8524,7 +8701,7 @@ var keySections = [
|
|
|
8524
8701
|
];
|
|
8525
8702
|
|
|
8526
8703
|
// src/application/ui/tui/components/tasks-panel.tsx
|
|
8527
|
-
import { useMemo as useMemo7, useState as useState22 } from "react";
|
|
8704
|
+
import { useEffect as useEffect14, useMemo as useMemo7, useRef as useRef7, useState as useState22 } from "react";
|
|
8528
8705
|
import { Box as Box21, Text as Text22, useInput as useInput11 } from "ink";
|
|
8529
8706
|
|
|
8530
8707
|
// src/application/ui/tui/runtime/use-no-color.ts
|
|
@@ -9325,15 +9502,25 @@ var TasksPanel = ({
|
|
|
9325
9502
|
const [focusedKey, setFocusedKey] = useState22(void 0);
|
|
9326
9503
|
const [expandedKeys, setExpandedKeys] = useState22(() => /* @__PURE__ */ new Set());
|
|
9327
9504
|
const [criteriaExpandedIds, setCriteriaExpandedIds] = useState22(() => /* @__PURE__ */ new Set());
|
|
9328
|
-
const [expandedTaskIds, setExpandedTaskIds] = useState22(() => /* @__PURE__ */ new Set());
|
|
9329
|
-
const [cardCursor, setCardCursor] = useState22(void 0);
|
|
9330
9505
|
const activeTaskIdx = bucketed.tasks.findIndex((t) => t.status !== "completed");
|
|
9331
9506
|
const activeTaskId = activeTaskIdx >= 0 ? bucketed.tasks[activeTaskIdx]?.id : void 0;
|
|
9332
|
-
const
|
|
9333
|
-
|
|
9334
|
-
|
|
9335
|
-
|
|
9336
|
-
|
|
9507
|
+
const [expandedTaskIds, setExpandedTaskIds] = useState22(
|
|
9508
|
+
() => new Set(activeTaskId !== void 0 ? [activeTaskId] : [])
|
|
9509
|
+
);
|
|
9510
|
+
const [cardCursor, setCardCursor] = useState22(void 0);
|
|
9511
|
+
const prevActiveTaskIdRef = useRef7(activeTaskId);
|
|
9512
|
+
useEffect14(() => {
|
|
9513
|
+
if (activeTaskId !== void 0 && prevActiveTaskIdRef.current !== activeTaskId) {
|
|
9514
|
+
setExpandedTaskIds((prev) => {
|
|
9515
|
+
if (prev.has(activeTaskId)) return prev;
|
|
9516
|
+
const next = new Set(prev);
|
|
9517
|
+
next.add(activeTaskId);
|
|
9518
|
+
return next;
|
|
9519
|
+
});
|
|
9520
|
+
}
|
|
9521
|
+
prevActiveTaskIdRef.current = activeTaskId;
|
|
9522
|
+
}, [activeTaskId]);
|
|
9523
|
+
const isCardExpanded = (taskId) => expandedTaskIds.has(taskId);
|
|
9337
9524
|
const effectiveCardCursor = useMemo7(() => {
|
|
9338
9525
|
if (cardCursor !== void 0 && cardCursor >= 0 && cardCursor < bucketed.tasks.length) return cardCursor;
|
|
9339
9526
|
if (activeTaskIdx >= 0) return activeTaskIdx;
|
|
@@ -9355,7 +9542,7 @@ var TasksPanel = ({
|
|
|
9355
9542
|
return;
|
|
9356
9543
|
}
|
|
9357
9544
|
if (key.escape) {
|
|
9358
|
-
if (focusedCardId !== void 0 &&
|
|
9545
|
+
if (focusedCardId !== void 0 && expandedTaskIds.has(focusedCardId)) {
|
|
9359
9546
|
setExpandedTaskIds((prev) => {
|
|
9360
9547
|
const next = new Set(prev);
|
|
9361
9548
|
next.delete(focusedCardId);
|
|
@@ -9390,10 +9577,12 @@ var TasksPanel = ({
|
|
|
9390
9577
|
return;
|
|
9391
9578
|
}
|
|
9392
9579
|
if (key.return || input === " ") {
|
|
9393
|
-
|
|
9580
|
+
const rowCursorAnchored = focusedCardExpanded && flatKeys.length > 0 && focusedIndex >= 0;
|
|
9581
|
+
if (focusedCardId !== void 0 && !rowCursorAnchored) {
|
|
9394
9582
|
setExpandedTaskIds((prev) => {
|
|
9395
9583
|
const next = new Set(prev);
|
|
9396
|
-
next.
|
|
9584
|
+
if (next.has(focusedCardId)) next.delete(focusedCardId);
|
|
9585
|
+
else next.add(focusedCardId);
|
|
9397
9586
|
return next;
|
|
9398
9587
|
});
|
|
9399
9588
|
return;
|
|
@@ -9637,9 +9826,11 @@ var composeSkillSources = (...sources) => ({
|
|
|
9637
9826
|
|
|
9638
9827
|
// src/business/settings/resolve-effort.ts
|
|
9639
9828
|
var resolveEffort = (flow, settings) => {
|
|
9640
|
-
const row = settings.ai
|
|
9829
|
+
const row = primaryFlowRow(settings.ai, flow);
|
|
9830
|
+
return resolveEffortForRow(row, settings.ai.effort);
|
|
9831
|
+
};
|
|
9832
|
+
var resolveEffortForRow = (row, globalEffort) => {
|
|
9641
9833
|
if (row.effort !== void 0) return row.effort;
|
|
9642
|
-
const globalEffort = settings.ai.effort;
|
|
9643
9834
|
if (globalEffort === void 0) return void 0;
|
|
9644
9835
|
return _floorForProvider(globalEffort, row.provider);
|
|
9645
9836
|
};
|
|
@@ -9913,6 +10104,14 @@ var appendExecutionSetupRun = (execution, run) => ({
|
|
|
9913
10104
|
...execution,
|
|
9914
10105
|
setupRanAt: [...execution.setupRanAt, run]
|
|
9915
10106
|
});
|
|
10107
|
+
var setExecutionBaselineBrokenPolicy = (execution, policy) => {
|
|
10108
|
+
if (policy === void 0) {
|
|
10109
|
+
const { baselineBrokenPolicy: _omit, ...rest } = execution;
|
|
10110
|
+
void _omit;
|
|
10111
|
+
return rest;
|
|
10112
|
+
}
|
|
10113
|
+
return { ...execution, baselineBrokenPolicy: policy };
|
|
10114
|
+
};
|
|
9916
10115
|
|
|
9917
10116
|
// src/domain/entity/sprint.ts
|
|
9918
10117
|
var sprintBaseFrom = (sprint) => ({
|
|
@@ -11581,6 +11780,77 @@ var PROVIDER_BINARY2 = {
|
|
|
11581
11780
|
"github-copilot": "gh",
|
|
11582
11781
|
"openai-codex": "codex"
|
|
11583
11782
|
};
|
|
11783
|
+
var PROVIDER_INSTALL_GUIDANCE = {
|
|
11784
|
+
"claude-code": {
|
|
11785
|
+
docsUrl: "https://docs.claude.com/en/docs/claude-code/setup",
|
|
11786
|
+
commandsByPlatform: {
|
|
11787
|
+
darwin: [
|
|
11788
|
+
"brew install --cask claude-code",
|
|
11789
|
+
"curl -fsSL https://claude.ai/install.sh | bash",
|
|
11790
|
+
"npm install -g @anthropic-ai/claude-code"
|
|
11791
|
+
],
|
|
11792
|
+
linux: ["curl -fsSL https://claude.ai/install.sh | bash", "npm install -g @anthropic-ai/claude-code"],
|
|
11793
|
+
win32: [
|
|
11794
|
+
"winget install Anthropic.ClaudeCode",
|
|
11795
|
+
"irm https://claude.ai/install.ps1 | iex",
|
|
11796
|
+
"npm install -g @anthropic-ai/claude-code"
|
|
11797
|
+
]
|
|
11798
|
+
}
|
|
11799
|
+
},
|
|
11800
|
+
"github-copilot": {
|
|
11801
|
+
docsUrl: "https://docs.github.com/en/copilot/how-tos/use-copilot-agents/use-copilot-in-the-cli",
|
|
11802
|
+
commandsByPlatform: {
|
|
11803
|
+
darwin: ["brew install gh && gh extension install github/gh-copilot", "gh extension install github/gh-copilot"],
|
|
11804
|
+
linux: [
|
|
11805
|
+
"install gh from https://github.com/cli/cli/blob/trunk/docs/install_linux.md, then: gh extension install github/gh-copilot",
|
|
11806
|
+
"gh extension install github/gh-copilot"
|
|
11807
|
+
],
|
|
11808
|
+
win32: [
|
|
11809
|
+
"winget install --id GitHub.cli && gh extension install github/gh-copilot",
|
|
11810
|
+
"gh extension install github/gh-copilot"
|
|
11811
|
+
]
|
|
11812
|
+
}
|
|
11813
|
+
},
|
|
11814
|
+
"openai-codex": {
|
|
11815
|
+
docsUrl: "https://github.com/openai/codex",
|
|
11816
|
+
commandsByPlatform: {
|
|
11817
|
+
darwin: [
|
|
11818
|
+
"brew install --cask codex",
|
|
11819
|
+
"curl -fsSL https://chatgpt.com/codex/install.sh | sh",
|
|
11820
|
+
"npm install -g @openai/codex"
|
|
11821
|
+
],
|
|
11822
|
+
linux: ["curl -fsSL https://chatgpt.com/codex/install.sh | sh", "npm install -g @openai/codex"],
|
|
11823
|
+
win32: [
|
|
11824
|
+
'powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"',
|
|
11825
|
+
"npm install -g @openai/codex"
|
|
11826
|
+
]
|
|
11827
|
+
}
|
|
11828
|
+
}
|
|
11829
|
+
};
|
|
11830
|
+
var resolveInstallPlatform = (platform = process.platform) => {
|
|
11831
|
+
if (platform === "darwin" || platform === "win32") return platform;
|
|
11832
|
+
return "linux";
|
|
11833
|
+
};
|
|
11834
|
+
var primaryInstallCommand = (provider, platform = process.platform) => {
|
|
11835
|
+
const os = resolveInstallPlatform(platform);
|
|
11836
|
+
const list = PROVIDER_INSTALL_GUIDANCE[provider].commandsByPlatform[os];
|
|
11837
|
+
const first = list[0];
|
|
11838
|
+
if (first === void 0) {
|
|
11839
|
+
throw new Error(`No install command registered for ${provider} on ${os}`);
|
|
11840
|
+
}
|
|
11841
|
+
return first;
|
|
11842
|
+
};
|
|
11843
|
+
var renderProviderInstallGuidance = (provider, platform = process.platform) => {
|
|
11844
|
+
const os = resolveInstallPlatform(platform);
|
|
11845
|
+
const guidance = PROVIDER_INSTALL_GUIDANCE[provider];
|
|
11846
|
+
const commands = guidance.commandsByPlatform[os];
|
|
11847
|
+
const header = `${provider} CLI (${PROVIDER_BINARY2[provider]}) not on PATH`;
|
|
11848
|
+
const bullets = commands.map((c) => ` \u2022 ${c}`).join("\n");
|
|
11849
|
+
return `${header}
|
|
11850
|
+
Install options (${os}):
|
|
11851
|
+
${bullets}
|
|
11852
|
+
Docs: ${guidance.docsUrl}`;
|
|
11853
|
+
};
|
|
11584
11854
|
var defaultWhich = (binary) => new Promise((resolve) => {
|
|
11585
11855
|
const child = spawn2("command", ["-v", binary], { stdio: "pipe", shell: true });
|
|
11586
11856
|
let settled = false;
|
|
@@ -11621,18 +11891,48 @@ var aiFlowIdForCheck = (flowId) => {
|
|
|
11621
11891
|
return void 0;
|
|
11622
11892
|
}
|
|
11623
11893
|
};
|
|
11894
|
+
var rowExpectationsFor = (aiFlow, settings) => {
|
|
11895
|
+
if (aiFlow === "implement") {
|
|
11896
|
+
return [
|
|
11897
|
+
{
|
|
11898
|
+
provider: settings.ai.implement.generator.provider,
|
|
11899
|
+
settingsKey: "ai.implement.generator.provider",
|
|
11900
|
+
role: "generator"
|
|
11901
|
+
},
|
|
11902
|
+
{
|
|
11903
|
+
provider: settings.ai.implement.evaluator.provider,
|
|
11904
|
+
settingsKey: "ai.implement.evaluator.provider",
|
|
11905
|
+
role: "evaluator"
|
|
11906
|
+
}
|
|
11907
|
+
];
|
|
11908
|
+
}
|
|
11909
|
+
return [
|
|
11910
|
+
{
|
|
11911
|
+
provider: primaryFlowRow(settings.ai, aiFlow).provider,
|
|
11912
|
+
settingsKey: `ai.${aiFlow}.provider`
|
|
11913
|
+
}
|
|
11914
|
+
];
|
|
11915
|
+
};
|
|
11916
|
+
var renderMissing = (missing, aiFlow) => {
|
|
11917
|
+
const formatOne = (m) => {
|
|
11918
|
+
const binary = PROVIDER_BINARY2[m.provider];
|
|
11919
|
+
const installHint = primaryInstallCommand(m.provider);
|
|
11920
|
+
const docsUrl = PROVIDER_INSTALL_GUIDANCE[m.provider].docsUrl;
|
|
11921
|
+
const roleSuffix = m.role !== void 0 ? ` (${m.role})` : "";
|
|
11922
|
+
return `CLI ${binary} not on PATH for flow ${aiFlow}${roleSuffix}. Change ${m.settingsKey} or install with: ${installHint} (alternatives: ${docsUrl}).`;
|
|
11923
|
+
};
|
|
11924
|
+
if (missing.length === 1) return formatOne(missing[0]);
|
|
11925
|
+
return missing.map(formatOne).join(" ");
|
|
11926
|
+
};
|
|
11624
11927
|
var checkCli = async (flowId, settings, options = {}) => {
|
|
11625
11928
|
const aiFlow = aiFlowIdForCheck(flowId);
|
|
11626
11929
|
if (aiFlow === void 0) return void 0;
|
|
11627
|
-
const
|
|
11628
|
-
const binary = PROVIDER_BINARY2[provider];
|
|
11930
|
+
const expectations = rowExpectationsFor(aiFlow, settings);
|
|
11629
11931
|
const detect = options.detect ?? (() => detectInstalledProviders());
|
|
11630
11932
|
const installed = await detect();
|
|
11631
|
-
|
|
11632
|
-
return
|
|
11633
|
-
|
|
11634
|
-
reason: `CLI ${binary} not on PATH for flow ${aiFlow}. Change ai.${aiFlow}.provider or install the CLI.`
|
|
11635
|
-
};
|
|
11933
|
+
const missing = expectations.filter((e) => !installed.has(e.provider));
|
|
11934
|
+
if (missing.length === 0) return void 0;
|
|
11935
|
+
return { ok: false, reason: renderMissing(missing, aiFlow) };
|
|
11636
11936
|
};
|
|
11637
11937
|
|
|
11638
11938
|
// src/application/ui/shared/launch/refine.ts
|
|
@@ -12277,6 +12577,8 @@ var markTaskDone = (task, now) => {
|
|
|
12277
12577
|
...guard2.value.maxAttempts !== void 0 ? { maxAttempts: guard2.value.maxAttempts } : {},
|
|
12278
12578
|
...guard2.value.extraDimensions !== void 0 ? { extraDimensions: guard2.value.extraDimensions } : {},
|
|
12279
12579
|
...guard2.value.externalRefs !== void 0 ? { externalRefs: guard2.value.externalRefs } : {},
|
|
12580
|
+
...guard2.value.escalatedFromModel !== void 0 ? { escalatedFromModel: guard2.value.escalatedFromModel } : {},
|
|
12581
|
+
...guard2.value.escalatedToModel !== void 0 ? { escalatedToModel: guard2.value.escalatedToModel } : {},
|
|
12280
12582
|
status: "done",
|
|
12281
12583
|
attempts,
|
|
12282
12584
|
finalAttemptN: verified.n
|
|
@@ -12306,6 +12608,24 @@ var failCurrentAttempt = (task, now, reason, abortMeta) => {
|
|
|
12306
12608
|
}
|
|
12307
12609
|
return Result.ok(inProgressNext);
|
|
12308
12610
|
};
|
|
12611
|
+
var recordTaskEscalation = (task, fromModel, toModel) => {
|
|
12612
|
+
if (task.escalatedFromModel !== void 0 || task.escalatedToModel !== void 0) {
|
|
12613
|
+
return Result.error(
|
|
12614
|
+
new InvalidStateError({
|
|
12615
|
+
entity: "task",
|
|
12616
|
+
currentState: "in_progress",
|
|
12617
|
+
attemptedAction: "record-escalation",
|
|
12618
|
+
message: `task '${task.id}' already escalated (${String(task.escalatedFromModel)} \u2192 ${String(task.escalatedToModel)})`,
|
|
12619
|
+
hint: "The once-per-task cap blocks a second escalation; transition to blocked instead."
|
|
12620
|
+
})
|
|
12621
|
+
);
|
|
12622
|
+
}
|
|
12623
|
+
const from = parseRequiredString("task.escalatedFromModel", fromModel);
|
|
12624
|
+
if (!from.ok) return Result.error(from.error);
|
|
12625
|
+
const to = parseRequiredString("task.escalatedToModel", toModel);
|
|
12626
|
+
if (!to.ok) return Result.error(to.error);
|
|
12627
|
+
return Result.ok({ ...task, escalatedFromModel: from.value, escalatedToModel: to.value });
|
|
12628
|
+
};
|
|
12309
12629
|
var markTaskBlocked = (task, reason) => {
|
|
12310
12630
|
const guard2 = requireStatus(
|
|
12311
12631
|
"task",
|
|
@@ -12475,13 +12795,13 @@ var parseTaskList = (rawTasks, input) => {
|
|
|
12475
12795
|
);
|
|
12476
12796
|
}
|
|
12477
12797
|
const dependsOn = [];
|
|
12478
|
-
for (const
|
|
12479
|
-
const resolved = idMap.get(
|
|
12798
|
+
for (const ref3 of t.blockedBy ?? []) {
|
|
12799
|
+
const resolved = idMap.get(ref3);
|
|
12480
12800
|
if (resolved === void 0) {
|
|
12481
12801
|
return Result.error(
|
|
12482
12802
|
new ParseError({
|
|
12483
12803
|
subCode: "schema-mismatch",
|
|
12484
|
-
message: `task-list: tasks[${String(i)}].blockedBy references unknown task id '${
|
|
12804
|
+
message: `task-list: tasks[${String(i)}].blockedBy references unknown task id '${ref3}'`
|
|
12485
12805
|
})
|
|
12486
12806
|
);
|
|
12487
12807
|
}
|
|
@@ -12534,23 +12854,23 @@ var resolveTicketRef = (t, i, mode) => {
|
|
|
12534
12854
|
})
|
|
12535
12855
|
);
|
|
12536
12856
|
}
|
|
12537
|
-
const
|
|
12538
|
-
const match = mode.tickets.find((tk) => String(tk.id) ===
|
|
12857
|
+
const ref3 = t.ticketRef;
|
|
12858
|
+
const match = mode.tickets.find((tk) => String(tk.id) === ref3);
|
|
12539
12859
|
if (match === void 0) {
|
|
12540
12860
|
return Result.error(
|
|
12541
12861
|
new ParseError({
|
|
12542
12862
|
subCode: "schema-mismatch",
|
|
12543
|
-
message: `task-list: tasks[${String(i)}].ticketRef '${
|
|
12863
|
+
message: `task-list: tasks[${String(i)}].ticketRef '${ref3}' is not an approved ticket on the sprint`,
|
|
12544
12864
|
hint: `available ticket ids: ${mode.tickets.map((tk) => String(tk.id)).join(", ")}`
|
|
12545
12865
|
})
|
|
12546
12866
|
);
|
|
12547
12867
|
}
|
|
12548
|
-
const parsed = TicketId.parse(
|
|
12868
|
+
const parsed = TicketId.parse(ref3);
|
|
12549
12869
|
if (!parsed.ok) {
|
|
12550
12870
|
return Result.error(
|
|
12551
12871
|
new ParseError({
|
|
12552
12872
|
subCode: "schema-mismatch",
|
|
12553
|
-
message: `task-list: tasks[${String(i)}].ticketRef '${
|
|
12873
|
+
message: `task-list: tasks[${String(i)}].ticketRef '${ref3}' is not a valid ticket id format`,
|
|
12554
12874
|
cause: parsed.error
|
|
12555
12875
|
})
|
|
12556
12876
|
);
|
|
@@ -13805,13 +14125,14 @@ var FULL_AUTO = {
|
|
|
13805
14125
|
};
|
|
13806
14126
|
|
|
13807
14127
|
// src/application/flows/implement/leaves/implement-session.ts
|
|
13808
|
-
var implementSession = (sandboxCwd, repoPath, sprintDir2, prompt, model, signalsFile, resume, effort) => ({
|
|
14128
|
+
var implementSession = (sandboxCwd, repoPath, sprintDir2, prompt, model, signalsFile, role, resume, effort) => ({
|
|
13809
14129
|
prompt,
|
|
13810
14130
|
cwd: repoPath,
|
|
13811
14131
|
additionalRoots: [sandboxCwd, sprintDir2],
|
|
13812
14132
|
model,
|
|
13813
14133
|
permissions: FULL_AUTO,
|
|
13814
14134
|
signalsFile,
|
|
14135
|
+
role,
|
|
13815
14136
|
...resume !== void 0 ? { resume } : {},
|
|
13816
14137
|
...effort !== void 0 ? { effort } : {}
|
|
13817
14138
|
});
|
|
@@ -14048,6 +14369,7 @@ var evaluatorLeaf = (deps, taskId) => leaf(`evaluator-${String(taskId)}`, {
|
|
|
14048
14369
|
prompt.value,
|
|
14049
14370
|
deps.model,
|
|
14050
14371
|
signalsFile,
|
|
14372
|
+
"evaluator",
|
|
14051
14373
|
input.priorEvaluatorSessionId,
|
|
14052
14374
|
deps.effort
|
|
14053
14375
|
)
|
|
@@ -14135,52 +14457,187 @@ var evaluatorLeaf = (deps, taskId) => leaf(`evaluator-${String(taskId)}`, {
|
|
|
14135
14457
|
}
|
|
14136
14458
|
});
|
|
14137
14459
|
|
|
14138
|
-
// src/business/task/
|
|
14139
|
-
var
|
|
14140
|
-
|
|
14141
|
-
|
|
14142
|
-
|
|
14143
|
-
|
|
14144
|
-
|
|
14145
|
-
|
|
14146
|
-
|
|
14147
|
-
case "plateau":
|
|
14148
|
-
return { verdict: "failed", warning: { kind: "plateau", dimensions: exit.dimensions } };
|
|
14149
|
-
case "budget-exhausted":
|
|
14150
|
-
return {
|
|
14151
|
-
verdict: "failed",
|
|
14152
|
-
warning: { kind: "budget-exhausted", turnsUsed: exit.turnsUsed, turnBudget: exit.turnBudget }
|
|
14153
|
-
};
|
|
14460
|
+
// src/business/task/escalation-policy.ts
|
|
14461
|
+
var decideEscalation = (props) => {
|
|
14462
|
+
if (!props.flagOn) return { kind: "flag-off" };
|
|
14463
|
+
if (props.task.escalatedFromModel !== void 0 && props.task.escalatedToModel !== void 0) {
|
|
14464
|
+
return {
|
|
14465
|
+
kind: "already-escalated",
|
|
14466
|
+
from: props.task.escalatedFromModel,
|
|
14467
|
+
to: props.task.escalatedToModel
|
|
14468
|
+
};
|
|
14154
14469
|
}
|
|
14155
|
-
|
|
14156
|
-
|
|
14157
|
-
|
|
14158
|
-
|
|
14159
|
-
|
|
14160
|
-
|
|
14161
|
-
} else {
|
|
14162
|
-
const cfg = await props.readConfig();
|
|
14163
|
-
exit = { kind: "budget-exhausted", turnsUsed: props.turnsUsed, turnBudget: Math.max(1, cfg.maxTurns) };
|
|
14470
|
+
if (props.task.maxAttempts !== void 0 && props.task.attempts.length >= props.task.maxAttempts) {
|
|
14471
|
+
return {
|
|
14472
|
+
kind: "budget-exhausted",
|
|
14473
|
+
attemptsUsed: props.task.attempts.length,
|
|
14474
|
+
maxAttempts: props.task.maxAttempts
|
|
14475
|
+
};
|
|
14164
14476
|
}
|
|
14165
|
-
|
|
14166
|
-
const
|
|
14167
|
-
|
|
14477
|
+
const effective = mergeEscalationMap(props.userMap);
|
|
14478
|
+
const to = effective[props.generatorModel];
|
|
14479
|
+
if (to === void 0 || to === props.generatorModel) {
|
|
14480
|
+
return { kind: "no-mapping", currentModel: props.generatorModel };
|
|
14481
|
+
}
|
|
14482
|
+
return { kind: "escalate", from: props.generatorModel, to };
|
|
14483
|
+
};
|
|
14484
|
+
var escalationBannerId = (taskId) => `model-escalation-${taskId}`;
|
|
14485
|
+
var applyEscalation = (props) => {
|
|
14486
|
+
const { task, decision, eventBus, clock } = props;
|
|
14487
|
+
const log = props.logger.named("task.escalation-policy");
|
|
14488
|
+
const bannerId = escalationBannerId(String(task.id));
|
|
14489
|
+
const now = clock();
|
|
14490
|
+
switch (decision.kind) {
|
|
14491
|
+
case "flag-off":
|
|
14492
|
+
return Result.ok({ task });
|
|
14493
|
+
case "escalate": {
|
|
14494
|
+
const stamped = recordTaskEscalation(task, decision.from, decision.to);
|
|
14495
|
+
if (!stamped.ok) return Result.error(stamped.error);
|
|
14496
|
+
eventBus.publish({
|
|
14497
|
+
type: "model-escalated",
|
|
14498
|
+
taskId: String(task.id),
|
|
14499
|
+
attemptN: task.attempts.length,
|
|
14500
|
+
from: decision.from,
|
|
14501
|
+
to: decision.to,
|
|
14502
|
+
reason: "plateau",
|
|
14503
|
+
at: now
|
|
14504
|
+
});
|
|
14505
|
+
eventBus.publish({
|
|
14506
|
+
type: "banner-show",
|
|
14507
|
+
id: bannerId,
|
|
14508
|
+
tier: "info",
|
|
14509
|
+
message: `escalated generator model: ${decision.from} \u2192 ${decision.to}`,
|
|
14510
|
+
cause: "plateau",
|
|
14511
|
+
at: now
|
|
14512
|
+
});
|
|
14513
|
+
log.info(`escalating generator model: ${decision.from} \u2192 ${decision.to}`, {
|
|
14514
|
+
taskId: String(task.id),
|
|
14515
|
+
attemptN: task.attempts.length,
|
|
14516
|
+
from: decision.from,
|
|
14517
|
+
to: decision.to,
|
|
14518
|
+
reason: "plateau"
|
|
14519
|
+
});
|
|
14520
|
+
return Result.ok({ task: stamped.value });
|
|
14521
|
+
}
|
|
14522
|
+
case "already-escalated": {
|
|
14523
|
+
const message = `plateau persists after escalation (${decision.from} \u2192 ${decision.to}); blocking task`;
|
|
14524
|
+
eventBus.publish({
|
|
14525
|
+
type: "banner-show",
|
|
14526
|
+
id: bannerId,
|
|
14527
|
+
tier: "warn",
|
|
14528
|
+
message: "plateau persists after escalation",
|
|
14529
|
+
cause: `${decision.from} \u2192 ${decision.to}`,
|
|
14530
|
+
at: now
|
|
14531
|
+
});
|
|
14532
|
+
log.warn(message, {
|
|
14533
|
+
taskId: String(task.id),
|
|
14534
|
+
from: decision.from,
|
|
14535
|
+
to: decision.to
|
|
14536
|
+
});
|
|
14537
|
+
return Result.ok({ task, blockedReason: message });
|
|
14538
|
+
}
|
|
14539
|
+
case "no-mapping": {
|
|
14540
|
+
const message = `plateau at top of configured escalation ladder for '${decision.currentModel}'`;
|
|
14541
|
+
eventBus.publish({
|
|
14542
|
+
type: "banner-show",
|
|
14543
|
+
id: bannerId,
|
|
14544
|
+
tier: "warn",
|
|
14545
|
+
message,
|
|
14546
|
+
at: now
|
|
14547
|
+
});
|
|
14548
|
+
log.warn(message, { taskId: String(task.id), currentModel: decision.currentModel });
|
|
14549
|
+
return Result.ok({ task, blockedReason: message });
|
|
14550
|
+
}
|
|
14551
|
+
case "budget-exhausted": {
|
|
14552
|
+
const message = `plateau with attempt budget exhausted (attempts=${String(decision.attemptsUsed)}, maxAttempts=${String(decision.maxAttempts)})`;
|
|
14553
|
+
eventBus.publish({
|
|
14554
|
+
type: "banner-show",
|
|
14555
|
+
id: bannerId,
|
|
14556
|
+
tier: "warn",
|
|
14557
|
+
message: "plateau, attempt budget exhausted",
|
|
14558
|
+
cause: `attempts=${String(decision.attemptsUsed)}/${String(decision.maxAttempts)}`,
|
|
14559
|
+
at: now
|
|
14560
|
+
});
|
|
14561
|
+
log.warn(message, {
|
|
14562
|
+
taskId: String(task.id),
|
|
14563
|
+
attemptsUsed: decision.attemptsUsed,
|
|
14564
|
+
maxAttempts: decision.maxAttempts
|
|
14565
|
+
});
|
|
14566
|
+
return Result.ok({ task, blockedReason: message });
|
|
14567
|
+
}
|
|
14568
|
+
}
|
|
14569
|
+
};
|
|
14570
|
+
|
|
14571
|
+
// src/business/task/finalize-gen-eval.ts
|
|
14572
|
+
var mapExit = (exit) => {
|
|
14573
|
+
switch (exit.kind) {
|
|
14574
|
+
case "passed":
|
|
14575
|
+
return { verdict: "passed" };
|
|
14576
|
+
case "self-blocked":
|
|
14577
|
+
return { verdict: "failed", blockedReason: exit.reason };
|
|
14578
|
+
case "malformed":
|
|
14579
|
+
return { verdict: "malformed", warning: { kind: "malformed", detail: exit.detail } };
|
|
14580
|
+
case "plateau":
|
|
14581
|
+
return { verdict: "failed", warning: { kind: "plateau", dimensions: exit.dimensions } };
|
|
14582
|
+
case "budget-exhausted":
|
|
14583
|
+
return {
|
|
14584
|
+
verdict: "failed",
|
|
14585
|
+
warning: { kind: "budget-exhausted", turnsUsed: exit.turnsUsed, turnBudget: exit.turnBudget }
|
|
14586
|
+
};
|
|
14587
|
+
}
|
|
14588
|
+
};
|
|
14589
|
+
var finalizeGenEvalUseCase = async (props) => {
|
|
14590
|
+
const log = props.logger.named("task.finalize-gen-eval");
|
|
14591
|
+
const cfg = await props.readConfig();
|
|
14592
|
+
let exit;
|
|
14593
|
+
if (props.exit !== void 0) {
|
|
14594
|
+
exit = props.exit;
|
|
14595
|
+
} else {
|
|
14596
|
+
exit = { kind: "budget-exhausted", turnsUsed: props.turnsUsed, turnBudget: Math.max(1, cfg.maxTurns) };
|
|
14597
|
+
}
|
|
14598
|
+
log.debug(`finalizing gen-eval (${exit.kind})`, { taskId: props.task.id, exitKind: exit.kind });
|
|
14599
|
+
const mapped = mapExit(exit);
|
|
14600
|
+
let taskForPersist = props.task;
|
|
14601
|
+
let blockedReason = mapped.blockedReason;
|
|
14602
|
+
let shouldFailAttempt = false;
|
|
14603
|
+
if (exit.kind === "plateau") {
|
|
14604
|
+
const decision = decideEscalation({
|
|
14605
|
+
task: props.task,
|
|
14606
|
+
generatorModel: props.generatorModel,
|
|
14607
|
+
flagOn: cfg.escalateOnPlateau,
|
|
14608
|
+
userMap: cfg.escalationMap
|
|
14609
|
+
});
|
|
14610
|
+
const applied = applyEscalation({
|
|
14611
|
+
task: props.task,
|
|
14612
|
+
decision,
|
|
14613
|
+
eventBus: props.eventBus,
|
|
14614
|
+
logger: props.logger,
|
|
14615
|
+
clock: props.clock
|
|
14616
|
+
});
|
|
14617
|
+
if (!applied.ok) return Result.error(applied.error);
|
|
14618
|
+
taskForPersist = applied.value.task;
|
|
14619
|
+
if (applied.value.blockedReason !== void 0) blockedReason = applied.value.blockedReason;
|
|
14620
|
+
if (decision.kind === "escalate") shouldFailAttempt = true;
|
|
14621
|
+
}
|
|
14622
|
+
const persisted = await props.taskRepo.update(props.sprintId, taskForPersist);
|
|
14168
14623
|
if (!persisted.ok) {
|
|
14169
|
-
log.error("persist failed", { taskId:
|
|
14624
|
+
log.error("persist failed", { taskId: taskForPersist.id, error: persisted.error.message });
|
|
14170
14625
|
return Result.error(persisted.error);
|
|
14171
14626
|
}
|
|
14172
14627
|
log.info(`gen-eval finalised \u2192 verdict=${mapped.verdict}`, {
|
|
14173
|
-
taskId:
|
|
14628
|
+
taskId: taskForPersist.id,
|
|
14174
14629
|
exitKind: exit.kind,
|
|
14175
14630
|
verdict: mapped.verdict,
|
|
14176
|
-
...mapped.warning !== void 0 ? { warningKind: mapped.warning.kind } : {}
|
|
14631
|
+
...mapped.warning !== void 0 ? { warningKind: mapped.warning.kind } : {},
|
|
14632
|
+
...blockedReason !== void 0 ? { blockedReason } : {}
|
|
14177
14633
|
});
|
|
14178
14634
|
return Result.ok({
|
|
14179
|
-
task:
|
|
14635
|
+
task: taskForPersist,
|
|
14180
14636
|
exit,
|
|
14181
14637
|
verdict: mapped.verdict,
|
|
14182
14638
|
...mapped.warning !== void 0 ? { warning: mapped.warning } : {},
|
|
14183
|
-
...
|
|
14639
|
+
...blockedReason !== void 0 ? { blockedReason } : {},
|
|
14640
|
+
...shouldFailAttempt ? { shouldFailAttempt: true } : {}
|
|
14184
14641
|
});
|
|
14185
14642
|
};
|
|
14186
14643
|
|
|
@@ -14194,7 +14651,10 @@ var finalizeGenEvalLeaf = (deps, taskId) => leaf(`finalize-gen-eval-${String(tas
|
|
|
14194
14651
|
turnsUsed: input.turnsUsed,
|
|
14195
14652
|
readConfig: deps.readConfig,
|
|
14196
14653
|
taskRepo: deps.taskRepo,
|
|
14197
|
-
logger: deps.logger
|
|
14654
|
+
logger: deps.logger,
|
|
14655
|
+
eventBus: deps.eventBus,
|
|
14656
|
+
clock: deps.clock,
|
|
14657
|
+
generatorModel: input.generatorModel
|
|
14198
14658
|
})
|
|
14199
14659
|
},
|
|
14200
14660
|
input: (ctx) => {
|
|
@@ -14214,11 +14674,13 @@ var finalizeGenEvalLeaf = (deps, taskId) => leaf(`finalize-gen-eval-${String(tas
|
|
|
14214
14674
|
message: `finalize-gen-eval-${String(taskId)}: expected in_progress task`
|
|
14215
14675
|
});
|
|
14216
14676
|
}
|
|
14677
|
+
const generatorModel = ctx.currentTask.escalatedToModel ?? deps.configuredGeneratorModel;
|
|
14217
14678
|
return {
|
|
14218
14679
|
task: ctx.currentTask,
|
|
14219
14680
|
sprintId: ctx.sprintId,
|
|
14220
14681
|
...ctx.lastExit !== void 0 ? { exit: ctx.lastExit } : {},
|
|
14221
|
-
turnsUsed: ctx.genEvalTurn ?? 0
|
|
14682
|
+
turnsUsed: ctx.genEvalTurn ?? 0,
|
|
14683
|
+
generatorModel
|
|
14222
14684
|
};
|
|
14223
14685
|
},
|
|
14224
14686
|
output: (ctx, out) => {
|
|
@@ -14230,7 +14692,8 @@ var finalizeGenEvalLeaf = (deps, taskId) => leaf(`finalize-gen-eval-${String(tas
|
|
|
14230
14692
|
lastExit: out.exit,
|
|
14231
14693
|
lastVerdict: out.verdict,
|
|
14232
14694
|
...out.warning !== void 0 ? { lastWarning: out.warning } : {},
|
|
14233
|
-
...out.blockedReason !== void 0 ? { lastBlockReason: out.blockedReason } : {}
|
|
14695
|
+
...out.blockedReason !== void 0 ? { lastBlockReason: out.blockedReason } : {},
|
|
14696
|
+
...out.shouldFailAttempt === true ? { lastShouldFailAttempt: true } : {}
|
|
14234
14697
|
};
|
|
14235
14698
|
}
|
|
14236
14699
|
});
|
|
@@ -14490,6 +14953,11 @@ var generatorLeaf = (deps, taskId) => leaf(`generator-${String(taskId)}`, {
|
|
|
14490
14953
|
totalCap: deps.maxTurns,
|
|
14491
14954
|
at: deps.clock()
|
|
14492
14955
|
});
|
|
14956
|
+
deps.eventBus.publish({
|
|
14957
|
+
type: "banner-clear",
|
|
14958
|
+
id: escalationBannerId(String(taskId)),
|
|
14959
|
+
at: deps.clock()
|
|
14960
|
+
});
|
|
14493
14961
|
deps.logger.named("task.round-started").info(`round ${String(roundNum)}/${String(deps.maxTurns)} of attempt ${String(input.task.attempts.length)}`, {
|
|
14494
14962
|
taskId: input.task.id,
|
|
14495
14963
|
attemptN: input.task.attempts.length,
|
|
@@ -14518,14 +14986,16 @@ var generatorLeaf = (deps, taskId) => leaf(`generator-${String(taskId)}`, {
|
|
|
14518
14986
|
});
|
|
14519
14987
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
14520
14988
|
await writeRoundPrompt(input.workspaceRoot, roundNum, "generator", String(prompt.value), deps.logger);
|
|
14989
|
+
const effectiveModel = task.escalatedToModel ?? deps.model;
|
|
14521
14990
|
const spawn3 = await deps.provider.generate(
|
|
14522
14991
|
implementSession(
|
|
14523
14992
|
input.workspaceRoot,
|
|
14524
14993
|
deps.cwd,
|
|
14525
14994
|
deps.sprintDir,
|
|
14526
14995
|
prompt.value,
|
|
14527
|
-
|
|
14996
|
+
effectiveModel,
|
|
14528
14997
|
signalsFile,
|
|
14998
|
+
"generator",
|
|
14529
14999
|
input.priorGeneratorSessionId,
|
|
14530
15000
|
deps.effort
|
|
14531
15001
|
)
|
|
@@ -14845,111 +15315,217 @@ var postTaskVerifyLeaf = (deps, opts, taskId) => leaf(`post-task-verify-${String
|
|
|
14845
15315
|
|
|
14846
15316
|
// src/application/flows/implement/leaves/pre-task-verify.ts
|
|
14847
15317
|
import { join as join28 } from "path";
|
|
14848
|
-
var
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
|
|
14853
|
-
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
|
|
14857
|
-
|
|
14858
|
-
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
14868
|
-
|
|
14869
|
-
|
|
14870
|
-
|
|
15318
|
+
var defaultEnvironment = () => ({
|
|
15319
|
+
isStdinTty: process.stdin.isTTY === true,
|
|
15320
|
+
isCi: isTruthyEnv(process.env.CI),
|
|
15321
|
+
isNoTui: isTruthyEnv(process.env.RALPHCTL_NO_TUI)
|
|
15322
|
+
});
|
|
15323
|
+
var isTruthyEnv = (raw) => raw !== void 0 && raw !== "" && raw !== "0";
|
|
15324
|
+
var isInteractive = (env) => env.isStdinTty && !env.isCi && !env.isNoTui;
|
|
15325
|
+
var askRedBaselineDecision = async (interactive, cwd, exitCode) => {
|
|
15326
|
+
const detail = exitCode !== null ? ` (exit=${String(exitCode)})` : "";
|
|
15327
|
+
return interactive.askChoice(
|
|
15328
|
+
`Pre-task verify failed${detail} at ${String(cwd)}. The baseline is already red \u2014 how should the harness proceed?`,
|
|
15329
|
+
[
|
|
15330
|
+
{
|
|
15331
|
+
label: "Proceed anyway \u2014 run the task on the broken baseline",
|
|
15332
|
+
value: "proceed",
|
|
15333
|
+
description: "remembered for the rest of this sprint until the baseline turns green again"
|
|
15334
|
+
},
|
|
15335
|
+
{
|
|
15336
|
+
label: "Skip this task \u2014 mark it blocked, continue with the next task",
|
|
15337
|
+
value: "skip",
|
|
15338
|
+
description: "one-shot; the next task still gets prompted on a red baseline"
|
|
15339
|
+
},
|
|
15340
|
+
{
|
|
15341
|
+
label: "Abort the sprint \u2014 stop the implement run now",
|
|
15342
|
+
value: "abort",
|
|
15343
|
+
description: "fix the baseline, then re-launch implement"
|
|
15344
|
+
}
|
|
15345
|
+
]
|
|
15346
|
+
);
|
|
15347
|
+
};
|
|
15348
|
+
var preTaskVerifyLeaf = (deps, opts, taskId) => {
|
|
15349
|
+
const env = deps.environment ?? defaultEnvironment();
|
|
15350
|
+
return leaf(`pre-task-verify-${String(taskId)}`, {
|
|
15351
|
+
useCase: {
|
|
15352
|
+
execute: async (input) => {
|
|
15353
|
+
const { run, rawOutput, spawnErrorMessage } = await runVerifyScriptUseCase({
|
|
15354
|
+
cwd: opts.cwd,
|
|
15355
|
+
phase: "pre",
|
|
15356
|
+
...opts.verifyScript !== void 0 ? { verifyScript: opts.verifyScript } : {},
|
|
15357
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
15358
|
+
clock: deps.clock,
|
|
15359
|
+
runShellScript: (cwd, script, scriptOpts) => deps.shellScriptRunner.run(cwd, script, scriptOpts),
|
|
15360
|
+
logger: deps.logger
|
|
15361
|
+
});
|
|
15362
|
+
if (opts.sprintDir !== void 0 && rawOutput.length > 0) {
|
|
15363
|
+
const attemptN = input.task.attempts.length;
|
|
15364
|
+
const logPath = join28(
|
|
15365
|
+
String(opts.sprintDir),
|
|
15366
|
+
"logs",
|
|
15367
|
+
"verify",
|
|
15368
|
+
String(input.task.id),
|
|
15369
|
+
`pre-attempt-${String(attemptN)}.log`
|
|
15370
|
+
);
|
|
15371
|
+
const wrote = await writeTextAtomic(logPath, rawOutput);
|
|
15372
|
+
if (!wrote.ok) {
|
|
15373
|
+
deps.eventBus.publish({
|
|
15374
|
+
type: "log",
|
|
15375
|
+
level: "warn",
|
|
15376
|
+
message: `pre-task-verify ${String(opts.cwd)}: failed to persist full log to ${logPath} \u2014 ${wrote.error.message}`,
|
|
15377
|
+
at: deps.clock()
|
|
15378
|
+
});
|
|
15379
|
+
}
|
|
15380
|
+
}
|
|
15381
|
+
let updated = appendAttemptVerifyRun(input.task, run);
|
|
15382
|
+
if (!updated.ok) return Result.error(updated.error);
|
|
15383
|
+
if (run.outcome === "failed") {
|
|
15384
|
+
const flagged = markAttemptBaselineBroken(updated.value);
|
|
15385
|
+
if (!flagged.ok) return Result.error(flagged.error);
|
|
15386
|
+
updated = flagged;
|
|
15387
|
+
}
|
|
15388
|
+
const persisted = await deps.taskRepo.update(input.sprintId, updated.value);
|
|
15389
|
+
if (!persisted.ok) {
|
|
14871
15390
|
deps.eventBus.publish({
|
|
14872
15391
|
type: "log",
|
|
14873
15392
|
level: "warn",
|
|
14874
|
-
message: `pre-task-verify
|
|
15393
|
+
message: `pre-task-verify audit persist failed for task ${String(taskId)} \u2014 ${persisted.error.message}`,
|
|
14875
15394
|
at: deps.clock()
|
|
14876
15395
|
});
|
|
14877
15396
|
}
|
|
15397
|
+
let execution = input.execution;
|
|
15398
|
+
if (run.outcome === "failed") {
|
|
15399
|
+
if (execution.baselineBrokenPolicy === "proceed") {
|
|
15400
|
+
emitBaselineRedLog(deps, opts, run);
|
|
15401
|
+
emitBaselineRedBanner(deps, taskId);
|
|
15402
|
+
return Result.ok({ task: updated.value, run, execution });
|
|
15403
|
+
}
|
|
15404
|
+
if (!isInteractive(env)) {
|
|
15405
|
+
const reason = "baseline already red at task start (non-interactive \u2014 operator could not be prompted)";
|
|
15406
|
+
deps.eventBus.publish({
|
|
15407
|
+
type: "log",
|
|
15408
|
+
level: "warn",
|
|
15409
|
+
message: `pre-task-verify ${String(opts.cwd)}: ${reason}`,
|
|
15410
|
+
at: deps.clock()
|
|
15411
|
+
});
|
|
15412
|
+
return Result.ok({ task: updated.value, run, execution, blockReason: reason });
|
|
15413
|
+
}
|
|
15414
|
+
const decision = await askRedBaselineDecision(deps.interactive, opts.cwd, run.exitCode);
|
|
15415
|
+
if (!decision.ok) return Result.error(decision.error);
|
|
15416
|
+
if (decision.value === "abort") {
|
|
15417
|
+
return Result.error(
|
|
15418
|
+
new AbortError({
|
|
15419
|
+
elementName: `pre-task-verify-${String(taskId)}`,
|
|
15420
|
+
reason: "operator aborted sprint on broken baseline"
|
|
15421
|
+
})
|
|
15422
|
+
);
|
|
15423
|
+
}
|
|
15424
|
+
if (decision.value === "skip") {
|
|
15425
|
+
const reason = "operator skipped task on broken baseline";
|
|
15426
|
+
deps.eventBus.publish({
|
|
15427
|
+
type: "log",
|
|
15428
|
+
level: "warn",
|
|
15429
|
+
message: `pre-task-verify ${String(opts.cwd)}: ${reason}`,
|
|
15430
|
+
at: deps.clock()
|
|
15431
|
+
});
|
|
15432
|
+
return Result.ok({ task: updated.value, run, execution, blockReason: reason });
|
|
15433
|
+
}
|
|
15434
|
+
const nextExecution = setExecutionBaselineBrokenPolicy(execution, "proceed");
|
|
15435
|
+
const saved = await deps.sprintExecutionRepo.save(nextExecution);
|
|
15436
|
+
if (!saved.ok) return Result.error(saved.error);
|
|
15437
|
+
execution = nextExecution;
|
|
15438
|
+
emitBaselineRedLog(deps, opts, run);
|
|
15439
|
+
emitBaselineRedBanner(deps, taskId);
|
|
15440
|
+
return Result.ok({ task: updated.value, run, execution });
|
|
15441
|
+
}
|
|
15442
|
+
if (run.outcome === "spawn-error") {
|
|
15443
|
+
deps.eventBus.publish({
|
|
15444
|
+
type: "log",
|
|
15445
|
+
level: "warn",
|
|
15446
|
+
message: `pre-task-verify ${String(opts.cwd)}: spawn-error \u2014 ${spawnErrorMessage ?? "unknown spawn error"}; attribution will be skipped`,
|
|
15447
|
+
at: deps.clock()
|
|
15448
|
+
});
|
|
15449
|
+
} else {
|
|
15450
|
+
deps.eventBus.publish({
|
|
15451
|
+
type: "banner-clear",
|
|
15452
|
+
id: `baseline-broken-${String(taskId)}`,
|
|
15453
|
+
at: deps.clock()
|
|
15454
|
+
});
|
|
15455
|
+
if (execution.baselineBrokenPolicy === "proceed") {
|
|
15456
|
+
const nextExecution = setExecutionBaselineBrokenPolicy(execution, void 0);
|
|
15457
|
+
const saved = await deps.sprintExecutionRepo.save(nextExecution);
|
|
15458
|
+
if (!saved.ok) return Result.error(saved.error);
|
|
15459
|
+
execution = nextExecution;
|
|
15460
|
+
}
|
|
15461
|
+
}
|
|
15462
|
+
return Result.ok({ task: updated.value, run, execution });
|
|
14878
15463
|
}
|
|
14879
|
-
|
|
14880
|
-
|
|
14881
|
-
if (
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
if (!persisted.ok) {
|
|
14888
|
-
deps.eventBus.publish({
|
|
14889
|
-
type: "log",
|
|
14890
|
-
level: "warn",
|
|
14891
|
-
message: `pre-task-verify audit persist failed for task ${String(taskId)} \u2014 ${persisted.error.message}`,
|
|
14892
|
-
at: deps.clock()
|
|
15464
|
+
},
|
|
15465
|
+
input: (ctx) => {
|
|
15466
|
+
if (ctx.currentTask === void 0 || ctx.currentTask.id !== taskId) {
|
|
15467
|
+
throw new InvalidStateError({
|
|
15468
|
+
entity: "chain",
|
|
15469
|
+
currentState: "pre-pre-task-verify",
|
|
15470
|
+
attemptedAction: `pre-task-verify-${String(taskId)}`,
|
|
15471
|
+
message: `pre-task-verify-${String(taskId)}: ctx.currentTask is missing or mismatched`
|
|
14893
15472
|
});
|
|
14894
15473
|
}
|
|
14895
|
-
if (
|
|
14896
|
-
|
|
14897
|
-
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
});
|
|
14902
|
-
deps.eventBus.publish({
|
|
14903
|
-
type: "banner-show",
|
|
14904
|
-
id: `baseline-broken-${String(taskId)}`,
|
|
14905
|
-
tier: "warn",
|
|
14906
|
-
message: "Pre-task verify baseline is red \u2014 task started on broken state",
|
|
14907
|
-
cause: `task ${String(taskId)}`,
|
|
14908
|
-
at: deps.clock()
|
|
14909
|
-
});
|
|
14910
|
-
} else if (run.outcome === "spawn-error") {
|
|
14911
|
-
deps.eventBus.publish({
|
|
14912
|
-
type: "log",
|
|
14913
|
-
level: "warn",
|
|
14914
|
-
message: `pre-task-verify ${String(opts.cwd)}: spawn-error \u2014 ${spawnErrorMessage ?? "unknown spawn error"}; attribution will be skipped`,
|
|
14915
|
-
at: deps.clock()
|
|
15474
|
+
if (ctx.currentTask.status !== "in_progress") {
|
|
15475
|
+
throw new InvalidStateError({
|
|
15476
|
+
entity: "task",
|
|
15477
|
+
currentState: ctx.currentTask.status,
|
|
15478
|
+
attemptedAction: `pre-task-verify-${String(taskId)}`,
|
|
15479
|
+
message: `pre-task-verify-${String(taskId)}: expected in_progress task \u2014 got '${ctx.currentTask.status}'`
|
|
14916
15480
|
});
|
|
14917
|
-
}
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
15481
|
+
}
|
|
15482
|
+
if (ctx.execution === void 0) {
|
|
15483
|
+
throw new InvalidStateError({
|
|
15484
|
+
entity: "chain",
|
|
15485
|
+
currentState: "pre-pre-task-verify",
|
|
15486
|
+
attemptedAction: `pre-task-verify-${String(taskId)}`,
|
|
15487
|
+
message: `pre-task-verify-${String(taskId)}: ctx.execution is undefined \u2014 load-sprint-execution must run first`
|
|
14922
15488
|
});
|
|
14923
15489
|
}
|
|
14924
|
-
return
|
|
14925
|
-
}
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
});
|
|
15490
|
+
return { task: ctx.currentTask, sprintId: ctx.sprintId, execution: ctx.execution };
|
|
15491
|
+
},
|
|
15492
|
+
output: (ctx, out) => {
|
|
15493
|
+
const next = {
|
|
15494
|
+
...ctx,
|
|
15495
|
+
currentTask: out.task,
|
|
15496
|
+
tasks: (ctx.tasks ?? []).map((t) => t.id === out.task.id ? out.task : t),
|
|
15497
|
+
execution: out.execution,
|
|
15498
|
+
lastPreVerifyOutcome: out.run.outcome
|
|
15499
|
+
};
|
|
15500
|
+
if (out.blockReason !== void 0) {
|
|
15501
|
+
return {
|
|
15502
|
+
...next,
|
|
15503
|
+
lastExit: { kind: "self-blocked", reason: out.blockReason },
|
|
15504
|
+
lastBlockReason: out.blockReason
|
|
15505
|
+
};
|
|
15506
|
+
}
|
|
15507
|
+
return next;
|
|
14943
15508
|
}
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
});
|
|
15509
|
+
});
|
|
15510
|
+
};
|
|
15511
|
+
var emitBaselineRedLog = (deps, opts, run) => {
|
|
15512
|
+
deps.eventBus.publish({
|
|
15513
|
+
type: "log",
|
|
15514
|
+
level: "warn",
|
|
15515
|
+
message: `pre-task-verify ${String(opts.cwd)}: baseline already red (exit=${String(run.exitCode)}) \u2014 task will start on broken baseline`,
|
|
15516
|
+
at: deps.clock()
|
|
15517
|
+
});
|
|
15518
|
+
};
|
|
15519
|
+
var emitBaselineRedBanner = (deps, taskId) => {
|
|
15520
|
+
deps.eventBus.publish({
|
|
15521
|
+
type: "banner-show",
|
|
15522
|
+
id: `baseline-broken-${String(taskId)}`,
|
|
15523
|
+
tier: "warn",
|
|
15524
|
+
message: "Pre-task verify baseline is red \u2014 task started on broken state",
|
|
15525
|
+
cause: `task ${String(taskId)}`,
|
|
15526
|
+
at: deps.clock()
|
|
15527
|
+
});
|
|
15528
|
+
};
|
|
14953
15529
|
|
|
14954
15530
|
// src/business/task/preflight-task.ts
|
|
14955
15531
|
var ELEMENT_NAME = "preflight-task";
|
|
@@ -15257,6 +15833,9 @@ var settleTask = (props, now) => {
|
|
|
15257
15833
|
}
|
|
15258
15834
|
return markTaskBlocked(aborted.value, props.blockedReason);
|
|
15259
15835
|
}
|
|
15836
|
+
if (props.shouldFailAttempt === true) {
|
|
15837
|
+
return failCurrentAttempt(task, now, "failed");
|
|
15838
|
+
}
|
|
15260
15839
|
return markTaskDone(task, now);
|
|
15261
15840
|
};
|
|
15262
15841
|
var settleAttemptUseCase = async (props) => {
|
|
@@ -15267,7 +15846,7 @@ var settleAttemptUseCase = async (props) => {
|
|
|
15267
15846
|
...props.blockedReason !== void 0 ? { blockedReason: props.blockedReason } : {},
|
|
15268
15847
|
...props.warning !== void 0 ? { warning: props.warning.kind } : {}
|
|
15269
15848
|
});
|
|
15270
|
-
if (props.blockedReason === void 0 && props.hasUncommittedChanges !== void 0) {
|
|
15849
|
+
if (props.blockedReason === void 0 && props.shouldFailAttempt !== true && props.hasUncommittedChanges !== void 0) {
|
|
15271
15850
|
const dirty = await props.hasUncommittedChanges();
|
|
15272
15851
|
if (!dirty.ok) {
|
|
15273
15852
|
log.error("settle: worktree status check failed", {
|
|
@@ -15471,7 +16050,8 @@ var settleAttemptLeaf = (deps, opts, taskId) => {
|
|
|
15471
16050
|
...warning !== void 0 ? { warning } : {},
|
|
15472
16051
|
...ctx.taskWorkspaceRoot !== void 0 ? { workspaceRoot: ctx.taskWorkspaceRoot } : {},
|
|
15473
16052
|
...ctx.currentRoundNum !== void 0 ? { roundNum: ctx.currentRoundNum } : {},
|
|
15474
|
-
...ctx.lastEvaluation !== void 0 ? { evaluation: ctx.lastEvaluation } : {}
|
|
16053
|
+
...ctx.lastEvaluation !== void 0 ? { evaluation: ctx.lastEvaluation } : {},
|
|
16054
|
+
...ctx.lastShouldFailAttempt === true ? { shouldFailAttempt: true } : {}
|
|
15475
16055
|
};
|
|
15476
16056
|
},
|
|
15477
16057
|
output: (ctx, settled) => {
|
|
@@ -15487,7 +16067,8 @@ var settleAttemptLeaf = (deps, opts, taskId) => {
|
|
|
15487
16067
|
lastWarning: void 0,
|
|
15488
16068
|
lastVerifyResult: void 0,
|
|
15489
16069
|
lastPreVerifyOutcome: void 0,
|
|
15490
|
-
lastCommitSha: void 0
|
|
16070
|
+
lastCommitSha: void 0,
|
|
16071
|
+
lastShouldFailAttempt: void 0
|
|
15491
16072
|
};
|
|
15492
16073
|
}
|
|
15493
16074
|
});
|
|
@@ -16120,7 +16701,11 @@ var workingTreeCleanCheckLeaf = (deps, cwd, name = "working-tree-clean-check", o
|
|
|
16120
16701
|
// src/application/flows/implement/flow.ts
|
|
16121
16702
|
var IMPLEMENT_TASK_TERMINAL_LEAF = "uninstall-skills";
|
|
16122
16703
|
var createImplementFlow = (deps, opts) => {
|
|
16123
|
-
const readConfig = () => Promise.resolve({
|
|
16704
|
+
const readConfig = () => Promise.resolve({
|
|
16705
|
+
maxTurns: deps.config.harness.maxTurns,
|
|
16706
|
+
escalateOnPlateau: deps.config.harness.escalateOnPlateau,
|
|
16707
|
+
escalationMap: deps.config.harness.escalationMap
|
|
16708
|
+
});
|
|
16124
16709
|
const resolveRepo = (task) => {
|
|
16125
16710
|
const repo = opts.repositories.get(task.repositoryId);
|
|
16126
16711
|
if (repo === void 0) {
|
|
@@ -16148,8 +16733,7 @@ var createImplementFlow = (deps, opts) => {
|
|
|
16148
16733
|
const perTaskSubChain = (task) => {
|
|
16149
16734
|
const taskId = task.id;
|
|
16150
16735
|
const repo = resolveRepo(task);
|
|
16151
|
-
const
|
|
16152
|
-
provider: deps.provider,
|
|
16736
|
+
const sharedLeafDeps = {
|
|
16153
16737
|
templateLoader: deps.templateLoader,
|
|
16154
16738
|
signals: deps.signals,
|
|
16155
16739
|
// Threaded into both gen-eval leaves so harness-owned sidecars (audit-[09]
|
|
@@ -16161,8 +16745,6 @@ var createImplementFlow = (deps, opts) => {
|
|
|
16161
16745
|
// sprint-wide artifacts (`progress.md`) that live outside the per-task sandbox.
|
|
16162
16746
|
sprintDir: opts.sprintDir,
|
|
16163
16747
|
progressFile: opts.progressFile,
|
|
16164
|
-
model: opts.model,
|
|
16165
|
-
...opts.effort !== void 0 ? { effort: opts.effort } : {},
|
|
16166
16748
|
clock: deps.clock,
|
|
16167
16749
|
logger: deps.logger,
|
|
16168
16750
|
eventBus: deps.eventBus,
|
|
@@ -16170,6 +16752,18 @@ var createImplementFlow = (deps, opts) => {
|
|
|
16170
16752
|
plateauThreshold: deps.config.harness.plateauThreshold,
|
|
16171
16753
|
...repo.verifyScript !== void 0 ? { verifyScript: repo.verifyScript } : {}
|
|
16172
16754
|
};
|
|
16755
|
+
const generatorLeafDeps = {
|
|
16756
|
+
...sharedLeafDeps,
|
|
16757
|
+
provider: deps.generatorProvider,
|
|
16758
|
+
model: opts.generatorModel,
|
|
16759
|
+
...opts.generatorEffort !== void 0 ? { effort: opts.generatorEffort } : {}
|
|
16760
|
+
};
|
|
16761
|
+
const evaluatorLeafDeps = {
|
|
16762
|
+
...sharedLeafDeps,
|
|
16763
|
+
provider: deps.evaluatorProvider,
|
|
16764
|
+
model: opts.evaluatorModel,
|
|
16765
|
+
...opts.evaluatorEffort !== void 0 ? { effort: opts.evaluatorEffort } : {}
|
|
16766
|
+
};
|
|
16173
16767
|
return sequential(`task-${String(taskId)}`, [
|
|
16174
16768
|
branchPreflightLeaf(
|
|
16175
16769
|
{ gitRunner: deps.gitRunner, logger: deps.logger },
|
|
@@ -16200,6 +16794,8 @@ var createImplementFlow = (deps, opts) => {
|
|
|
16200
16794
|
{
|
|
16201
16795
|
shellScriptRunner: deps.shellScriptRunner,
|
|
16202
16796
|
taskRepo: deps.taskRepo,
|
|
16797
|
+
sprintExecutionRepo: deps.sprintExecutionRepo,
|
|
16798
|
+
interactive: deps.interactive,
|
|
16203
16799
|
clock: deps.clock,
|
|
16204
16800
|
eventBus: deps.eventBus,
|
|
16205
16801
|
logger: deps.logger
|
|
@@ -16217,11 +16813,11 @@ var createImplementFlow = (deps, opts) => {
|
|
|
16217
16813
|
loop(
|
|
16218
16814
|
`gen-eval-${String(taskId)}`,
|
|
16219
16815
|
sequential(`gen-eval-turn-${String(taskId)}`, [
|
|
16220
|
-
generatorLeaf(
|
|
16816
|
+
generatorLeaf(generatorLeafDeps, taskId),
|
|
16221
16817
|
guard(
|
|
16222
16818
|
`evaluator-guard-${String(taskId)}`,
|
|
16223
16819
|
(ctx) => ctx.lastExit === void 0,
|
|
16224
|
-
evaluatorLeaf(
|
|
16820
|
+
evaluatorLeaf(evaluatorLeafDeps, taskId)
|
|
16225
16821
|
)
|
|
16226
16822
|
]),
|
|
16227
16823
|
{
|
|
@@ -16232,7 +16828,17 @@ var createImplementFlow = (deps, opts) => {
|
|
|
16232
16828
|
shouldStop: (ctx) => ctx.lastExit !== void 0
|
|
16233
16829
|
}
|
|
16234
16830
|
),
|
|
16235
|
-
finalizeGenEvalLeaf(
|
|
16831
|
+
finalizeGenEvalLeaf(
|
|
16832
|
+
{
|
|
16833
|
+
taskRepo: deps.taskRepo,
|
|
16834
|
+
readConfig,
|
|
16835
|
+
logger: deps.logger,
|
|
16836
|
+
eventBus: deps.eventBus,
|
|
16837
|
+
clock: deps.clock,
|
|
16838
|
+
configuredGeneratorModel: opts.generatorModel
|
|
16839
|
+
},
|
|
16840
|
+
taskId
|
|
16841
|
+
),
|
|
16236
16842
|
// Verify gate sits BEFORE commit so a red verifyScript blocks the task instead of landing
|
|
16237
16843
|
// broken code on the sprint branch. On `verify-failed` the leaf stamps `lastBlockReason`,
|
|
16238
16844
|
// the guard around `commit-task` skips, and `settle-attempt` marks the task `blocked`.
|
|
@@ -16393,9 +16999,28 @@ var createImplementFlow = (deps, opts) => {
|
|
|
16393
16999
|
};
|
|
16394
17000
|
|
|
16395
17001
|
// src/application/ui/shared/launch/implement.ts
|
|
17002
|
+
var applyImplementRoleOverrides = (base, overrides) => {
|
|
17003
|
+
if (overrides === void 0) return base;
|
|
17004
|
+
const next = {
|
|
17005
|
+
generator: base.generator,
|
|
17006
|
+
evaluator: base.evaluator
|
|
17007
|
+
};
|
|
17008
|
+
if (overrides.generator !== void 0) {
|
|
17009
|
+
next.generator = { provider: overrides.generator.provider, model: overrides.generator.model };
|
|
17010
|
+
}
|
|
17011
|
+
if (overrides.evaluator !== void 0) {
|
|
17012
|
+
next.evaluator = { provider: overrides.evaluator.provider, model: overrides.evaluator.model };
|
|
17013
|
+
}
|
|
17014
|
+
return next;
|
|
17015
|
+
};
|
|
16396
17016
|
var launchImplement = async (ctx) => {
|
|
16397
|
-
const { deps, snapshot, extras, settings,
|
|
16398
|
-
const
|
|
17017
|
+
const { deps, snapshot, extras, settings, skillsAdapter, skillSource, bridge, sessionId: sessionId2 } = ctx;
|
|
17018
|
+
const implementPair = applyImplementRoleOverrides(settings.ai.implement, extras.implementRoleOverrides);
|
|
17019
|
+
const effectiveSettings = {
|
|
17020
|
+
...settings,
|
|
17021
|
+
ai: { ...settings.ai, implement: implementPair }
|
|
17022
|
+
};
|
|
17023
|
+
const missing = await checkCli("implement", effectiveSettings);
|
|
16399
17024
|
if (missing !== void 0) return missing;
|
|
16400
17025
|
if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
|
|
16401
17026
|
if (!snapshot.project) return { ok: false, reason: "No project loaded for the selected sprint." };
|
|
@@ -16440,18 +17065,31 @@ var launchImplement = async (ctx) => {
|
|
|
16440
17065
|
...r.setupScript !== void 0 ? { setupScript: r.setupScript } : {}
|
|
16441
17066
|
});
|
|
16442
17067
|
}
|
|
17068
|
+
const generatorProvider = createAiProvider({
|
|
17069
|
+
row: implementPair.generator,
|
|
17070
|
+
harnessConfig: effectiveSettings.harness,
|
|
17071
|
+
eventBus: deps.app.eventBus
|
|
17072
|
+
});
|
|
17073
|
+
const evaluatorProvider = createAiProvider({
|
|
17074
|
+
row: implementPair.evaluator,
|
|
17075
|
+
harnessConfig: effectiveSettings.harness,
|
|
17076
|
+
eventBus: deps.app.eventBus
|
|
17077
|
+
});
|
|
17078
|
+
const generatorEffort = resolveEffortForRow(implementPair.generator, effectiveSettings.ai.effort);
|
|
17079
|
+
const evaluatorEffort = resolveEffortForRow(implementPair.evaluator, effectiveSettings.ai.effort);
|
|
16443
17080
|
const element = createImplementFlow(
|
|
16444
17081
|
{
|
|
16445
17082
|
sprintRepo: deps.app.sprintRepo,
|
|
16446
17083
|
sprintExecutionRepo: deps.app.sprintExecutionRepo,
|
|
16447
17084
|
taskRepo: deps.app.taskRepo,
|
|
16448
|
-
|
|
17085
|
+
generatorProvider,
|
|
17086
|
+
evaluatorProvider,
|
|
16449
17087
|
templateLoader: deps.app.templateLoader,
|
|
16450
17088
|
signals,
|
|
16451
17089
|
eventBus: deps.app.eventBus,
|
|
16452
17090
|
logger: deps.app.logger,
|
|
16453
17091
|
clock: deps.app.clock,
|
|
16454
|
-
config:
|
|
17092
|
+
config: effectiveSettings,
|
|
16455
17093
|
gitRunner: deps.app.gitRunner,
|
|
16456
17094
|
shellScriptRunner: deps.app.shellScriptRunner,
|
|
16457
17095
|
fileLocker: deps.app.fileLocker,
|
|
@@ -16468,8 +17106,13 @@ var launchImplement = async (ctx) => {
|
|
|
16468
17106
|
repositories,
|
|
16469
17107
|
progressFile: progressPath.value,
|
|
16470
17108
|
sprintDir: sprintDirPath.value,
|
|
16471
|
-
|
|
16472
|
-
|
|
17109
|
+
// `extras.modelOverride` is a legacy single-model knob from the flows-view picker;
|
|
17110
|
+
// applied to the generator role since that's the one that drove the prior single-model
|
|
17111
|
+
// implement path. Evaluator model stays bound to its settings row.
|
|
17112
|
+
generatorModel: extras.modelOverride ?? implementPair.generator.model,
|
|
17113
|
+
...generatorEffort !== void 0 ? { generatorEffort } : {},
|
|
17114
|
+
evaluatorModel: implementPair.evaluator.model,
|
|
17115
|
+
...evaluatorEffort !== void 0 ? { evaluatorEffort } : {}
|
|
16473
17116
|
}
|
|
16474
17117
|
);
|
|
16475
17118
|
const runner = createRunner({
|
|
@@ -16477,11 +17120,12 @@ var launchImplement = async (ctx) => {
|
|
|
16477
17120
|
element,
|
|
16478
17121
|
initialCtx: { sprintId: snapshot.sprint.id }
|
|
16479
17122
|
});
|
|
16480
|
-
runner.subscribe((evt) => {
|
|
17123
|
+
const unsubRunner = runner.subscribe((evt) => {
|
|
16481
17124
|
if (evt.type === "completed" || evt.type === "failed" || evt.type === "aborted") {
|
|
16482
17125
|
chainLog.stop();
|
|
16483
17126
|
void chainLog.flush();
|
|
16484
17127
|
unsubTaskTracker();
|
|
17128
|
+
unsubRunner();
|
|
16485
17129
|
}
|
|
16486
17130
|
});
|
|
16487
17131
|
const taskNames = new Map(todoTasks.map((t) => [String(t.id), t.name]));
|
|
@@ -16503,6 +17147,8 @@ var launchImplement = async (ctx) => {
|
|
|
16503
17147
|
for (const leaf2 of flattened) {
|
|
16504
17148
|
if (leaf2.label !== void 0 && leaf2.label.length > 0) planLabelByName.set(leaf2.name, leaf2.label);
|
|
16505
17149
|
}
|
|
17150
|
+
const generatorModel = extras.modelOverride ?? implementPair.generator.model;
|
|
17151
|
+
const evaluatorModel = implementPair.evaluator.model;
|
|
16506
17152
|
return {
|
|
16507
17153
|
ok: true,
|
|
16508
17154
|
runner: bridge(runner),
|
|
@@ -16512,7 +17158,9 @@ var launchImplement = async (ctx) => {
|
|
|
16512
17158
|
plannedLeaves,
|
|
16513
17159
|
...planLabelByName.size > 0 ? { planLabelByName } : {},
|
|
16514
17160
|
terminalSubstepName: IMPLEMENT_TASK_TERMINAL_LEAF,
|
|
16515
|
-
...taskRecovering.size > 0 ? { taskRecovering } : {}
|
|
17161
|
+
...taskRecovering.size > 0 ? { taskRecovering } : {},
|
|
17162
|
+
generatorModel,
|
|
17163
|
+
evaluatorModel
|
|
16516
17164
|
};
|
|
16517
17165
|
};
|
|
16518
17166
|
|
|
@@ -17109,9 +17757,9 @@ var launchReview = (ctx) => {
|
|
|
17109
17757
|
fileLocker: deps.app.fileLocker,
|
|
17110
17758
|
locksRoot: deps.storage.locksRoot,
|
|
17111
17759
|
appendFile: deps.app.appendFile,
|
|
17112
|
-
// Review uses the implement model — same code-mutation profile, same
|
|
17113
|
-
// expectations. No per-flow `review` row in settings today.
|
|
17114
|
-
model: extras.modelOverride ?? settings.ai.implement.model
|
|
17760
|
+
// Review uses the implement generator model — same code-mutation profile, same
|
|
17761
|
+
// accuracy expectations. No per-flow `review` row in settings today.
|
|
17762
|
+
model: extras.modelOverride ?? settings.ai.implement.generator.model
|
|
17115
17763
|
},
|
|
17116
17764
|
{
|
|
17117
17765
|
sprintId: snapshot.sprint.id,
|
|
@@ -17529,9 +18177,9 @@ var collectArtefactPaths = (state) => {
|
|
|
17529
18177
|
if (a.settings !== void 0) paths.push(String(a.settings.path));
|
|
17530
18178
|
if (a.settingsLocal !== void 0) paths.push(String(a.settingsLocal.path));
|
|
17531
18179
|
if (a.mcpConfig !== void 0) paths.push(String(a.mcpConfig.path));
|
|
17532
|
-
for (const
|
|
17533
|
-
for (const
|
|
17534
|
-
for (const
|
|
18180
|
+
for (const ref3 of a.skills) paths.push(String(ref3.path));
|
|
18181
|
+
for (const ref3 of a.commands) paths.push(String(ref3.path));
|
|
18182
|
+
for (const ref3 of a.agents) paths.push(String(ref3.path));
|
|
17535
18183
|
} else if (a.tool === "copilot") {
|
|
17536
18184
|
if (a.copilotInstructions !== void 0) paths.push(String(a.copilotInstructions.path));
|
|
17537
18185
|
}
|
|
@@ -17861,24 +18509,30 @@ var FLOW_IDS = ["refine", "plan", "implement", "readiness", "ideate"];
|
|
|
17861
18509
|
var pickRowForProvider = (ai, provider) => {
|
|
17862
18510
|
if (ai.readiness.provider === provider) return "readiness";
|
|
17863
18511
|
for (const flow of FLOW_IDS) {
|
|
17864
|
-
if (ai
|
|
18512
|
+
if (primaryFlowRow(ai, flow).provider === provider) return flow;
|
|
17865
18513
|
}
|
|
17866
18514
|
throw new Error(`pickRowForProvider: provider ${provider} not referenced in ai settings`);
|
|
17867
18515
|
};
|
|
17868
18516
|
var uniqueProvidersFromAi = (ai) => {
|
|
17869
18517
|
const seen = /* @__PURE__ */ new Set();
|
|
17870
18518
|
const ordered = [];
|
|
18519
|
+
const visit = (provider) => {
|
|
18520
|
+
if (seen.has(provider)) return;
|
|
18521
|
+
seen.add(provider);
|
|
18522
|
+
ordered.push(provider);
|
|
18523
|
+
};
|
|
17871
18524
|
for (const flow of FLOW_IDS) {
|
|
17872
|
-
|
|
17873
|
-
|
|
17874
|
-
|
|
17875
|
-
|
|
18525
|
+
if (flow === "implement") {
|
|
18526
|
+
visit(ai.implement.generator.provider);
|
|
18527
|
+
visit(ai.implement.evaluator.provider);
|
|
18528
|
+
continue;
|
|
17876
18529
|
}
|
|
18530
|
+
visit(ai[flow].provider);
|
|
17877
18531
|
}
|
|
17878
18532
|
return ordered;
|
|
17879
18533
|
};
|
|
17880
|
-
var
|
|
17881
|
-
const row = ai
|
|
18534
|
+
var resolveEffortForRow2 = (ai, flow) => {
|
|
18535
|
+
const row = primaryFlowRow(ai, flow);
|
|
17882
18536
|
if (row.effort !== void 0) return row.effort;
|
|
17883
18537
|
const globalEffort = ai.effort;
|
|
17884
18538
|
if (globalEffort === void 0) return void 0;
|
|
@@ -17887,8 +18541,8 @@ var resolveEffortForRow = (ai, flow) => {
|
|
|
17887
18541
|
};
|
|
17888
18542
|
var buildPerToolSubchain = (deps, opts, provider, tool) => {
|
|
17889
18543
|
const rowFlow = pickRowForProvider(opts.ai, provider);
|
|
17890
|
-
const row = opts.ai
|
|
17891
|
-
const effort =
|
|
18544
|
+
const row = primaryFlowRow(opts.ai, rowFlow);
|
|
18545
|
+
const effort = resolveEffortForRow2(opts.ai, rowFlow);
|
|
17892
18546
|
const provideAi = deps.providerFor(provider);
|
|
17893
18547
|
const skillsAdapter = deps.skillsAdapterFor(provider);
|
|
17894
18548
|
return sequential(`tool-${tool}`, [
|
|
@@ -17942,7 +18596,7 @@ var createReadinessFlow = (deps, opts) => {
|
|
|
17942
18596
|
var flowIdForProvider = (settings, provider) => {
|
|
17943
18597
|
if (settings.ai.readiness.provider === provider) return "readiness";
|
|
17944
18598
|
for (const flow of FLOW_IDS) {
|
|
17945
|
-
if (settings.ai
|
|
18599
|
+
if (primaryFlowRow(settings.ai, flow).provider === provider) return flow;
|
|
17946
18600
|
}
|
|
17947
18601
|
throw new Error(`flowIdForProvider: provider ${provider} not referenced in ai settings`);
|
|
17948
18602
|
};
|
|
@@ -19677,12 +20331,14 @@ var sessionHintsFromLaunchResult = (result) => ({
|
|
|
19677
20331
|
...result.plannedLeaves !== void 0 ? { plannedLeaves: result.plannedLeaves } : {},
|
|
19678
20332
|
...result.planLabelByName !== void 0 ? { planLabelByName: result.planLabelByName } : {},
|
|
19679
20333
|
...result.terminalSubstepName !== void 0 ? { terminalSubstepName: result.terminalSubstepName } : {},
|
|
19680
|
-
...result.taskRecovering !== void 0 ? { taskRecovering: result.taskRecovering } : {}
|
|
20334
|
+
...result.taskRecovering !== void 0 ? { taskRecovering: result.taskRecovering } : {},
|
|
20335
|
+
...result.generatorModel !== void 0 ? { generatorModel: result.generatorModel } : {},
|
|
20336
|
+
...result.evaluatorModel !== void 0 ? { evaluatorModel: result.evaluatorModel } : {}
|
|
19681
20337
|
});
|
|
19682
20338
|
var modelsForFlowProvider = (flowId, settings) => {
|
|
19683
20339
|
const aiFlow = aiFlowIdFor(flowId);
|
|
19684
20340
|
if (aiFlow === void 0) return [];
|
|
19685
|
-
switch (settings.ai
|
|
20341
|
+
switch (primaryFlowRow(settings.ai, aiFlow).provider) {
|
|
19686
20342
|
case "claude-code":
|
|
19687
20343
|
return CLAUDE_MODELS;
|
|
19688
20344
|
case "github-copilot":
|
|
@@ -19694,7 +20350,7 @@ var modelsForFlowProvider = (flowId, settings) => {
|
|
|
19694
20350
|
var modelForFlow = (flowId, settings) => {
|
|
19695
20351
|
const aiFlow = aiFlowIdFor(flowId);
|
|
19696
20352
|
if (aiFlow === void 0) return void 0;
|
|
19697
|
-
return settings.ai
|
|
20353
|
+
return primaryFlowRow(settings.ai, aiFlow).model;
|
|
19698
20354
|
};
|
|
19699
20355
|
var aiFlowIdFor = (flowId) => {
|
|
19700
20356
|
switch (flowId) {
|
|
@@ -19738,7 +20394,7 @@ var launchFlow = async (deps, flowId, snapshot, extras = {}) => {
|
|
|
19738
20394
|
eventBus: deps.app.eventBus
|
|
19739
20395
|
});
|
|
19740
20396
|
const skillsAdapter = createSkillsAdapter({
|
|
19741
|
-
provider: settings.ai
|
|
20397
|
+
provider: primaryFlowRow(settings.ai, adapterFlow).provider,
|
|
19742
20398
|
logger: deps.app.logger
|
|
19743
20399
|
});
|
|
19744
20400
|
const effort = aiFlow !== void 0 ? resolveEffort(aiFlow, settings) : void 0;
|
|
@@ -19804,16 +20460,19 @@ var launchSprintBoundFlow = async (deps, flowId, snapshot, extras = {}) => {
|
|
|
19804
20460
|
return result;
|
|
19805
20461
|
};
|
|
19806
20462
|
var attachReseatSubscriber = (runner, fallbackLabel, onReseat) => {
|
|
19807
|
-
runner.subscribe((event) => {
|
|
20463
|
+
const unsub = runner.subscribe((event) => {
|
|
20464
|
+
if (event.type === "failed" || event.type === "aborted") {
|
|
20465
|
+
unsub();
|
|
20466
|
+
return;
|
|
20467
|
+
}
|
|
19808
20468
|
if (event.type !== "completed") return;
|
|
19809
20469
|
const ctx = event.ctx;
|
|
19810
20470
|
if (ctx.sprint !== void 0) {
|
|
19811
20471
|
onReseat({ id: ctx.sprint.id, name: ctx.sprint.name });
|
|
19812
|
-
|
|
19813
|
-
}
|
|
19814
|
-
if (ctx.sprintId !== void 0) {
|
|
20472
|
+
} else if (ctx.sprintId !== void 0) {
|
|
19815
20473
|
onReseat({ id: ctx.sprintId, name: fallbackLabel ?? String(ctx.sprintId) });
|
|
19816
20474
|
}
|
|
20475
|
+
unsub();
|
|
19817
20476
|
});
|
|
19818
20477
|
};
|
|
19819
20478
|
|
|
@@ -19843,7 +20502,7 @@ var HomeView = () => {
|
|
|
19843
20502
|
const currentSprint = snapshot?.sprint;
|
|
19844
20503
|
const recentSprints = snapshot?.recentSprints ?? [];
|
|
19845
20504
|
const [localError, setLocalError] = useState23(void 0);
|
|
19846
|
-
const errorTimerRef =
|
|
20505
|
+
const errorTimerRef = useRef8(void 0);
|
|
19847
20506
|
const flashErr = useCallback8((text) => {
|
|
19848
20507
|
setLocalError(text);
|
|
19849
20508
|
if (errorTimerRef.current !== void 0) clearTimeout(errorTimerRef.current);
|
|
@@ -19852,14 +20511,14 @@ var HomeView = () => {
|
|
|
19852
20511
|
errorTimerRef.current = void 0;
|
|
19853
20512
|
}, SWITCH_FEEDBACK_MS);
|
|
19854
20513
|
}, []);
|
|
19855
|
-
|
|
20514
|
+
useEffect15(
|
|
19856
20515
|
() => () => {
|
|
19857
20516
|
if (errorTimerRef.current !== void 0) clearTimeout(errorTimerRef.current);
|
|
19858
20517
|
},
|
|
19859
20518
|
[]
|
|
19860
20519
|
);
|
|
19861
20520
|
const lastSwitch = selection.lastSwitch;
|
|
19862
|
-
|
|
20521
|
+
useEffect15(() => {
|
|
19863
20522
|
if (lastSwitch === void 0) return void 0;
|
|
19864
20523
|
const elapsed = Date.now() - lastSwitch.at;
|
|
19865
20524
|
const remaining = SWITCH_FEEDBACK_MS - elapsed;
|
|
@@ -20639,6 +21298,7 @@ var FlowsView = () => {
|
|
|
20639
21298
|
modelOverride = picked.value;
|
|
20640
21299
|
}
|
|
20641
21300
|
}
|
|
21301
|
+
const implementRoleOverrides = entry.manifest.id === "implement" ? getImplementRoleOverrides() : void 0;
|
|
20642
21302
|
const result = await launchFlow(
|
|
20643
21303
|
{ app: deps, interactive, storage: storage2, runInTerminal: getRunInTerminal() },
|
|
20644
21304
|
entry.manifest.id,
|
|
@@ -20646,6 +21306,7 @@ var FlowsView = () => {
|
|
|
20646
21306
|
{
|
|
20647
21307
|
...ui.sessionRepositoryId !== void 0 ? { repositoryId: ui.sessionRepositoryId } : {},
|
|
20648
21308
|
...modelOverride !== void 0 ? { modelOverride } : {},
|
|
21309
|
+
...implementRoleOverrides !== void 0 ? { implementRoleOverrides } : {},
|
|
20649
21310
|
settingsSnapshot: settings
|
|
20650
21311
|
}
|
|
20651
21312
|
);
|
|
@@ -20653,10 +21314,15 @@ var FlowsView = () => {
|
|
|
20653
21314
|
setLaunchError(`${entry.manifest.title}: ${result.reason}`);
|
|
20654
21315
|
return;
|
|
20655
21316
|
}
|
|
20656
|
-
result.runner.subscribe((event) => {
|
|
21317
|
+
const unsubRepoCapture = result.runner.subscribe((event) => {
|
|
21318
|
+
if (event.type === "failed" || event.type === "aborted") {
|
|
21319
|
+
unsubRepoCapture();
|
|
21320
|
+
return;
|
|
21321
|
+
}
|
|
20657
21322
|
if (event.type !== "completed") return;
|
|
20658
21323
|
const ctx = event.ctx;
|
|
20659
21324
|
if (ctx.repository !== void 0) ui.setSessionRepositoryId(ctx.repository.id);
|
|
21325
|
+
unsubRepoCapture();
|
|
20660
21326
|
});
|
|
20661
21327
|
sessions.register({
|
|
20662
21328
|
runner: result.runner,
|
|
@@ -20717,11 +21383,11 @@ var FlowsView = () => {
|
|
|
20717
21383
|
};
|
|
20718
21384
|
|
|
20719
21385
|
// src/application/ui/tui/views/projects-view.tsx
|
|
20720
|
-
import { useEffect as
|
|
21386
|
+
import { useEffect as useEffect18, useState as useState27 } from "react";
|
|
20721
21387
|
import { Box as Box28, Text as Text30, useInput as useInput15 } from "ink";
|
|
20722
21388
|
|
|
20723
21389
|
// src/application/ui/tui/components/card-list.tsx
|
|
20724
|
-
import { useEffect as
|
|
21390
|
+
import { useEffect as useEffect16, useMemo as useMemo10, useState as useState25 } from "react";
|
|
20725
21391
|
import { Box as Box26, Text as Text28, useInput as useInput14 } from "ink";
|
|
20726
21392
|
import { jsx as jsx38, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
20727
21393
|
var clamp3 = (n, min, max) => Math.max(min, Math.min(max, n));
|
|
@@ -20737,10 +21403,10 @@ function CardList({
|
|
|
20737
21403
|
footer
|
|
20738
21404
|
}) {
|
|
20739
21405
|
const [cursor, setCursor] = useState25(() => clamp3(initialIndex, 0, Math.max(0, items.length - 1)));
|
|
20740
|
-
|
|
21406
|
+
useEffect16(() => {
|
|
20741
21407
|
if (cursor >= items.length) setCursor(Math.max(0, items.length - 1));
|
|
20742
21408
|
}, [items.length, cursor]);
|
|
20743
|
-
|
|
21409
|
+
useEffect16(() => {
|
|
20744
21410
|
if (items.length === 0) return;
|
|
20745
21411
|
const item = items[cursor];
|
|
20746
21412
|
if (item !== void 0) onCursor?.(item, cursor);
|
|
@@ -20824,7 +21490,7 @@ var EmptyState = ({ title, hint, action }) => /* @__PURE__ */ jsxs28(
|
|
|
20824
21490
|
);
|
|
20825
21491
|
|
|
20826
21492
|
// src/application/ui/tui/runtime/use-edit-field.ts
|
|
20827
|
-
import { useCallback as useCallback9, useEffect as
|
|
21493
|
+
import { useCallback as useCallback9, useEffect as useEffect17, useRef as useRef9, useState as useState26 } from "react";
|
|
20828
21494
|
var enqueueText = (queue, title, kind, initial) => new Promise((resolve, reject) => {
|
|
20829
21495
|
const base = { message: title, initial, resolve, reject };
|
|
20830
21496
|
const prompt = kind === "short" ? { kind: "text", ...base } : { kind: "textarea", ...base };
|
|
@@ -20834,8 +21500,8 @@ var useEditField = () => {
|
|
|
20834
21500
|
const queue = usePromptQueue();
|
|
20835
21501
|
const ui = useUiState();
|
|
20836
21502
|
const [feedback, setFeedbackState] = useState26(void 0);
|
|
20837
|
-
const mountedRef =
|
|
20838
|
-
|
|
21503
|
+
const mountedRef = useRef9(true);
|
|
21504
|
+
useEffect17(
|
|
20839
21505
|
() => () => {
|
|
20840
21506
|
mountedRef.current = false;
|
|
20841
21507
|
},
|
|
@@ -20937,7 +21603,7 @@ var ProjectsView = () => {
|
|
|
20937
21603
|
reload();
|
|
20938
21604
|
}
|
|
20939
21605
|
});
|
|
20940
|
-
|
|
21606
|
+
useEffect18(() => confirmDelete !== void 0 ? ui.claimPrompt() : void 0, [confirmDelete, ui.claimPrompt]);
|
|
20941
21607
|
const handleDeleteConfirmed = async (target, confirmed) => {
|
|
20942
21608
|
setConfirmDelete(void 0);
|
|
20943
21609
|
if (!confirmed) return;
|
|
@@ -21036,7 +21702,7 @@ var ProjectsView = () => {
|
|
|
21036
21702
|
};
|
|
21037
21703
|
|
|
21038
21704
|
// src/application/ui/tui/views/project-detail-view.tsx
|
|
21039
|
-
import React44, { useEffect as
|
|
21705
|
+
import React44, { useEffect as useEffect19, useState as useState28 } from "react";
|
|
21040
21706
|
import { Box as Box30, Text as Text32, useInput as useInput16 } from "ink";
|
|
21041
21707
|
|
|
21042
21708
|
// src/application/ui/tui/components/field-list.tsx
|
|
@@ -21222,7 +21888,7 @@ var ProjectDetailView = () => {
|
|
|
21222
21888
|
if (target !== void 0) void launchPerRepoFlow("detect-skills", target);
|
|
21223
21889
|
}
|
|
21224
21890
|
});
|
|
21225
|
-
|
|
21891
|
+
useEffect19(() => confirmRemove !== void 0 ? ui.claimPrompt() : void 0, [confirmRemove, ui.claimPrompt]);
|
|
21226
21892
|
const handleRemoveConfirmed = async (target, confirmed) => {
|
|
21227
21893
|
setConfirmRemove(void 0);
|
|
21228
21894
|
if (!confirmed || project === void 0) return;
|
|
@@ -21347,7 +22013,7 @@ var Body = ({ project, cursorIdx, feedback }) => /* @__PURE__ */ jsxs31(Box30, {
|
|
|
21347
22013
|
] });
|
|
21348
22014
|
|
|
21349
22015
|
// src/application/ui/tui/views/sprints-view.tsx
|
|
21350
|
-
import { useEffect as
|
|
22016
|
+
import { useEffect as useEffect20, useState as useState29 } from "react";
|
|
21351
22017
|
import { Box as Box31, Text as Text33, useInput as useInput17 } from "ink";
|
|
21352
22018
|
|
|
21353
22019
|
// src/business/task/unblock-task.ts
|
|
@@ -21406,7 +22072,7 @@ var SprintsView = () => {
|
|
|
21406
22072
|
const [feedback, setFeedback] = useState29(void 0);
|
|
21407
22073
|
const [focusedSprintTasks, setFocusedSprintTasks] = useState29([]);
|
|
21408
22074
|
const focusedSprint = items.find((s) => s.id === cursorId) ?? items[0];
|
|
21409
|
-
|
|
22075
|
+
useEffect20(() => {
|
|
21410
22076
|
if (focusedSprint === void 0) {
|
|
21411
22077
|
setFocusedSprintTasks([]);
|
|
21412
22078
|
return void 0;
|
|
@@ -21432,7 +22098,7 @@ var SprintsView = () => {
|
|
|
21432
22098
|
{ keys: "r", label: "reload" },
|
|
21433
22099
|
...stuckCount > 0 ? [{ keys: "u", label: `unblock (${String(stuckCount)})` }] : []
|
|
21434
22100
|
]);
|
|
21435
|
-
|
|
22101
|
+
useEffect20(() => confirmDelete !== void 0 ? ui.claimPrompt() : void 0, [confirmDelete, ui.claimPrompt]);
|
|
21436
22102
|
const launchCreateSprint2 = async () => {
|
|
21437
22103
|
if (selection.projectId === void 0) {
|
|
21438
22104
|
setFeedback("\u2717 pick a project first (Projects \u2192 open one)");
|
|
@@ -21650,7 +22316,7 @@ var SprintsView = () => {
|
|
|
21650
22316
|
};
|
|
21651
22317
|
|
|
21652
22318
|
// src/application/ui/tui/views/sprint-detail-view.tsx
|
|
21653
|
-
import React46, { useEffect as
|
|
22319
|
+
import React46, { useEffect as useEffect21, useMemo as useMemo11, useState as useState30 } from "react";
|
|
21654
22320
|
import { Box as Box32, Text as Text34, useInput as useInput18 } from "ink";
|
|
21655
22321
|
|
|
21656
22322
|
// src/application/flows/ticket-remove/flow.ts
|
|
@@ -21766,21 +22432,19 @@ var SprintDetailView = () => {
|
|
|
21766
22432
|
const focusedTicket = focusedNow?.kind === "ticket" && ticketsEditable ? focusedNow.ticket : void 0;
|
|
21767
22433
|
const focusedTodoTask = focusedNow?.kind === "task" && focusedNow.task.status === "todo" ? focusedNow.task : void 0;
|
|
21768
22434
|
const canEdit = focusedTicket !== void 0 || focusedTodoTask !== void 0;
|
|
21769
|
-
useViewHints(
|
|
21770
|
-
|
|
21771
|
-
|
|
21772
|
-
|
|
21773
|
-
|
|
21774
|
-
|
|
21775
|
-
|
|
21776
|
-
|
|
21777
|
-
|
|
21778
|
-
|
|
21779
|
-
|
|
21780
|
-
|
|
21781
|
-
|
|
21782
|
-
]
|
|
21783
|
-
);
|
|
22435
|
+
useViewHints([
|
|
22436
|
+
{ keys: "n", label: "flows" },
|
|
22437
|
+
{ keys: "\u21B5/o", label: inDetail ? "expand/collapse" : "expand" },
|
|
22438
|
+
{ keys: "a", label: "add ticket" },
|
|
22439
|
+
...canEdit ? [{ keys: "e", label: "edit field" }] : [],
|
|
22440
|
+
{ keys: "d", label: "remove ticket" },
|
|
22441
|
+
// Surface the `m` chord only when this sprint is not already the current one — once
|
|
22442
|
+
// they match, the action is a no-op and the hint adds noise. Suppressed while a
|
|
22443
|
+
// stuck task is focused so the `u unblock` hint (a more urgent operator action)
|
|
22444
|
+
// stays prominent in the footer without competing for horizontal space.
|
|
22445
|
+
...sprint !== void 0 && selection.sprintId !== sprint.id && focusedStuckTask === void 0 ? [{ keys: "m", label: "current" }] : [],
|
|
22446
|
+
...focusedStuckTask !== void 0 ? [{ keys: "u", label: "unblock" }] : []
|
|
22447
|
+
]);
|
|
21784
22448
|
const buildTicketEdit = (ticket, field) => {
|
|
21785
22449
|
if (sprint === void 0) return void 0;
|
|
21786
22450
|
if (field === "requirements" && ticket.status !== "approved") {
|
|
@@ -21858,8 +22522,8 @@ var SprintDetailView = () => {
|
|
|
21858
22522
|
};
|
|
21859
22523
|
useInput18((input, key) => {
|
|
21860
22524
|
if (ui.helpOpen || ui.promptActive || confirmRemove !== void 0 || sprint === void 0) return;
|
|
21861
|
-
if (inDetail) {
|
|
21862
|
-
|
|
22525
|
+
if ((key.escape || input === "q") && inDetail) {
|
|
22526
|
+
setOpenIdx(void 0);
|
|
21863
22527
|
return;
|
|
21864
22528
|
}
|
|
21865
22529
|
if (input === "a" && ticketsEditable) {
|
|
@@ -21886,7 +22550,8 @@ var SprintDetailView = () => {
|
|
|
21886
22550
|
return;
|
|
21887
22551
|
}
|
|
21888
22552
|
if ((key.return || input === "o") && focusList.length > 0) {
|
|
21889
|
-
|
|
22553
|
+
const target = Math.min(cursorIdx, focusList.length - 1);
|
|
22554
|
+
setOpenIdx((prev) => prev === target ? void 0 : target);
|
|
21890
22555
|
return;
|
|
21891
22556
|
}
|
|
21892
22557
|
if (input === "d" && ticketsEditable) {
|
|
@@ -21898,8 +22563,8 @@ var SprintDetailView = () => {
|
|
|
21898
22563
|
void handleUnblock(focusedStuckTask);
|
|
21899
22564
|
}
|
|
21900
22565
|
});
|
|
21901
|
-
|
|
21902
|
-
|
|
22566
|
+
useEffect21(() => confirmRemove !== void 0 ? ui.claimPrompt() : void 0, [confirmRemove, ui.claimPrompt]);
|
|
22567
|
+
useEffect21(() => inDetail ? ui.claimEscape() : void 0, [inDetail, ui.claimEscape]);
|
|
21903
22568
|
const handleRemoveConfirmed = async (target, confirmed) => {
|
|
21904
22569
|
setConfirmRemove(void 0);
|
|
21905
22570
|
if (!confirmed || sprint === void 0) return;
|
|
@@ -21968,19 +22633,7 @@ var Body2 = ({
|
|
|
21968
22633
|
}) => {
|
|
21969
22634
|
const { sprint, tasks } = bundle;
|
|
21970
22635
|
const action = phaseAction(sprint, tasks);
|
|
21971
|
-
|
|
21972
|
-
const focused = focusList[openIdx];
|
|
21973
|
-
return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
|
|
21974
|
-
/* @__PURE__ */ jsx44(SprintHeader, { sprint, tasks, isCurrent }),
|
|
21975
|
-
focused !== void 0 && /* @__PURE__ */ jsx44(Box32, { marginTop: spacing.section, children: focused.kind === "ticket" ? /* @__PURE__ */ jsx44(TicketDetail, { ticket: focused.ticket, tasks }) : /* @__PURE__ */ jsx44(TaskDetail, { task: focused.task, sprint, tasks, project }) }),
|
|
21976
|
-
/* @__PURE__ */ jsx44(Box32, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsxs33(Text34, { dimColor: true, children: [
|
|
21977
|
-
glyphs.bullet,
|
|
21978
|
-
" esc back to list ",
|
|
21979
|
-
glyphs.bullet,
|
|
21980
|
-
" \u2191/\u2193 scroll"
|
|
21981
|
-
] }) })
|
|
21982
|
-
] });
|
|
21983
|
-
}
|
|
22636
|
+
const expandedFocusItem = openIdx !== void 0 ? focusList[openIdx] : void 0;
|
|
21984
22637
|
return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
|
|
21985
22638
|
/* @__PURE__ */ jsx44(SprintHeader, { sprint, tasks, isCurrent }),
|
|
21986
22639
|
action !== void 0 && (sprint.status === "done" ? /* @__PURE__ */ jsx44(Box32, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsxs33(Text34, { dimColor: true, children: [
|
|
@@ -22003,15 +22656,26 @@ var Body2 = ({
|
|
|
22003
22656
|
focusList,
|
|
22004
22657
|
cursorIdx,
|
|
22005
22658
|
ticketsEditable,
|
|
22006
|
-
feedback
|
|
22659
|
+
feedback,
|
|
22660
|
+
expandedFocusItem
|
|
22661
|
+
}
|
|
22662
|
+
),
|
|
22663
|
+
/* @__PURE__ */ jsx44(
|
|
22664
|
+
TasksSection,
|
|
22665
|
+
{
|
|
22666
|
+
sprint,
|
|
22667
|
+
tasks,
|
|
22668
|
+
focusList,
|
|
22669
|
+
cursorIdx,
|
|
22670
|
+
project,
|
|
22671
|
+
expandedFocusItem
|
|
22007
22672
|
}
|
|
22008
22673
|
),
|
|
22009
|
-
/* @__PURE__ */ jsx44(TasksSection, { sprint, tasks, focusList, cursorIdx, project }),
|
|
22010
22674
|
/* @__PURE__ */ jsx44(Box32, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsxs33(Text34, { dimColor: true, children: [
|
|
22011
22675
|
glyphs.bullet,
|
|
22012
22676
|
" \u2191/\u2193 focus ",
|
|
22013
22677
|
glyphs.bullet,
|
|
22014
|
-
" \u21B5/o
|
|
22678
|
+
" \u21B5/o expand/collapse ",
|
|
22015
22679
|
glyphs.bullet,
|
|
22016
22680
|
" n flows ",
|
|
22017
22681
|
glyphs.bullet,
|
|
@@ -22160,7 +22824,8 @@ var TicketsSection = ({
|
|
|
22160
22824
|
focusList,
|
|
22161
22825
|
cursorIdx,
|
|
22162
22826
|
ticketsEditable,
|
|
22163
|
-
feedback
|
|
22827
|
+
feedback,
|
|
22828
|
+
expandedFocusItem
|
|
22164
22829
|
}) => /* @__PURE__ */ jsxs33(Box32, { marginTop: spacing.section, flexDirection: "column", children: [
|
|
22165
22830
|
/* @__PURE__ */ jsxs33(Text34, { bold: true, children: [
|
|
22166
22831
|
glyphs.badge,
|
|
@@ -22174,16 +22839,30 @@ var TicketsSection = ({
|
|
|
22174
22839
|
}
|
|
22175
22840
|
) }) : /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", marginTop: 1, children: sprint.tickets.map((ticket, idx) => {
|
|
22176
22841
|
const focused = focusList[cursorIdx]?.kind === "ticket" && focusList[cursorIdx]?.ticket.id === ticket.id;
|
|
22842
|
+
const expanded = expandedFocusItem?.kind === "ticket" && expandedFocusItem.ticket.id === ticket.id;
|
|
22177
22843
|
const taskCount = tasks.filter((t) => t.ticketId === ticket.id).length;
|
|
22178
|
-
return /* @__PURE__ */ jsx44(
|
|
22844
|
+
return /* @__PURE__ */ jsx44(
|
|
22845
|
+
TicketCard,
|
|
22846
|
+
{
|
|
22847
|
+
ticket,
|
|
22848
|
+
tasks,
|
|
22849
|
+
taskCount,
|
|
22850
|
+
focused,
|
|
22851
|
+
expanded,
|
|
22852
|
+
index: idx
|
|
22853
|
+
},
|
|
22854
|
+
ticket.id
|
|
22855
|
+
);
|
|
22179
22856
|
}) }),
|
|
22180
|
-
/* @__PURE__ */ jsx44(Box32, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsx44(Text34, { dimColor: true, children: ticketsEditable ? `${glyphs.bullet} a add ${glyphs.bullet} \u21B5/o
|
|
22857
|
+
/* @__PURE__ */ jsx44(Box32, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsx44(Text34, { dimColor: true, children: ticketsEditable ? `${glyphs.bullet} a add ${glyphs.bullet} \u21B5/o expand/collapse ${glyphs.bullet} d remove` : `${glyphs.bullet} tickets frozen (sprint not in draft) ${glyphs.bullet} \u21B5/o expand/collapse` }) }),
|
|
22181
22858
|
feedback !== void 0 && /* @__PURE__ */ jsx44(Box32, { paddingX: spacing.indent, marginTop: 1, children: /* @__PURE__ */ jsx44(Text34, { color: feedback.startsWith("\u2717") ? inkColors.error : inkColors.primary, children: feedback }) })
|
|
22182
22859
|
] });
|
|
22183
22860
|
var TicketCard = ({
|
|
22184
22861
|
ticket,
|
|
22862
|
+
tasks,
|
|
22185
22863
|
taskCount,
|
|
22186
22864
|
focused,
|
|
22865
|
+
expanded,
|
|
22187
22866
|
index
|
|
22188
22867
|
}) => /* @__PURE__ */ jsx44(Box32, { marginBottom: 1, children: /* @__PURE__ */ jsx44(
|
|
22189
22868
|
Card,
|
|
@@ -22222,11 +22901,19 @@ var TicketCard = ({
|
|
|
22222
22901
|
" requirements \u2713"
|
|
22223
22902
|
] })
|
|
22224
22903
|
] }),
|
|
22225
|
-
ticket.description !== void 0 && /* @__PURE__ */ jsx44(Description, { text: ticket.description, maxLines: 2 })
|
|
22904
|
+
!expanded && ticket.description !== void 0 && /* @__PURE__ */ jsx44(Description, { text: ticket.description, maxLines: 2 }),
|
|
22905
|
+
expanded && /* @__PURE__ */ jsx44(TicketDetailBody, { ticket, tasks })
|
|
22226
22906
|
] })
|
|
22227
22907
|
}
|
|
22228
22908
|
) });
|
|
22229
|
-
var TasksSection = ({
|
|
22909
|
+
var TasksSection = ({
|
|
22910
|
+
sprint,
|
|
22911
|
+
tasks,
|
|
22912
|
+
focusList,
|
|
22913
|
+
cursorIdx,
|
|
22914
|
+
project,
|
|
22915
|
+
expandedFocusItem
|
|
22916
|
+
}) => /* @__PURE__ */ jsxs33(Box32, { marginTop: spacing.section, flexDirection: "column", children: [
|
|
22230
22917
|
/* @__PURE__ */ jsxs33(Text34, { bold: true, children: [
|
|
22231
22918
|
glyphs.badge,
|
|
22232
22919
|
" Tasks"
|
|
@@ -22234,26 +22921,39 @@ var TasksSection = ({ sprint, tasks, focusList, cursorIdx, project }) => /* @__P
|
|
|
22234
22921
|
tasks.length === 0 ? /* @__PURE__ */ jsx44(Box32, { marginTop: 1, children: /* @__PURE__ */ jsx44(EmptyState, { title: "No tasks yet", hint: "Run plan from Flows (n) once tickets are approved." }) }) : /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", marginTop: 1, children: tasks.map((task, idx) => {
|
|
22235
22922
|
const focusItem = focusList[cursorIdx];
|
|
22236
22923
|
const focused = focusItem?.kind === "task" && focusItem.task.id === task.id;
|
|
22924
|
+
const expanded = expandedFocusItem?.kind === "task" && expandedFocusItem.task.id === task.id;
|
|
22237
22925
|
const ticket = sprint.tickets.find((t) => t.id === task.ticketId);
|
|
22238
22926
|
const repoName = repositoryName(project, task.repositoryId);
|
|
22239
22927
|
return /* @__PURE__ */ jsx44(
|
|
22240
22928
|
TaskCard,
|
|
22241
22929
|
{
|
|
22242
22930
|
task,
|
|
22931
|
+
sprint,
|
|
22932
|
+
tasks,
|
|
22933
|
+
project,
|
|
22243
22934
|
ticketTitle: ticket?.title,
|
|
22244
22935
|
repoName,
|
|
22245
22936
|
focused,
|
|
22937
|
+
expanded,
|
|
22246
22938
|
index: idx + 1
|
|
22247
22939
|
},
|
|
22248
22940
|
task.id
|
|
22249
22941
|
);
|
|
22250
|
-
}) })
|
|
22942
|
+
}) }),
|
|
22943
|
+
/* @__PURE__ */ jsx44(Box32, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsxs33(Text34, { dimColor: true, children: [
|
|
22944
|
+
glyphs.bullet,
|
|
22945
|
+
" \u21B5/o expand/collapse"
|
|
22946
|
+
] }) })
|
|
22251
22947
|
] });
|
|
22252
22948
|
var TaskCard = ({
|
|
22253
22949
|
task,
|
|
22950
|
+
sprint,
|
|
22951
|
+
tasks,
|
|
22952
|
+
project,
|
|
22254
22953
|
ticketTitle,
|
|
22255
22954
|
repoName,
|
|
22256
22955
|
focused,
|
|
22956
|
+
expanded,
|
|
22257
22957
|
index
|
|
22258
22958
|
}) => {
|
|
22259
22959
|
const lastAttempt2 = task.attempts[task.attempts.length - 1];
|
|
@@ -22309,12 +23009,13 @@ var TaskCard = ({
|
|
|
22309
23009
|
fmtDuration(lastAttemptElapsed)
|
|
22310
23010
|
] })
|
|
22311
23011
|
] }),
|
|
22312
|
-
task.description !== void 0 && /* @__PURE__ */ jsx44(Description, { text: task.description, maxLines: 2 }),
|
|
22313
|
-
task.status === "blocked" && /* @__PURE__ */ jsx44(Box32, { paddingLeft: 2, children: /* @__PURE__ */ jsxs33(Text34, { color: inkColors.error, children: [
|
|
23012
|
+
!expanded && task.description !== void 0 && /* @__PURE__ */ jsx44(Description, { text: task.description, maxLines: 2 }),
|
|
23013
|
+
!expanded && task.status === "blocked" && /* @__PURE__ */ jsx44(Box32, { paddingLeft: 2, children: /* @__PURE__ */ jsxs33(Text34, { color: inkColors.error, children: [
|
|
22314
23014
|
glyphs.cross,
|
|
22315
23015
|
" blocked: ",
|
|
22316
23016
|
task.blockedReason
|
|
22317
|
-
] }) })
|
|
23017
|
+
] }) }),
|
|
23018
|
+
expanded && /* @__PURE__ */ jsx44(TaskDetailBody, { task, sprint, tasks, project })
|
|
22318
23019
|
] })
|
|
22319
23020
|
}
|
|
22320
23021
|
) });
|
|
@@ -22330,43 +23031,24 @@ var attemptElapsedMs = (attempt) => {
|
|
|
22330
23031
|
const started = Date.parse(attempt.startedAt);
|
|
22331
23032
|
return Number.isFinite(finished) && Number.isFinite(started) ? finished - started : void 0;
|
|
22332
23033
|
};
|
|
22333
|
-
var
|
|
23034
|
+
var TicketDetailBody = ({
|
|
22334
23035
|
ticket,
|
|
22335
23036
|
tasks
|
|
22336
23037
|
}) => {
|
|
22337
23038
|
const referencedTasks = tasks.filter((t) => t.ticketId === ticket.id);
|
|
22338
|
-
return /* @__PURE__ */
|
|
22339
|
-
|
|
22340
|
-
{
|
|
22341
|
-
|
|
22342
|
-
|
|
22343
|
-
|
|
22344
|
-
|
|
22345
|
-
|
|
22346
|
-
FieldList,
|
|
22347
|
-
{
|
|
22348
|
-
fields: [
|
|
22349
|
-
{ label: "Title", value: ticket.title },
|
|
22350
|
-
{ label: "Status", value: ticket.status },
|
|
22351
|
-
...ticket.link !== void 0 ? [{ label: "Link", value: String(ticket.link) }] : [],
|
|
22352
|
-
{ label: "Tasks", value: String(referencedTasks.length) }
|
|
22353
|
-
]
|
|
22354
|
-
}
|
|
22355
|
-
),
|
|
22356
|
-
ticket.description !== void 0 && /* @__PURE__ */ jsx44(Section, { heading: "Description", children: /* @__PURE__ */ jsx44(Description, { text: ticket.description, maxLines: Number.POSITIVE_INFINITY }) }),
|
|
22357
|
-
ticket.status === "approved" && /* @__PURE__ */ jsx44(Section, { heading: "Requirements", children: /* @__PURE__ */ jsx44(Description, { text: ticket.requirements, maxLines: Number.POSITIVE_INFINITY }) }),
|
|
22358
|
-
referencedTasks.length > 0 && /* @__PURE__ */ jsx44(Section, { heading: "Referenced tasks", children: /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", paddingLeft: 2, children: referencedTasks.map((t) => /* @__PURE__ */ jsxs33(Box32, { children: [
|
|
22359
|
-
/* @__PURE__ */ jsx44(StatusChip, { label: t.status, kind: taskStatusKind(t.status) }),
|
|
22360
|
-
/* @__PURE__ */ jsxs33(Text34, { bold: true, children: [
|
|
22361
|
-
" ",
|
|
22362
|
-
t.name
|
|
22363
|
-
] })
|
|
22364
|
-
] }, t.id)) }) })
|
|
23039
|
+
return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
|
|
23040
|
+
ticket.description !== void 0 && /* @__PURE__ */ jsx44(Section, { heading: "Description", children: /* @__PURE__ */ jsx44(Description, { text: ticket.description, maxLines: Number.POSITIVE_INFINITY }) }),
|
|
23041
|
+
ticket.status === "approved" && /* @__PURE__ */ jsx44(Section, { heading: "Requirements", children: /* @__PURE__ */ jsx44(Description, { text: ticket.requirements, maxLines: Number.POSITIVE_INFINITY }) }),
|
|
23042
|
+
referencedTasks.length > 0 && /* @__PURE__ */ jsx44(Section, { heading: "Referenced tasks", children: /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", paddingLeft: 2, children: referencedTasks.map((t) => /* @__PURE__ */ jsxs33(Box32, { children: [
|
|
23043
|
+
/* @__PURE__ */ jsx44(StatusChip, { label: t.status, kind: taskStatusKind(t.status) }),
|
|
23044
|
+
/* @__PURE__ */ jsxs33(Text34, { bold: true, children: [
|
|
23045
|
+
" ",
|
|
23046
|
+
t.name
|
|
22365
23047
|
] })
|
|
22366
|
-
}
|
|
22367
|
-
);
|
|
23048
|
+
] }, t.id)) }) })
|
|
23049
|
+
] });
|
|
22368
23050
|
};
|
|
22369
|
-
var
|
|
23051
|
+
var TaskDetailBody = ({
|
|
22370
23052
|
task,
|
|
22371
23053
|
sprint,
|
|
22372
23054
|
tasks,
|
|
@@ -22375,69 +23057,55 @@ var TaskDetail = ({
|
|
|
22375
23057
|
const ticket = sprint.tickets.find((t) => t.id === task.ticketId);
|
|
22376
23058
|
const dependsOnTasks = task.dependsOn.map((id) => tasks.find((t) => t.id === id)).filter((t) => t !== void 0);
|
|
22377
23059
|
const repoName = repositoryName(project, task.repositoryId);
|
|
22378
|
-
return /* @__PURE__ */
|
|
22379
|
-
|
|
22380
|
-
|
|
22381
|
-
|
|
22382
|
-
|
|
22383
|
-
|
|
22384
|
-
children: /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", paddingX: spacing.indent, children: [
|
|
22385
|
-
/* @__PURE__ */ jsx44(
|
|
22386
|
-
FieldList,
|
|
23060
|
+
return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
|
|
23061
|
+
/* @__PURE__ */ jsx44(
|
|
23062
|
+
FieldList,
|
|
23063
|
+
{
|
|
23064
|
+
fields: [
|
|
23065
|
+
{ label: "Order", value: String(task.order) },
|
|
22387
23066
|
{
|
|
22388
|
-
|
|
22389
|
-
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
|
|
22394
|
-
|
|
22395
|
-
|
|
22396
|
-
|
|
22397
|
-
|
|
22398
|
-
|
|
22399
|
-
|
|
22400
|
-
|
|
22401
|
-
|
|
22402
|
-
|
|
22403
|
-
|
|
22404
|
-
|
|
22405
|
-
|
|
22406
|
-
|
|
22407
|
-
|
|
22408
|
-
|
|
22409
|
-
|
|
22410
|
-
|
|
22411
|
-
|
|
22412
|
-
|
|
22413
|
-
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22417
|
-
|
|
22418
|
-
|
|
22419
|
-
|
|
22420
|
-
|
|
22421
|
-
|
|
22422
|
-
|
|
22423
|
-
|
|
22424
|
-
|
|
22425
|
-
|
|
22426
|
-
c.check === "auto" && c.command !== void 0 ? ` \`${c.command}\`` : "",
|
|
22427
|
-
" \u2014 ",
|
|
22428
|
-
c.assertion
|
|
22429
|
-
] }, `vc-${String(i)}`)) }) }),
|
|
22430
|
-
dependsOnTasks.length > 0 && /* @__PURE__ */ jsx44(Section, { heading: "Depends on", children: /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", paddingLeft: 2, children: dependsOnTasks.map((d) => /* @__PURE__ */ jsxs33(Box32, { children: [
|
|
22431
|
-
/* @__PURE__ */ jsx44(StatusChip, { label: d.status, kind: taskStatusKind(d.status) }),
|
|
22432
|
-
/* @__PURE__ */ jsxs33(Text34, { bold: true, children: [
|
|
22433
|
-
" ",
|
|
22434
|
-
d.name
|
|
22435
|
-
] })
|
|
22436
|
-
] }, d.id)) }) }),
|
|
22437
|
-
task.attempts.length > 0 && /* @__PURE__ */ jsx44(Section, { heading: "Attempt history", children: /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", paddingLeft: 2, children: task.attempts.map((attempt) => /* @__PURE__ */ jsx44(AttemptCard, { attempt }, `attempt-${String(attempt.n)}`)) }) })
|
|
23067
|
+
label: "Repository",
|
|
23068
|
+
value: repoName !== void 0 ? `${repoName} (${String(task.repositoryId)})` : String(task.repositoryId)
|
|
23069
|
+
},
|
|
23070
|
+
{
|
|
23071
|
+
label: "Ticket",
|
|
23072
|
+
value: ticket !== void 0 ? `${ticket.title} [${ticket.status}]` : String(task.ticketId)
|
|
23073
|
+
},
|
|
23074
|
+
...task.status === "done" ? [{ label: "Final attempt", value: `#${String(task.finalAttemptN)}` }] : [],
|
|
23075
|
+
...task.extraDimensions !== void 0 && task.extraDimensions.length > 0 ? [{ label: "Extra dims", value: task.extraDimensions.join(", ") }] : []
|
|
23076
|
+
]
|
|
23077
|
+
}
|
|
23078
|
+
),
|
|
23079
|
+
task.status === "blocked" && /* @__PURE__ */ jsx44(Box32, { marginTop: 1, children: /* @__PURE__ */ jsxs33(Text34, { color: inkColors.error, children: [
|
|
23080
|
+
glyphs.cross,
|
|
23081
|
+
" blocked: ",
|
|
23082
|
+
task.blockedReason
|
|
23083
|
+
] }) }),
|
|
23084
|
+
task.description !== void 0 && /* @__PURE__ */ jsx44(Section, { heading: "Description", children: /* @__PURE__ */ jsx44(Description, { text: task.description, maxLines: Number.POSITIVE_INFINITY }) }),
|
|
23085
|
+
task.steps.length > 0 && /* @__PURE__ */ jsx44(Section, { heading: "Steps", children: /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", paddingLeft: 2, children: task.steps.map((s, i) => /* @__PURE__ */ jsxs33(Text34, { dimColor: true, children: [
|
|
23086
|
+
String(i + 1),
|
|
23087
|
+
". ",
|
|
23088
|
+
s
|
|
23089
|
+
] }, `step-${String(i)}`)) }) }),
|
|
23090
|
+
task.verificationCriteria.length > 0 && /* @__PURE__ */ jsx44(Section, { heading: "Verification", children: /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", paddingLeft: 2, children: task.verificationCriteria.map((c, i) => /* @__PURE__ */ jsxs33(Text34, { dimColor: true, children: [
|
|
23091
|
+
glyphs.bullet,
|
|
23092
|
+
" [",
|
|
23093
|
+
c.id,
|
|
23094
|
+
"] ",
|
|
23095
|
+
c.check,
|
|
23096
|
+
c.check === "auto" && c.command !== void 0 ? ` \`${c.command}\`` : "",
|
|
23097
|
+
" \u2014 ",
|
|
23098
|
+
c.assertion
|
|
23099
|
+
] }, `vc-${String(i)}`)) }) }),
|
|
23100
|
+
dependsOnTasks.length > 0 && /* @__PURE__ */ jsx44(Section, { heading: "Depends on", children: /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", paddingLeft: 2, children: dependsOnTasks.map((d) => /* @__PURE__ */ jsxs33(Box32, { children: [
|
|
23101
|
+
/* @__PURE__ */ jsx44(StatusChip, { label: d.status, kind: taskStatusKind(d.status) }),
|
|
23102
|
+
/* @__PURE__ */ jsxs33(Text34, { bold: true, children: [
|
|
23103
|
+
" ",
|
|
23104
|
+
d.name
|
|
22438
23105
|
] })
|
|
22439
|
-
}
|
|
22440
|
-
|
|
23106
|
+
] }, d.id)) }) }),
|
|
23107
|
+
task.attempts.length > 0 && /* @__PURE__ */ jsx44(Section, { heading: "Attempt history", children: /* @__PURE__ */ jsx44(Box32, { flexDirection: "column", paddingLeft: 2, children: task.attempts.map((attempt) => /* @__PURE__ */ jsx44(AttemptCard, { attempt }, `attempt-${String(attempt.n)}`)) }) })
|
|
23108
|
+
] });
|
|
22441
23109
|
};
|
|
22442
23110
|
var AttemptCard = ({ attempt }) => {
|
|
22443
23111
|
const elapsedMs = attemptElapsedMs(attempt);
|
|
@@ -22575,7 +23243,7 @@ var Description = ({
|
|
|
22575
23243
|
};
|
|
22576
23244
|
|
|
22577
23245
|
// src/application/ui/tui/views/execute-view.tsx
|
|
22578
|
-
import React55, { useEffect as
|
|
23246
|
+
import React55, { useEffect as useEffect27, useMemo as useMemo14 } from "react";
|
|
22579
23247
|
import { Box as Box41, Text as Text43, useInput as useInput20 } from "ink";
|
|
22580
23248
|
|
|
22581
23249
|
// src/application/ui/tui/components/step-trace.tsx
|
|
@@ -23220,7 +23888,7 @@ var MultiFlowStrip = ({
|
|
|
23220
23888
|
};
|
|
23221
23889
|
|
|
23222
23890
|
// src/application/ui/tui/runtime/use-token-usage.ts
|
|
23223
|
-
import { useEffect as
|
|
23891
|
+
import { useEffect as useEffect22, useState as useState31 } from "react";
|
|
23224
23892
|
var TOKEN_USAGE_SESSION_CAP = 100;
|
|
23225
23893
|
var isTokenUsage = (e) => e.type === "token-usage";
|
|
23226
23894
|
var toUsage = (e) => ({
|
|
@@ -23234,7 +23902,7 @@ var toUsage = (e) => ({
|
|
|
23234
23902
|
});
|
|
23235
23903
|
var useTokenUsage = (bus) => {
|
|
23236
23904
|
const [usage, setUsage] = useState31(() => /* @__PURE__ */ new Map());
|
|
23237
|
-
|
|
23905
|
+
useEffect22(() => {
|
|
23238
23906
|
const unsub = bus.subscribe((event) => {
|
|
23239
23907
|
if (!isTokenUsage(event)) return;
|
|
23240
23908
|
setUsage((prev) => {
|
|
@@ -23255,12 +23923,12 @@ var useTokenUsage = (bus) => {
|
|
|
23255
23923
|
};
|
|
23256
23924
|
|
|
23257
23925
|
// src/application/ui/tui/runtime/use-sink-stream.ts
|
|
23258
|
-
import { useEffect as
|
|
23926
|
+
import { useEffect as useEffect23, useState as useState32 } from "react";
|
|
23259
23927
|
var useSinkStream = (bus, opts = {}) => {
|
|
23260
23928
|
const limit = opts.limit ?? 100;
|
|
23261
23929
|
const replay = opts.replay !== false;
|
|
23262
23930
|
const [items, setItems] = useState32(() => replay ? bus.entries.slice(-limit) : []);
|
|
23263
|
-
|
|
23931
|
+
useEffect23(() => {
|
|
23264
23932
|
if (replay) setItems(bus.entries.slice(-limit));
|
|
23265
23933
|
const unsub = bus.subscribe((value) => {
|
|
23266
23934
|
setItems((prev) => {
|
|
@@ -23274,13 +23942,13 @@ var useSinkStream = (bus, opts = {}) => {
|
|
|
23274
23942
|
};
|
|
23275
23943
|
|
|
23276
23944
|
// src/application/ui/tui/runtime/use-event-bus.ts
|
|
23277
|
-
import { useEffect as
|
|
23945
|
+
import { useEffect as useEffect24, useRef as useRef10, useState as useState33 } from "react";
|
|
23278
23946
|
var useEventBusBuffer = (bus, opts) => {
|
|
23279
23947
|
const limit = opts.limit ?? 100;
|
|
23280
23948
|
const [items, setItems] = useState33([]);
|
|
23281
|
-
const filterRef =
|
|
23949
|
+
const filterRef = useRef10(opts.filter);
|
|
23282
23950
|
filterRef.current = opts.filter;
|
|
23283
|
-
|
|
23951
|
+
useEffect24(() => {
|
|
23284
23952
|
const unsub = bus.subscribe((event) => {
|
|
23285
23953
|
if (!filterRef.current(event)) return;
|
|
23286
23954
|
setItems((prev) => {
|
|
@@ -23294,11 +23962,11 @@ var useEventBusBuffer = (bus, opts) => {
|
|
|
23294
23962
|
};
|
|
23295
23963
|
|
|
23296
23964
|
// src/application/ui/tui/runtime/use-task-round-tracker.ts
|
|
23297
|
-
import { useEffect as
|
|
23965
|
+
import { useEffect as useEffect25, useState as useState34 } from "react";
|
|
23298
23966
|
var isTaskRoundStarted = (e) => e.type === "task-round-started";
|
|
23299
23967
|
var useTaskRoundTracker = (bus) => {
|
|
23300
23968
|
const [rounds, setRounds] = useState34(() => /* @__PURE__ */ new Map());
|
|
23301
|
-
|
|
23969
|
+
useEffect25(() => {
|
|
23302
23970
|
const unsub = bus.subscribe((event) => {
|
|
23303
23971
|
if (!isTaskRoundStarted(event)) return;
|
|
23304
23972
|
setRounds((prev) => {
|
|
@@ -23531,7 +24199,7 @@ var renderActiveTaskSummary = ({ task, displayName }) => {
|
|
|
23531
24199
|
};
|
|
23532
24200
|
|
|
23533
24201
|
// src/application/ui/tui/components/cancel-scope-overlay.tsx
|
|
23534
|
-
import { useEffect as
|
|
24202
|
+
import { useEffect as useEffect26 } from "react";
|
|
23535
24203
|
import { Box as Box40, Text as Text42, useInput as useInput19 } from "ink";
|
|
23536
24204
|
import { jsx as jsx52, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
23537
24205
|
var CancelScopeOverlay = ({
|
|
@@ -23554,7 +24222,7 @@ var CancelScopeOverlay = ({
|
|
|
23554
24222
|
onDismiss();
|
|
23555
24223
|
}
|
|
23556
24224
|
});
|
|
23557
|
-
|
|
24225
|
+
useEffect26(() => void 0, []);
|
|
23558
24226
|
const wasted = attemptElapsedMs2 !== void 0 ? fmtDuration(attemptElapsedMs2) : void 0;
|
|
23559
24227
|
const remainingHint = remainingTaskCount > 1 ? `${String(remainingTaskCount - 1)} other task${remainingTaskCount - 1 === 1 ? "" : "s"} still queued` : "no other tasks queued";
|
|
23560
24228
|
return /* @__PURE__ */ jsx52(Box40, { flexDirection: "column", paddingX: spacing.indent, paddingY: 0, marginTop: spacing.section, children: /* @__PURE__ */ jsxs41(
|
|
@@ -23722,7 +24390,7 @@ var ExecuteView = () => {
|
|
|
23722
24390
|
if (input === "D") router.reset();
|
|
23723
24391
|
});
|
|
23724
24392
|
const [now, setNow] = React55.useState(() => Date.now());
|
|
23725
|
-
|
|
24393
|
+
useEffect27(() => {
|
|
23726
24394
|
if (!isRunning) return void 0;
|
|
23727
24395
|
const id = setInterval(() => {
|
|
23728
24396
|
setNow(Date.now());
|
|
@@ -23779,7 +24447,7 @@ var ExecuteView = () => {
|
|
|
23779
24447
|
const currentTask = currentTaskIdx >= 0 ? bucketed?.tasks[currentTaskIdx] : void 0;
|
|
23780
24448
|
const currentTaskName = currentTask !== void 0 ? descriptor.taskNames?.get(currentTask.id) ?? `${currentTask.id.slice(0, 8)}${glyphs.clipEllipsis}` : void 0;
|
|
23781
24449
|
const currentSubStep = currentTask?.subSteps[currentTask.subSteps.length - 1]?.leafName;
|
|
23782
|
-
|
|
24450
|
+
useEffect27(() => {
|
|
23783
24451
|
if (currentTask === void 0 || currentTaskName === void 0) {
|
|
23784
24452
|
ui.setActiveTaskSummaryProvider(void 0);
|
|
23785
24453
|
return void 0;
|
|
@@ -23834,6 +24502,7 @@ var ExecuteView = () => {
|
|
|
23834
24502
|
const onDismissCancelScope = React55.useCallback(() => {
|
|
23835
24503
|
setCancelScopeOpen(false);
|
|
23836
24504
|
}, []);
|
|
24505
|
+
const modelLine = descriptor.generatorModel !== void 0 && descriptor.evaluatorModel !== void 0 ? descriptor.generatorModel === descriptor.evaluatorModel ? descriptor.generatorModel : `${descriptor.generatorModel} ${glyphs.arrowRight} ${descriptor.evaluatorModel} (eval)` : void 0;
|
|
23837
24506
|
const headerCard = /* @__PURE__ */ jsx53(Card, { title: descriptor.title, tone: isRunning ? "info" : descriptor.status === "completed" ? "success" : "rule", children: /* @__PURE__ */ jsxs42(Box41, { flexDirection: "column", children: [
|
|
23838
24507
|
/* @__PURE__ */ jsxs42(Box41, { children: [
|
|
23839
24508
|
/* @__PURE__ */ jsx53(Text43, { dimColor: true, children: "flow " }),
|
|
@@ -23862,6 +24531,13 @@ var ExecuteView = () => {
|
|
|
23862
24531
|
] }),
|
|
23863
24532
|
isRunning && /* @__PURE__ */ jsx53(Box41, { marginLeft: 2, children: /* @__PURE__ */ jsx53(Spinner, { active: isRunning, color: inkColors.info, label: "live" }) })
|
|
23864
24533
|
] }),
|
|
24534
|
+
modelLine !== void 0 && /* @__PURE__ */ jsxs42(Box41, { children: [
|
|
24535
|
+
/* @__PURE__ */ jsxs42(Text43, { dimColor: true, children: [
|
|
24536
|
+
glyphs.activityArrow,
|
|
24537
|
+
" model "
|
|
24538
|
+
] }),
|
|
24539
|
+
/* @__PURE__ */ jsx53(Text43, { color: inkColors.highlight, children: modelLine })
|
|
24540
|
+
] }),
|
|
23865
24541
|
currentTask !== void 0 && currentTaskName !== void 0 && /* @__PURE__ */ jsxs42(Box41, { children: [
|
|
23866
24542
|
/* @__PURE__ */ jsxs42(Text43, { dimColor: true, children: [
|
|
23867
24543
|
glyphs.activityArrow,
|
|
@@ -24062,11 +24738,11 @@ var ExecuteView = () => {
|
|
|
24062
24738
|
};
|
|
24063
24739
|
|
|
24064
24740
|
// src/application/ui/tui/views/sessions-view.tsx
|
|
24065
|
-
import { useEffect as
|
|
24741
|
+
import { useEffect as useEffect29, useState as useState36 } from "react";
|
|
24066
24742
|
import { Box as Box43, Text as Text45, useInput as useInput22 } from "ink";
|
|
24067
24743
|
|
|
24068
24744
|
// src/application/ui/tui/components/list-view.tsx
|
|
24069
|
-
import { useEffect as
|
|
24745
|
+
import { useEffect as useEffect28, useMemo as useMemo15, useState as useState35 } from "react";
|
|
24070
24746
|
import { Box as Box42, Text as Text44, useInput as useInput21 } from "ink";
|
|
24071
24747
|
import { jsx as jsx54, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
24072
24748
|
var clamp4 = (n, min, max) => Math.max(min, Math.min(max, n));
|
|
@@ -24081,10 +24757,10 @@ function ListView({
|
|
|
24081
24757
|
initialIndex = 0
|
|
24082
24758
|
}) {
|
|
24083
24759
|
const [cursor, setCursor] = useState35(() => clamp4(initialIndex, 0, Math.max(0, items.length - 1)));
|
|
24084
|
-
|
|
24760
|
+
useEffect28(() => {
|
|
24085
24761
|
if (cursor >= items.length) setCursor(Math.max(0, items.length - 1));
|
|
24086
24762
|
}, [items.length, cursor]);
|
|
24087
|
-
|
|
24763
|
+
useEffect28(() => {
|
|
24088
24764
|
if (items.length === 0) return;
|
|
24089
24765
|
const item = items[cursor];
|
|
24090
24766
|
if (item !== void 0) onCursor?.(item, cursor);
|
|
@@ -24163,7 +24839,7 @@ var SessionsView = () => {
|
|
|
24163
24839
|
const [cursorId, setCursorId] = useState36(void 0);
|
|
24164
24840
|
const [confirmCancel, setConfirmCancel] = useState36(void 0);
|
|
24165
24841
|
const [feedback, setFeedback] = useState36(void 0);
|
|
24166
|
-
|
|
24842
|
+
useEffect29(() => confirmCancel !== void 0 ? ui.claimPrompt() : void 0, [confirmCancel, ui.claimPrompt]);
|
|
24167
24843
|
useInput22((input) => {
|
|
24168
24844
|
if (ui.helpOpen || ui.promptActive || confirmCancel !== void 0) return;
|
|
24169
24845
|
if (input === "c") {
|
|
@@ -24253,7 +24929,7 @@ var SessionsView = () => {
|
|
|
24253
24929
|
};
|
|
24254
24930
|
|
|
24255
24931
|
// src/application/ui/tui/views/settings-view.tsx
|
|
24256
|
-
import React58, { useEffect as
|
|
24932
|
+
import React58, { useEffect as useEffect30, useMemo as useMemo16, useState as useState37 } from "react";
|
|
24257
24933
|
import { Box as Box44, Text as Text46, useInput as useInput23 } from "ink";
|
|
24258
24934
|
|
|
24259
24935
|
// src/application/flows/settings-show/flow.ts
|
|
@@ -24286,8 +24962,38 @@ var createSettingsSetProviderFlow = (deps) => leaf("settings-set-provider", {
|
|
|
24286
24962
|
async execute(input) {
|
|
24287
24963
|
const current = await deps.settingsRepo.load();
|
|
24288
24964
|
if (!current.ok) return Result.error(current.error);
|
|
24289
|
-
const
|
|
24290
|
-
const
|
|
24965
|
+
const detect = deps.detectInstalledProviders ?? detectInstalledProviders;
|
|
24966
|
+
const installed = await detect();
|
|
24967
|
+
if (!installed.has(input.provider)) {
|
|
24968
|
+
const settingsKey = input.flow === "implement" ? `ai.implement.${input.role ?? "generator"}.provider` : `ai.${input.flow}.provider`;
|
|
24969
|
+
return Result.error(
|
|
24970
|
+
new ValidationError({
|
|
24971
|
+
field: settingsKey,
|
|
24972
|
+
value: input.provider,
|
|
24973
|
+
message: `${input.provider} CLI (${PROVIDER_BINARY2[input.provider]}) not on PATH \u2014 cannot set ${settingsKey}`,
|
|
24974
|
+
hint: renderProviderInstallGuidance(input.provider)
|
|
24975
|
+
})
|
|
24976
|
+
);
|
|
24977
|
+
}
|
|
24978
|
+
const defaultsForProvider = defaultAiSettingsForProvider(input.provider);
|
|
24979
|
+
let nextAi;
|
|
24980
|
+
if (input.flow === "implement") {
|
|
24981
|
+
if (input.role === void 0) {
|
|
24982
|
+
return Result.error(
|
|
24983
|
+
new ValidationError({
|
|
24984
|
+
field: "role",
|
|
24985
|
+
value: "undefined",
|
|
24986
|
+
message: "implement requires an explicit role (generator | evaluator) for a provider switch"
|
|
24987
|
+
})
|
|
24988
|
+
);
|
|
24989
|
+
}
|
|
24990
|
+
const rebuiltRoleRow = defaultsForProvider.implement[input.role];
|
|
24991
|
+
const nextImplement = { ...current.value.ai.implement, [input.role]: rebuiltRoleRow };
|
|
24992
|
+
nextAi = { ...current.value.ai, implement: nextImplement };
|
|
24993
|
+
} else {
|
|
24994
|
+
const rebuiltRow = defaultsForProvider[input.flow];
|
|
24995
|
+
nextAi = { ...current.value.ai, [input.flow]: rebuiltRow };
|
|
24996
|
+
}
|
|
24291
24997
|
const next = { ...current.value, ai: nextAi };
|
|
24292
24998
|
const saved = await deps.settingsRepo.save(next);
|
|
24293
24999
|
if (!saved.ok) return Result.error(saved.error);
|
|
@@ -24305,7 +25011,10 @@ var MIXED = {
|
|
|
24305
25011
|
effort: "high",
|
|
24306
25012
|
refine: { provider: "openai-codex", model: "gpt-5.5" },
|
|
24307
25013
|
plan: { provider: "github-copilot", model: "claude-sonnet-4.6", effort: "xhigh" },
|
|
24308
|
-
implement: {
|
|
25014
|
+
implement: {
|
|
25015
|
+
generator: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" },
|
|
25016
|
+
evaluator: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" }
|
|
25017
|
+
},
|
|
24309
25018
|
readiness: { provider: "github-copilot", model: "gpt-5-mini", effort: "medium" },
|
|
24310
25019
|
ideate: { provider: "claude-code", model: "claude-opus-4-7" }
|
|
24311
25020
|
};
|
|
@@ -24313,7 +25022,10 @@ var CLAUDE_ONLY = {
|
|
|
24313
25022
|
effort: "high",
|
|
24314
25023
|
refine: { provider: "claude-code", model: "claude-sonnet-4-6" },
|
|
24315
25024
|
plan: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" },
|
|
24316
|
-
implement: {
|
|
25025
|
+
implement: {
|
|
25026
|
+
generator: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" },
|
|
25027
|
+
evaluator: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" }
|
|
25028
|
+
},
|
|
24317
25029
|
readiness: { provider: "claude-code", model: "claude-haiku-4-5", effort: "medium" },
|
|
24318
25030
|
ideate: { provider: "claude-code", model: "claude-opus-4-7" }
|
|
24319
25031
|
};
|
|
@@ -24321,7 +25033,10 @@ var COPILOT_ONLY = {
|
|
|
24321
25033
|
effort: "high",
|
|
24322
25034
|
refine: { provider: "github-copilot", model: "claude-sonnet-4.6" },
|
|
24323
25035
|
plan: { provider: "github-copilot", model: "claude-opus-4.6", effort: "xhigh" },
|
|
24324
|
-
implement: {
|
|
25036
|
+
implement: {
|
|
25037
|
+
generator: { provider: "github-copilot", model: "claude-opus-4.6", effort: "xhigh" },
|
|
25038
|
+
evaluator: { provider: "github-copilot", model: "claude-opus-4.6", effort: "xhigh" }
|
|
25039
|
+
},
|
|
24325
25040
|
readiness: { provider: "github-copilot", model: "gpt-5-mini", effort: "medium" },
|
|
24326
25041
|
ideate: { provider: "github-copilot", model: "claude-opus-4.6" }
|
|
24327
25042
|
};
|
|
@@ -24329,7 +25044,10 @@ var CODEX_ONLY = {
|
|
|
24329
25044
|
effort: "high",
|
|
24330
25045
|
refine: { provider: "openai-codex", model: "gpt-5.4" },
|
|
24331
25046
|
plan: { provider: "openai-codex", model: "gpt-5.5", effort: "high" },
|
|
24332
|
-
implement: {
|
|
25047
|
+
implement: {
|
|
25048
|
+
generator: { provider: "openai-codex", model: "gpt-5.3-codex", effort: "high" },
|
|
25049
|
+
evaluator: { provider: "openai-codex", model: "gpt-5.3-codex", effort: "high" }
|
|
25050
|
+
},
|
|
24333
25051
|
readiness: { provider: "openai-codex", model: "gpt-5.4-mini", effort: "medium" },
|
|
24334
25052
|
ideate: { provider: "openai-codex", model: "gpt-5.5" }
|
|
24335
25053
|
};
|
|
@@ -24365,7 +25083,7 @@ var createSettingsApplyPresetFlow = (deps) => leaf("settings-apply-preset", {
|
|
|
24365
25083
|
var buildWarnings = (ai, installed) => {
|
|
24366
25084
|
const byProvider = /* @__PURE__ */ new Map();
|
|
24367
25085
|
for (const flow of FLOW_IDS) {
|
|
24368
|
-
const provider = ai
|
|
25086
|
+
const provider = primaryFlowRow(ai, flow).provider;
|
|
24369
25087
|
if (installed.has(provider)) continue;
|
|
24370
25088
|
const existing = byProvider.get(provider);
|
|
24371
25089
|
if (existing) existing.push(flow);
|
|
@@ -24375,38 +25093,48 @@ var buildWarnings = (ai, installed) => {
|
|
|
24375
25093
|
};
|
|
24376
25094
|
|
|
24377
25095
|
// src/business/settings/apply-key.ts
|
|
24378
|
-
var SETTINGS_KEY_HINT = "supported keys: ai.effort, ai.{flow}.provider,
|
|
25096
|
+
var SETTINGS_KEY_HINT = "supported keys: ai.effort, ai.{flow}.{provider,model,effort} (flow in {refine,plan,readiness,ideate}), ai.implement.{generator,evaluator}.{provider,model,effort}, harness.{maxTurns,maxAttempts,rateLimitRetries,plateauThreshold,escalateOnPlateau}, harness.escalationMap.<fromModel>, logging.level, concurrency.maxParallelTasks, ui.notifications.enabled";
|
|
25097
|
+
var IMPLEMENT_ROLES = ["generator", "evaluator"];
|
|
25098
|
+
var isImplementRole = (raw) => IMPLEMENT_ROLES.includes(raw);
|
|
24379
25099
|
var AI_PROVIDERS = ["claude-code", "github-copilot", "openai-codex"];
|
|
24380
25100
|
var isAiProvider = (raw) => AI_PROVIDERS.includes(raw);
|
|
24381
25101
|
var isFlowId = (raw) => FLOW_IDS.includes(raw);
|
|
24382
|
-
var
|
|
24383
|
-
const row = current.ai[flow];
|
|
24384
|
-
let nextRow;
|
|
25102
|
+
var updateFlowRow = (row, fieldKey, field, raw) => {
|
|
24385
25103
|
if (field === "provider") {
|
|
24386
25104
|
if (!isAiProvider(raw)) {
|
|
24387
25105
|
return Result.error(
|
|
24388
25106
|
new ValidationError({
|
|
24389
|
-
field:
|
|
25107
|
+
field: fieldKey,
|
|
24390
25108
|
value: raw,
|
|
24391
25109
|
message: `'${raw}' is not a recognised provider`,
|
|
24392
25110
|
hint: `expected one of: ${AI_PROVIDERS.join(", ")}`
|
|
24393
25111
|
})
|
|
24394
25112
|
);
|
|
24395
25113
|
}
|
|
24396
|
-
|
|
24397
|
-
}
|
|
24398
|
-
|
|
24399
|
-
|
|
24400
|
-
const trimmed = raw.trim();
|
|
24401
|
-
if (trimmed.length === 0) {
|
|
24402
|
-
const { effort: _drop, ...rowWithoutEffort } = row;
|
|
24403
|
-
void _drop;
|
|
24404
|
-
nextRow = rowWithoutEffort;
|
|
24405
|
-
} else {
|
|
24406
|
-
nextRow = { ...row, effort: trimmed };
|
|
24407
|
-
}
|
|
25114
|
+
return Result.ok({ ...row, provider: raw });
|
|
25115
|
+
}
|
|
25116
|
+
if (field === "model") {
|
|
25117
|
+
return Result.ok({ ...row, model: raw });
|
|
24408
25118
|
}
|
|
24409
|
-
const
|
|
25119
|
+
const trimmed = raw.trim();
|
|
25120
|
+
if (trimmed.length === 0) {
|
|
25121
|
+
const { effort: _drop, ...rowWithoutEffort } = row;
|
|
25122
|
+
void _drop;
|
|
25123
|
+
return Result.ok(rowWithoutEffort);
|
|
25124
|
+
}
|
|
25125
|
+
return Result.ok({ ...row, effort: trimmed });
|
|
25126
|
+
};
|
|
25127
|
+
var setAiFlowField = (current, flow, field, raw) => {
|
|
25128
|
+
const updated = updateFlowRow(current.ai[flow], `ai.${flow}.${field}`, field, raw);
|
|
25129
|
+
if (!updated.ok) return Result.error(updated.error);
|
|
25130
|
+
const nextAi = { ...current.ai, [flow]: updated.value };
|
|
25131
|
+
return Result.ok({ ...current, ai: nextAi });
|
|
25132
|
+
};
|
|
25133
|
+
var setAiImplementRoleField = (current, role, field, raw) => {
|
|
25134
|
+
const updated = updateFlowRow(current.ai.implement[role], `ai.implement.${role}.${field}`, field, raw);
|
|
25135
|
+
if (!updated.ok) return Result.error(updated.error);
|
|
25136
|
+
const nextImplement = { ...current.ai.implement, [role]: updated.value };
|
|
25137
|
+
const nextAi = { ...current.ai, implement: nextImplement };
|
|
24410
25138
|
return Result.ok({ ...current, ai: nextAi });
|
|
24411
25139
|
};
|
|
24412
25140
|
var applySettingsKey = (current, key, raw) => {
|
|
@@ -24415,9 +25143,26 @@ var applySettingsKey = (current, key, raw) => {
|
|
|
24415
25143
|
if (parts.length === 3 && parts[0] === "ai") {
|
|
24416
25144
|
const maybeFlow = parts[1] ?? "";
|
|
24417
25145
|
const maybeField = parts[2] ?? "";
|
|
24418
|
-
if (isFlowId(maybeFlow) && (maybeField === "provider" || maybeField === "model" || maybeField === "effort")) {
|
|
25146
|
+
if (isFlowId(maybeFlow) && maybeFlow !== "implement" && (maybeField === "provider" || maybeField === "model" || maybeField === "effort")) {
|
|
24419
25147
|
return setAiFlowField(current, maybeFlow, maybeField, raw);
|
|
24420
25148
|
}
|
|
25149
|
+
if (maybeFlow === "implement" && (maybeField === "provider" || maybeField === "model" || maybeField === "effort")) {
|
|
25150
|
+
return Result.error(
|
|
25151
|
+
new ValidationError({
|
|
25152
|
+
field: key,
|
|
25153
|
+
value: raw,
|
|
25154
|
+
message: `'${key}' is no longer addressable \u2014 implement splits into generator and evaluator roles`,
|
|
25155
|
+
hint: `use ai.implement.generator.${maybeField} or ai.implement.evaluator.${maybeField}`
|
|
25156
|
+
})
|
|
25157
|
+
);
|
|
25158
|
+
}
|
|
25159
|
+
}
|
|
25160
|
+
if (parts.length === 4 && parts[0] === "ai" && parts[1] === "implement") {
|
|
25161
|
+
const maybeRole = parts[2] ?? "";
|
|
25162
|
+
const maybeField = parts[3] ?? "";
|
|
25163
|
+
if (isImplementRole(maybeRole) && (maybeField === "provider" || maybeField === "model" || maybeField === "effort")) {
|
|
25164
|
+
return setAiImplementRoleField(current, maybeRole, maybeField, raw);
|
|
25165
|
+
}
|
|
24421
25166
|
}
|
|
24422
25167
|
if (key === "ai.effort") {
|
|
24423
25168
|
const trimmed = raw.trim();
|
|
@@ -24429,6 +25174,26 @@ var applySettingsKey = (current, key, raw) => {
|
|
|
24429
25174
|
return Result.ok({ ...current, ai: { ...current.ai, effort: trimmed } });
|
|
24430
25175
|
}
|
|
24431
25176
|
}
|
|
25177
|
+
if (key.startsWith("harness.escalationMap.")) {
|
|
25178
|
+
const fromModel = key.slice("harness.escalationMap.".length);
|
|
25179
|
+
if (fromModel.length === 0) {
|
|
25180
|
+
return Result.error(
|
|
25181
|
+
new ValidationError({
|
|
25182
|
+
field: "key",
|
|
25183
|
+
value: key,
|
|
25184
|
+
message: `'${key}' is missing the source model id`,
|
|
25185
|
+
hint: "use harness.escalationMap.<fromModel>"
|
|
25186
|
+
})
|
|
25187
|
+
);
|
|
25188
|
+
}
|
|
25189
|
+
const nextMap = { ...current.harness.escalationMap };
|
|
25190
|
+
if (raw.trim().length === 0) {
|
|
25191
|
+
delete nextMap[fromModel];
|
|
25192
|
+
} else {
|
|
25193
|
+
nextMap[fromModel] = raw;
|
|
25194
|
+
}
|
|
25195
|
+
return Result.ok({ ...current, harness: { ...current.harness, escalationMap: nextMap } });
|
|
25196
|
+
}
|
|
24432
25197
|
switch (key) {
|
|
24433
25198
|
case "harness.maxTurns":
|
|
24434
25199
|
case "harness.maxAttempts":
|
|
@@ -24441,6 +25206,20 @@ var applySettingsKey = (current, key, raw) => {
|
|
|
24441
25206
|
const which = key.split(".")[1];
|
|
24442
25207
|
return Result.ok({ ...current, harness: { ...current.harness, [which]: n } });
|
|
24443
25208
|
}
|
|
25209
|
+
case "harness.escalateOnPlateau": {
|
|
25210
|
+
const b = parseBool(raw);
|
|
25211
|
+
if (b === void 0) {
|
|
25212
|
+
return Result.error(
|
|
25213
|
+
new ValidationError({
|
|
25214
|
+
field: key,
|
|
25215
|
+
value: raw,
|
|
25216
|
+
message: `'${raw}' is not a boolean`,
|
|
25217
|
+
hint: "use 'true' or 'false'"
|
|
25218
|
+
})
|
|
25219
|
+
);
|
|
25220
|
+
}
|
|
25221
|
+
return Result.ok({ ...current, harness: { ...current.harness, escalateOnPlateau: b } });
|
|
25222
|
+
}
|
|
24444
25223
|
case "logging.level": {
|
|
24445
25224
|
return Result.ok({ ...current, logging: { level: raw } });
|
|
24446
25225
|
}
|
|
@@ -24532,29 +25311,36 @@ var buildEditableFields = (s) => {
|
|
|
24532
25311
|
options: [DEFAULT_TOKEN, ...GLOBAL_EFFORT_LEVELS],
|
|
24533
25312
|
current: s.ai.effort ?? DEFAULT_TOKEN
|
|
24534
25313
|
});
|
|
24535
|
-
|
|
24536
|
-
const row = s.ai[flow];
|
|
25314
|
+
const pushRowFields = (keyPrefix, label, row) => {
|
|
24537
25315
|
fields.push({
|
|
24538
25316
|
kind: "select",
|
|
24539
|
-
key:
|
|
24540
|
-
label: `${
|
|
25317
|
+
key: `${keyPrefix}.provider`,
|
|
25318
|
+
label: `${label} provider`,
|
|
24541
25319
|
options: AI_PROVIDERS2,
|
|
24542
25320
|
current: row.provider
|
|
24543
25321
|
});
|
|
24544
25322
|
fields.push({
|
|
24545
25323
|
kind: "model",
|
|
24546
|
-
key:
|
|
24547
|
-
label: `${
|
|
25324
|
+
key: `${keyPrefix}.model`,
|
|
25325
|
+
label: `${label} model`,
|
|
24548
25326
|
options: [...modelOptionsFor(row.provider), CUSTOM_TOKEN],
|
|
24549
25327
|
current: row.model
|
|
24550
25328
|
});
|
|
24551
25329
|
fields.push({
|
|
24552
25330
|
kind: "select",
|
|
24553
|
-
key:
|
|
24554
|
-
label: `${
|
|
25331
|
+
key: `${keyPrefix}.effort`,
|
|
25332
|
+
label: `${label} effort`,
|
|
24555
25333
|
options: [DEFAULT_TOKEN, ...PROVIDER_EFFORT_LEVELS[row.provider]],
|
|
24556
25334
|
current: row.effort ?? DEFAULT_TOKEN
|
|
24557
25335
|
});
|
|
25336
|
+
};
|
|
25337
|
+
for (const flow of FLOW_IDS) {
|
|
25338
|
+
if (flow === "implement") {
|
|
25339
|
+
pushRowFields("ai.implement.generator", "Implement (generator)", s.ai.implement.generator);
|
|
25340
|
+
pushRowFields("ai.implement.evaluator", "Implement (evaluator)", s.ai.implement.evaluator);
|
|
25341
|
+
continue;
|
|
25342
|
+
}
|
|
25343
|
+
pushRowFields(`ai.${flow}`, capitalize(flow), s.ai[flow]);
|
|
24558
25344
|
}
|
|
24559
25345
|
fields.push(
|
|
24560
25346
|
{ kind: "text", key: "harness.maxTurns", label: "Max turns", current: String(s.harness.maxTurns) },
|
|
@@ -24589,6 +25375,7 @@ var SettingsView = () => {
|
|
|
24589
25375
|
const logLevel = useLogLevel();
|
|
24590
25376
|
const [settings, setSettings] = useState37(void 0);
|
|
24591
25377
|
const [loadError, setLoadError] = useState37(void 0);
|
|
25378
|
+
const [installedProviders, setInstalledProviders] = useState37(void 0);
|
|
24592
25379
|
const [cursor, setCursor] = useState37(0);
|
|
24593
25380
|
const [editingField, setEditingField] = useState37(void 0);
|
|
24594
25381
|
const [customModelField, setCustomModelField] = useState37(void 0);
|
|
@@ -24607,14 +25394,23 @@ var SettingsView = () => {
|
|
|
24607
25394
|
if (result.ok) setSettings(result.value.ctx.output);
|
|
24608
25395
|
else setLoadError(result.error.error.message);
|
|
24609
25396
|
}, [deps]);
|
|
24610
|
-
|
|
25397
|
+
useEffect30(() => {
|
|
24611
25398
|
void refresh();
|
|
24612
25399
|
}, [refresh]);
|
|
25400
|
+
useEffect30(() => {
|
|
25401
|
+
let cancelled = false;
|
|
25402
|
+
void detectInstalledProviders().then((installed) => {
|
|
25403
|
+
if (!cancelled) setInstalledProviders(installed);
|
|
25404
|
+
});
|
|
25405
|
+
return () => {
|
|
25406
|
+
cancelled = true;
|
|
25407
|
+
};
|
|
25408
|
+
}, []);
|
|
24613
25409
|
const fields = useMemo16(
|
|
24614
25410
|
() => settings === void 0 ? [] : buildEditableFields(settings),
|
|
24615
25411
|
[settings]
|
|
24616
25412
|
);
|
|
24617
|
-
|
|
25413
|
+
useEffect30(() => {
|
|
24618
25414
|
if (cursor >= fields.length && fields.length > 0) setCursor(fields.length - 1);
|
|
24619
25415
|
}, [fields, cursor]);
|
|
24620
25416
|
useInput23((input, key) => {
|
|
@@ -24641,7 +25437,7 @@ var SettingsView = () => {
|
|
|
24641
25437
|
setEditingField(field);
|
|
24642
25438
|
}
|
|
24643
25439
|
});
|
|
24644
|
-
|
|
25440
|
+
useEffect30(
|
|
24645
25441
|
() => editingField !== void 0 || customModelField !== void 0 || pendingPreset !== void 0 ? ui.claimPrompt() : void 0,
|
|
24646
25442
|
[editingField, customModelField, pendingPreset, ui.claimPrompt]
|
|
24647
25443
|
);
|
|
@@ -24666,17 +25462,22 @@ var SettingsView = () => {
|
|
|
24666
25462
|
closeEditor();
|
|
24667
25463
|
return;
|
|
24668
25464
|
}
|
|
24669
|
-
const
|
|
24670
|
-
|
|
24671
|
-
|
|
25465
|
+
const implementRoleProviderMatch = /^ai\.implement\.(generator|evaluator)\.provider$/.exec(field.key);
|
|
25466
|
+
const flatProviderMatch = /^ai\.(refine|plan|readiness|ideate)\.provider$/.exec(field.key);
|
|
25467
|
+
if (implementRoleProviderMatch !== null || flatProviderMatch !== null) {
|
|
24672
25468
|
const providerFlow = createSettingsSetProviderFlow({ settingsRepo: deps.settingsRepo });
|
|
24673
|
-
const
|
|
25469
|
+
const flow = implementRoleProviderMatch !== null ? "implement" : flatProviderMatch[1];
|
|
25470
|
+
const role = implementRoleProviderMatch?.[1];
|
|
25471
|
+
const saved2 = await providerFlow.execute({
|
|
25472
|
+
input: { flow, provider: raw, ...role !== void 0 ? { role } : {} }
|
|
25473
|
+
});
|
|
24674
25474
|
if (!saved2.ok) {
|
|
24675
25475
|
setFeedback({ tone: "error", text: saved2.error.error.message });
|
|
24676
25476
|
closeEditor();
|
|
24677
25477
|
return;
|
|
24678
25478
|
}
|
|
24679
|
-
|
|
25479
|
+
const label = role !== void 0 ? `Implement (${role})` : capitalize(flow);
|
|
25480
|
+
setFeedback({ tone: "ok", text: `${label} provider = ${raw} \xB7 model reset to default` });
|
|
24680
25481
|
closeEditor();
|
|
24681
25482
|
await refresh();
|
|
24682
25483
|
return;
|
|
@@ -24702,6 +25503,27 @@ var SettingsView = () => {
|
|
|
24702
25503
|
closeEditor();
|
|
24703
25504
|
await refresh();
|
|
24704
25505
|
};
|
|
25506
|
+
const isProviderField = (field) => field.kind === "select" && (field.key.endsWith(".provider") || field.key === "ai.provider");
|
|
25507
|
+
const buildProviderOptions = (options) => {
|
|
25508
|
+
const installed = installedProviders;
|
|
25509
|
+
const choices = options.map((value) => {
|
|
25510
|
+
const provider = value;
|
|
25511
|
+
const available = installed === void 0 || installed.has(provider);
|
|
25512
|
+
const label = available ? value : `${value} (not installed)`;
|
|
25513
|
+
return available ? { label, value } : { label, value, disabled: true };
|
|
25514
|
+
});
|
|
25515
|
+
const anyEnabled = choices.some((o) => o.disabled !== true);
|
|
25516
|
+
const missing = options.filter((v) => installed !== void 0 && !installed.has(v));
|
|
25517
|
+
const footerParts = [];
|
|
25518
|
+
if (!anyEnabled) {
|
|
25519
|
+
footerParts.push("No AI provider CLI is installed.");
|
|
25520
|
+
}
|
|
25521
|
+
for (const m of missing) {
|
|
25522
|
+
footerParts.push(`install ${m}: ${primaryInstallCommand(m)}`);
|
|
25523
|
+
}
|
|
25524
|
+
if (footerParts.length === 0) return { choices };
|
|
25525
|
+
return { choices, footer: footerParts.join(" \xB7 ") };
|
|
25526
|
+
};
|
|
24705
25527
|
const renderEditor = (field) => {
|
|
24706
25528
|
if (field.kind === "model" && customModelField !== void 0) {
|
|
24707
25529
|
return /* @__PURE__ */ jsx56(
|
|
@@ -24715,6 +25537,19 @@ var SettingsView = () => {
|
|
|
24715
25537
|
);
|
|
24716
25538
|
}
|
|
24717
25539
|
if (field.kind === "select" || field.kind === "model") {
|
|
25540
|
+
if (field.kind === "select" && isProviderField(field)) {
|
|
25541
|
+
const { choices, footer } = buildProviderOptions(field.options);
|
|
25542
|
+
return /* @__PURE__ */ jsx56(
|
|
25543
|
+
SelectPrompt,
|
|
25544
|
+
{
|
|
25545
|
+
message: `${field.label} (current: ${field.current})`,
|
|
25546
|
+
options: choices,
|
|
25547
|
+
...footer !== void 0 ? { footer } : {},
|
|
25548
|
+
onSubmit: (value) => void submit(String(value), field),
|
|
25549
|
+
onCancel: closeEditor
|
|
25550
|
+
}
|
|
25551
|
+
);
|
|
25552
|
+
}
|
|
24718
25553
|
return /* @__PURE__ */ jsx56(
|
|
24719
25554
|
SelectPrompt,
|
|
24720
25555
|
{
|
|
@@ -24785,16 +25620,48 @@ var SettingsView = () => {
|
|
|
24785
25620
|
] }, w.provider)) })
|
|
24786
25621
|
] }),
|
|
24787
25622
|
/* @__PURE__ */ jsx56(Box44, { marginTop: spacing.section, children: /* @__PURE__ */ jsx56(Card, { title: "AI \u2014 global", tone: "primary", children: /* @__PURE__ */ jsx56(FieldList, { fields: [{ label: "Effort (default)", value: valueFor("ai.effort") }] }) }) }),
|
|
24788
|
-
FLOW_IDS.map(
|
|
24789
|
-
|
|
24790
|
-
|
|
24791
|
-
|
|
24792
|
-
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
|
|
24796
|
-
|
|
24797
|
-
|
|
25623
|
+
FLOW_IDS.map(
|
|
25624
|
+
(flow) => flow === "implement" ? (
|
|
25625
|
+
// Implement is the only flow whose runtime carries two AI sessions per task — the
|
|
25626
|
+
// generator that proposes a commit and the evaluator that judges it. Render the
|
|
25627
|
+
// parent flow name as a non-editable card title; the two roles render as indented
|
|
25628
|
+
// sub-rows underneath so the operator sees at a glance that they're two halves of
|
|
25629
|
+
// the same flow rather than two independent flows. Edits on either role flow
|
|
25630
|
+
// through the same dotted-path keys (`ai.implement.<role>.<field>`), so changing
|
|
25631
|
+
// one role's provider/model/effort cannot perturb the other.
|
|
25632
|
+
/* @__PURE__ */ jsx56(Box44, { marginTop: spacing.section, children: /* @__PURE__ */ jsx56(Card, { title: "AI \u2014 Implement", tone: "primary", children: ["generator", "evaluator"].map((role, idx) => /* @__PURE__ */ jsxs45(
|
|
25633
|
+
Box44,
|
|
25634
|
+
{
|
|
25635
|
+
flexDirection: "column",
|
|
25636
|
+
paddingLeft: spacing.indent,
|
|
25637
|
+
marginTop: idx === 0 ? 0 : spacing.section,
|
|
25638
|
+
children: [
|
|
25639
|
+
/* @__PURE__ */ jsx56(Text46, { dimColor: true, bold: true, children: role }),
|
|
25640
|
+
/* @__PURE__ */ jsx56(
|
|
25641
|
+
FieldList,
|
|
25642
|
+
{
|
|
25643
|
+
fields: [
|
|
25644
|
+
{ label: "Provider", value: valueFor(`ai.implement.${role}.provider`) },
|
|
25645
|
+
{ label: "Model", value: valueFor(`ai.implement.${role}.model`) },
|
|
25646
|
+
{ label: "Effort", value: valueFor(`ai.implement.${role}.effort`) }
|
|
25647
|
+
]
|
|
25648
|
+
}
|
|
25649
|
+
)
|
|
25650
|
+
]
|
|
25651
|
+
},
|
|
25652
|
+
role
|
|
25653
|
+
)) }) }, flow)
|
|
25654
|
+
) : /* @__PURE__ */ jsx56(Box44, { marginTop: spacing.section, children: /* @__PURE__ */ jsx56(Card, { title: `AI \u2014 ${capitalize(flow)}`, tone: "primary", children: /* @__PURE__ */ jsx56(
|
|
25655
|
+
FieldList,
|
|
25656
|
+
{
|
|
25657
|
+
fields: [
|
|
25658
|
+
{ label: "Provider", value: valueFor(`ai.${flow}.provider`) },
|
|
25659
|
+
{ label: "Model", value: valueFor(`ai.${flow}.model`) },
|
|
25660
|
+
{ label: "Effort", value: valueFor(`ai.${flow}.effort`) }
|
|
25661
|
+
]
|
|
25662
|
+
}
|
|
25663
|
+
) }) }, flow)
|
|
25664
|
+
),
|
|
24798
25665
|
/* @__PURE__ */ jsx56(Box44, { marginTop: spacing.section, children: /* @__PURE__ */ jsx56(Card, { title: "Harness budgets", tone: "primary", children: /* @__PURE__ */ jsx56(
|
|
24799
25666
|
FieldList,
|
|
24800
25667
|
{
|
|
@@ -24850,7 +25717,7 @@ var SettingsView = () => {
|
|
|
24850
25717
|
};
|
|
24851
25718
|
|
|
24852
25719
|
// src/application/ui/tui/views/doctor-view.tsx
|
|
24853
|
-
import React59, { useEffect as
|
|
25720
|
+
import React59, { useEffect as useEffect31 } from "react";
|
|
24854
25721
|
import { Box as Box45, Text as Text47, useInput as useInput24 } from "ink";
|
|
24855
25722
|
import { jsx as jsx57, jsxs as jsxs46 } from "react/jsx-runtime";
|
|
24856
25723
|
var GROUP_ORDER = [
|
|
@@ -24880,7 +25747,7 @@ var DoctorView = () => {
|
|
|
24880
25747
|
useViewHints([{ keys: "r", label: "reload" }]);
|
|
24881
25748
|
const refreshDoctor = system.refreshDoctor;
|
|
24882
25749
|
const triggered = React59.useRef(false);
|
|
24883
|
-
|
|
25750
|
+
useEffect31(() => {
|
|
24884
25751
|
if (triggered.current) return;
|
|
24885
25752
|
if (results !== void 0 || system.doctorLoading) return;
|
|
24886
25753
|
triggered.current = true;
|
|
@@ -24965,7 +25832,7 @@ var ProbeRow = ({ probe }) => /* @__PURE__ */ jsxs46(Box45, { flexDirection: "co
|
|
|
24965
25832
|
] });
|
|
24966
25833
|
|
|
24967
25834
|
// src/application/ui/tui/views/export-context-view.tsx
|
|
24968
|
-
import { useCallback as useCallback10, useEffect as
|
|
25835
|
+
import { useCallback as useCallback10, useEffect as useEffect32, useState as useState38 } from "react";
|
|
24969
25836
|
import { Box as Box46, Text as Text48, useInput as useInput25 } from "ink";
|
|
24970
25837
|
import { join as join43 } from "path";
|
|
24971
25838
|
|
|
@@ -25128,7 +25995,7 @@ var ExportContextView = () => {
|
|
|
25128
25995
|
const out = result.value.ctx.output;
|
|
25129
25996
|
setRun({ kind: "done", path: String(out.outputPath), bytes: out.byteCount });
|
|
25130
25997
|
}, [deps, storage2, selection.projectId, selection.sprintId]);
|
|
25131
|
-
|
|
25998
|
+
useEffect32(() => {
|
|
25132
25999
|
void runExport();
|
|
25133
26000
|
}, [runExport]);
|
|
25134
26001
|
useInput25((input) => {
|
|
@@ -25161,7 +26028,7 @@ var ExportContextView = () => {
|
|
|
25161
26028
|
};
|
|
25162
26029
|
|
|
25163
26030
|
// src/application/ui/tui/views/export-requirements-view.tsx
|
|
25164
|
-
import { useCallback as useCallback11, useEffect as
|
|
26031
|
+
import { useCallback as useCallback11, useEffect as useEffect33, useState as useState39 } from "react";
|
|
25165
26032
|
import { Box as Box47, Text as Text49, useInput as useInput26 } from "ink";
|
|
25166
26033
|
import { join as join44 } from "path";
|
|
25167
26034
|
|
|
@@ -25249,7 +26116,7 @@ var ExportRequirementsView = () => {
|
|
|
25249
26116
|
const out = result.value.ctx.output;
|
|
25250
26117
|
setRun({ kind: "done", path: String(out.outputPath), bytes: out.byteCount });
|
|
25251
26118
|
}, [deps, storage2, selection.sprintId]);
|
|
25252
|
-
|
|
26119
|
+
useEffect33(() => {
|
|
25253
26120
|
void runExport();
|
|
25254
26121
|
}, [runExport]);
|
|
25255
26122
|
useInput26((input) => {
|
|
@@ -25282,7 +26149,7 @@ var ExportRequirementsView = () => {
|
|
|
25282
26149
|
};
|
|
25283
26150
|
|
|
25284
26151
|
// src/application/ui/tui/views/create-pr-view.tsx
|
|
25285
|
-
import { useCallback as useCallback12, useEffect as
|
|
26152
|
+
import { useCallback as useCallback12, useEffect as useEffect34, useState as useState40 } from "react";
|
|
25286
26153
|
import { join as join46 } from "path";
|
|
25287
26154
|
import { Box as Box48, Text as Text50, useInput as useInput27 } from "ink";
|
|
25288
26155
|
|
|
@@ -25805,7 +26672,7 @@ var CreatePrView = () => {
|
|
|
25805
26672
|
{ keys: "a", label: "toggle AI" },
|
|
25806
26673
|
{ keys: "esc", label: "back" }
|
|
25807
26674
|
]);
|
|
25808
|
-
|
|
26675
|
+
useEffect34(() => {
|
|
25809
26676
|
const load = async () => {
|
|
25810
26677
|
if (selection.projectId === void 0 || selection.sprintId === void 0) {
|
|
25811
26678
|
setPrep({ kind: "error", message: "No project or sprint selected." });
|
|
@@ -25937,7 +26804,7 @@ var CreatePrView = () => {
|
|
|
25937
26804
|
};
|
|
25938
26805
|
|
|
25939
26806
|
// src/application/ui/tui/views/welcome-view.tsx
|
|
25940
|
-
import { useEffect as
|
|
26807
|
+
import { useEffect as useEffect35, useRef as useRef11, useState as useState41 } from "react";
|
|
25941
26808
|
import { Box as Box49, Text as Text51 } from "ink";
|
|
25942
26809
|
import { jsx as jsx61, jsxs as jsxs50 } from "react/jsx-runtime";
|
|
25943
26810
|
var PRESET_FOR_PROVIDER = {
|
|
@@ -25964,8 +26831,8 @@ var WelcomeView = () => {
|
|
|
25964
26831
|
const [step, setStep] = useState41("detecting");
|
|
25965
26832
|
const [chosenPreset, setChosenPreset] = useState41(void 0);
|
|
25966
26833
|
const [errorMsg, setErrorMsg] = useState41(void 0);
|
|
25967
|
-
const seededRef =
|
|
25968
|
-
|
|
26834
|
+
const seededRef = useRef11(false);
|
|
26835
|
+
useEffect35(() => {
|
|
25969
26836
|
if (seededRef.current) return;
|
|
25970
26837
|
seededRef.current = true;
|
|
25971
26838
|
const seed = async () => {
|
|
@@ -26007,13 +26874,13 @@ var WelcomeView = () => {
|
|
|
26007
26874
|
};
|
|
26008
26875
|
|
|
26009
26876
|
// src/application/ui/tui/views/create-project-view.tsx
|
|
26010
|
-
import { useEffect as
|
|
26877
|
+
import { useEffect as useEffect37, useState as useState43 } from "react";
|
|
26011
26878
|
import { Box as Box51, Text as Text53 } from "ink";
|
|
26012
26879
|
import { homedir as osHomedir2 } from "os";
|
|
26013
26880
|
import { basename as basename5, join as join48 } from "path";
|
|
26014
26881
|
|
|
26015
26882
|
// src/application/ui/tui/prompts/path-picker-prompt.tsx
|
|
26016
|
-
import { useEffect as
|
|
26883
|
+
import { useEffect as useEffect36, useState as useState42 } from "react";
|
|
26017
26884
|
import { promises as fs26 } from "fs";
|
|
26018
26885
|
import { dirname as dirname18, join as join47 } from "path";
|
|
26019
26886
|
import { homedir } from "os";
|
|
@@ -26038,7 +26905,7 @@ var PathPickerPrompt = ({
|
|
|
26038
26905
|
const [showHidden, setShowHidden] = useState42(false);
|
|
26039
26906
|
const [error, setError] = useState42(void 0);
|
|
26040
26907
|
const [typing, setTyping] = useState42(false);
|
|
26041
|
-
|
|
26908
|
+
useEffect36(() => {
|
|
26042
26909
|
const load = async () => {
|
|
26043
26910
|
try {
|
|
26044
26911
|
const items = await fs26.readdir(cwd, { withFileTypes: true });
|
|
@@ -26057,7 +26924,7 @@ var PathPickerPrompt = ({
|
|
|
26057
26924
|
{ kind: "select" },
|
|
26058
26925
|
...entries.map((e) => ({ kind: "entry", entry: e }))
|
|
26059
26926
|
];
|
|
26060
|
-
|
|
26927
|
+
useEffect36(() => {
|
|
26061
26928
|
setCursor((c) => clamp5(c, 0, Math.max(0, rows.length - 1)));
|
|
26062
26929
|
}, [rows.length]);
|
|
26063
26930
|
useInput28(
|
|
@@ -26232,7 +27099,7 @@ var CreateProjectView = () => {
|
|
|
26232
27099
|
const selection = useSelection();
|
|
26233
27100
|
const ui = useUiState();
|
|
26234
27101
|
const [step, setStep] = useState43({ kind: "name" });
|
|
26235
|
-
|
|
27102
|
+
useEffect37(() => ui.claimPrompt(), [ui.claimPrompt]);
|
|
26236
27103
|
const cancel = () => router.pop();
|
|
26237
27104
|
const submit = async (s) => {
|
|
26238
27105
|
setStep({ kind: "saving" });
|
|
@@ -26406,7 +27273,7 @@ var StepView = ({ step, onChange, onCancel, onSubmit }) => {
|
|
|
26406
27273
|
};
|
|
26407
27274
|
|
|
26408
27275
|
// src/application/ui/tui/views/add-repository-view.tsx
|
|
26409
|
-
import { useEffect as
|
|
27276
|
+
import { useEffect as useEffect38, useState as useState44 } from "react";
|
|
26410
27277
|
import { Box as Box52, Text as Text54 } from "ink";
|
|
26411
27278
|
import { homedir as osHomedir3 } from "os";
|
|
26412
27279
|
import { basename as basename6, join as join49 } from "path";
|
|
@@ -26435,7 +27302,7 @@ var AddRepositoryView = () => {
|
|
|
26435
27302
|
const ui = useUiState();
|
|
26436
27303
|
const { projectId } = useViewProps();
|
|
26437
27304
|
const [step, setStep] = useState44({ kind: "path" });
|
|
26438
|
-
|
|
27305
|
+
useEffect38(() => {
|
|
26439
27306
|
if (step.kind === "path" || step.kind === "name" || step.kind === "confirm") {
|
|
26440
27307
|
return ui.claimPrompt();
|
|
26441
27308
|
}
|
|
@@ -26554,7 +27421,7 @@ var StepView2 = ({ step, onChange, onCancel, onSubmit }) => {
|
|
|
26554
27421
|
};
|
|
26555
27422
|
|
|
26556
27423
|
// src/application/ui/tui/views/add-ticket-view.tsx
|
|
26557
|
-
import { useEffect as
|
|
27424
|
+
import { useEffect as useEffect39, useState as useState45 } from "react";
|
|
26558
27425
|
import { Box as Box53, Text as Text55 } from "ink";
|
|
26559
27426
|
|
|
26560
27427
|
// src/application/flows/ticket-add/flow.ts
|
|
@@ -26618,9 +27485,9 @@ var AddTicketView = () => {
|
|
|
26618
27485
|
const ui = useUiState();
|
|
26619
27486
|
const { sprintId } = useViewProps();
|
|
26620
27487
|
const [step, setStep] = useState45({ kind: "link" });
|
|
26621
|
-
|
|
27488
|
+
useEffect39(() => ui.claimPrompt(), [ui.claimPrompt]);
|
|
26622
27489
|
const cancel = () => router.pop();
|
|
26623
|
-
|
|
27490
|
+
useEffect39(() => {
|
|
26624
27491
|
if (step.kind !== "fetching") return;
|
|
26625
27492
|
const fetcher = deps.issueFetcher;
|
|
26626
27493
|
if (fetcher === void 0) {
|
|
@@ -26846,7 +27713,7 @@ var runFetch = async (fetcher, url) => {
|
|
|
26846
27713
|
};
|
|
26847
27714
|
|
|
26848
27715
|
// src/application/ui/tui/views/pick-project-view.tsx
|
|
26849
|
-
import { useEffect as
|
|
27716
|
+
import { useEffect as useEffect40, useMemo as useMemo17, useState as useState46 } from "react";
|
|
26850
27717
|
import { Box as Box54, Text as Text56, useInput as useInput29 } from "ink";
|
|
26851
27718
|
import { jsx as jsx66, jsxs as jsxs55 } from "react/jsx-runtime";
|
|
26852
27719
|
var PickProjectView = () => {
|
|
@@ -26871,10 +27738,10 @@ var PickProjectView = () => {
|
|
|
26871
27738
|
return i === -1 ? 0 : i;
|
|
26872
27739
|
}, [projects, selection.projectId]);
|
|
26873
27740
|
const [cursor, setCursor] = useState46(initialIdx);
|
|
26874
|
-
|
|
27741
|
+
useEffect40(() => {
|
|
26875
27742
|
setCursor((c) => c >= projects.length ? Math.max(0, projects.length - 1) : c);
|
|
26876
27743
|
}, [projects.length]);
|
|
26877
|
-
|
|
27744
|
+
useEffect40(() => {
|
|
26878
27745
|
setCursor(initialIdx);
|
|
26879
27746
|
}, [initialIdx]);
|
|
26880
27747
|
const pick = (project) => {
|
|
@@ -26955,7 +27822,7 @@ var PickProjectView = () => {
|
|
|
26955
27822
|
};
|
|
26956
27823
|
|
|
26957
27824
|
// src/application/ui/tui/views/pick-sprint-view.tsx
|
|
26958
|
-
import { useEffect as
|
|
27825
|
+
import { useEffect as useEffect41, useMemo as useMemo18, useState as useState47 } from "react";
|
|
26959
27826
|
import { Box as Box55, Text as Text57, useInput as useInput30 } from "ink";
|
|
26960
27827
|
|
|
26961
27828
|
// src/application/ui/tui/runtime/use-breakpoint.ts
|
|
@@ -27108,7 +27975,7 @@ var PickSprintView = () => {
|
|
|
27108
27975
|
return cursorableRowIndices(rows)[0] ?? 0;
|
|
27109
27976
|
}, [rows, selection.sprintId]);
|
|
27110
27977
|
const [cursor, setCursor] = useState47(initialIdx);
|
|
27111
|
-
|
|
27978
|
+
useEffect41(() => {
|
|
27112
27979
|
setCursor((c) => {
|
|
27113
27980
|
if (rows.length === 0) return 0;
|
|
27114
27981
|
if (c >= rows.length) return Math.max(0, rows.length - 1);
|
|
@@ -27119,7 +27986,7 @@ var PickSprintView = () => {
|
|
|
27119
27986
|
return c;
|
|
27120
27987
|
});
|
|
27121
27988
|
}, [rows]);
|
|
27122
|
-
|
|
27989
|
+
useEffect41(() => {
|
|
27123
27990
|
setCursor(initialIdx);
|
|
27124
27991
|
}, [initialIdx]);
|
|
27125
27992
|
const pick = (sprint) => {
|
|
@@ -27377,7 +28244,7 @@ var renderView = (entry) => {
|
|
|
27377
28244
|
};
|
|
27378
28245
|
|
|
27379
28246
|
// src/application/ui/tui/runtime/use-global-keys.ts
|
|
27380
|
-
import { useEffect as
|
|
28247
|
+
import { useEffect as useEffect42, useMemo as useMemo19, useRef as useRef12 } from "react";
|
|
27381
28248
|
import { useApp, useInput as useInput31 } from "ink";
|
|
27382
28249
|
|
|
27383
28250
|
// src/integration/io/clipboard.ts
|
|
@@ -27486,8 +28353,8 @@ var useGlobalKeys = (opts = {}) => {
|
|
|
27486
28353
|
() => opts.copyToClipboard ?? createCopyToClipboard(),
|
|
27487
28354
|
[opts.copyToClipboard]
|
|
27488
28355
|
);
|
|
27489
|
-
const clearTimerRef =
|
|
27490
|
-
|
|
28356
|
+
const clearTimerRef = useRef12(void 0);
|
|
28357
|
+
useEffect42(
|
|
27491
28358
|
() => () => {
|
|
27492
28359
|
if (clearTimerRef.current !== void 0) clearTimeout(clearTimerRef.current);
|
|
27493
28360
|
},
|
|
@@ -27588,20 +28455,20 @@ var emitClipboardBanner = (eventBus, spec) => {
|
|
|
27588
28455
|
at: IsoTimestamp.now()
|
|
27589
28456
|
});
|
|
27590
28457
|
};
|
|
27591
|
-
var scheduleClear = (eventBus,
|
|
27592
|
-
if (
|
|
27593
|
-
|
|
28458
|
+
var scheduleClear = (eventBus, ref3) => {
|
|
28459
|
+
if (ref3.current !== void 0) clearTimeout(ref3.current);
|
|
28460
|
+
ref3.current = setTimeout(() => {
|
|
27594
28461
|
eventBus.publish({
|
|
27595
28462
|
type: "banner-clear",
|
|
27596
28463
|
id: CLIPBOARD_BANNER_ID,
|
|
27597
28464
|
at: IsoTimestamp.now()
|
|
27598
28465
|
});
|
|
27599
|
-
|
|
28466
|
+
ref3.current = void 0;
|
|
27600
28467
|
}, CLIPBOARD_TOAST_DURATION_MS);
|
|
27601
28468
|
};
|
|
27602
28469
|
|
|
27603
28470
|
// src/application/ui/tui/components/memory-pressure-banner.tsx
|
|
27604
|
-
import { useEffect as
|
|
28471
|
+
import { useEffect as useEffect43, useState as useState48 } from "react";
|
|
27605
28472
|
import { Box as Box56, Text as Text58 } from "ink";
|
|
27606
28473
|
import { jsxs as jsxs57 } from "react/jsx-runtime";
|
|
27607
28474
|
var formatMb = (bytes) => `${(bytes / 1048576).toFixed(0)} MB`;
|
|
@@ -27609,7 +28476,7 @@ var formatPercent = (ratio) => `${Math.round(ratio * 100)}%`;
|
|
|
27609
28476
|
var MemoryPressureBanner = () => {
|
|
27610
28477
|
const deps = useDeps();
|
|
27611
28478
|
const [latest, setLatest] = useState48(void 0);
|
|
27612
|
-
|
|
28479
|
+
useEffect43(() => {
|
|
27613
28480
|
const unsub = deps.eventBus.subscribe((event) => {
|
|
27614
28481
|
if (event.type === "memory-pressure") setLatest(event);
|
|
27615
28482
|
});
|
|
@@ -27633,13 +28500,13 @@ var MemoryPressureBanner = () => {
|
|
|
27633
28500
|
};
|
|
27634
28501
|
|
|
27635
28502
|
// src/application/ui/tui/components/chain-log-degraded-banner.tsx
|
|
27636
|
-
import { useEffect as
|
|
28503
|
+
import { useEffect as useEffect44, useState as useState49 } from "react";
|
|
27637
28504
|
import { Box as Box57, Text as Text59 } from "ink";
|
|
27638
28505
|
import { jsx as jsx69, jsxs as jsxs58 } from "react/jsx-runtime";
|
|
27639
28506
|
var ChainLogDegradedBanner = () => {
|
|
27640
28507
|
const deps = useDeps();
|
|
27641
28508
|
const [degraded, setDegraded] = useState49(false);
|
|
27642
|
-
|
|
28509
|
+
useEffect44(() => {
|
|
27643
28510
|
const unsub = deps.eventBus.subscribe((event) => {
|
|
27644
28511
|
if (event.type === "chain-log-degraded") setDegraded(true);
|
|
27645
28512
|
});
|
|
@@ -27655,7 +28522,7 @@ var ChainLogDegradedBanner = () => {
|
|
|
27655
28522
|
// src/application/ui/tui/components/progress-overlay.tsx
|
|
27656
28523
|
import { promises as fs27 } from "fs";
|
|
27657
28524
|
import { join as join50 } from "path";
|
|
27658
|
-
import { useEffect as
|
|
28525
|
+
import { useEffect as useEffect45, useMemo as useMemo20, useState as useState50 } from "react";
|
|
27659
28526
|
import { Box as Box58, Text as Text60, useInput as useInput32 } from "ink";
|
|
27660
28527
|
import { jsx as jsx70, jsxs as jsxs59 } from "react/jsx-runtime";
|
|
27661
28528
|
var CHROME_ROWS = 10;
|
|
@@ -27676,7 +28543,7 @@ var ProgressOverlay = () => {
|
|
|
27676
28543
|
if (sprintId === void 0) return void 0;
|
|
27677
28544
|
return join50(String(storage2.dataRoot), "sprints", String(sprintId), "progress.md");
|
|
27678
28545
|
}, [sprintId, storage2.dataRoot]);
|
|
27679
|
-
|
|
28546
|
+
useEffect45(() => {
|
|
27680
28547
|
let cancelled = false;
|
|
27681
28548
|
if (progressPath === void 0) {
|
|
27682
28549
|
setState({ kind: "missing" });
|
|
@@ -27710,7 +28577,7 @@ var ProgressOverlay = () => {
|
|
|
27710
28577
|
};
|
|
27711
28578
|
}, [progressPath]);
|
|
27712
28579
|
const lineCount = state.kind === "ok" ? state.lines.length : 0;
|
|
27713
|
-
|
|
28580
|
+
useEffect45(() => {
|
|
27714
28581
|
setOffset(0);
|
|
27715
28582
|
}, [lineCount]);
|
|
27716
28583
|
const bodyRows = Math.max(MIN_BODY_ROWS, term.rows - CHROME_ROWS);
|
|
@@ -28215,7 +29082,8 @@ var bootstrap = async () => {
|
|
|
28215
29082
|
}
|
|
28216
29083
|
};
|
|
28217
29084
|
};
|
|
28218
|
-
var launchTui = async () => {
|
|
29085
|
+
var launchTui = async (options = {}) => {
|
|
29086
|
+
setImplementRoleOverrides(options.implementRoleOverrides);
|
|
28219
29087
|
let booted;
|
|
28220
29088
|
try {
|
|
28221
29089
|
booted = await bootstrap();
|
|
@@ -28236,6 +29104,54 @@ var launchTui = async () => {
|
|
|
28236
29104
|
}
|
|
28237
29105
|
};
|
|
28238
29106
|
|
|
29107
|
+
// src/application/ui/cli/parse-implement-role-overrides.ts
|
|
29108
|
+
var ALLOWED_PROVIDERS = /* @__PURE__ */ new Set(["claude-code", "github-copilot", "openai-codex"]);
|
|
29109
|
+
var isAiProvider2 = (v) => ALLOWED_PROVIDERS.has(v);
|
|
29110
|
+
var parseRole = (role, provider, model) => {
|
|
29111
|
+
if (provider !== void 0 && model === void 0) {
|
|
29112
|
+
return {
|
|
29113
|
+
ok: false,
|
|
29114
|
+
error: `--implement-${role}-provider requires --implement-${role}-model (both must be supplied together).`
|
|
29115
|
+
};
|
|
29116
|
+
}
|
|
29117
|
+
if (model !== void 0 && provider === void 0) {
|
|
29118
|
+
return {
|
|
29119
|
+
ok: false,
|
|
29120
|
+
error: `--implement-${role}-model requires --implement-${role}-provider (both must be supplied together).`
|
|
29121
|
+
};
|
|
29122
|
+
}
|
|
29123
|
+
if (provider === void 0 || model === void 0) {
|
|
29124
|
+
return { ok: true };
|
|
29125
|
+
}
|
|
29126
|
+
if (!isAiProvider2(provider)) {
|
|
29127
|
+
return {
|
|
29128
|
+
ok: false,
|
|
29129
|
+
error: `--implement-${role}-provider: '${provider}' is not a supported provider (claude-code | github-copilot | openai-codex).`
|
|
29130
|
+
};
|
|
29131
|
+
}
|
|
29132
|
+
const trimmedModel = model.trim();
|
|
29133
|
+
if (trimmedModel.length === 0) {
|
|
29134
|
+
return { ok: false, error: `--implement-${role}-model must be a non-empty string.` };
|
|
29135
|
+
}
|
|
29136
|
+
return { ok: true, row: { provider, model: trimmedModel } };
|
|
29137
|
+
};
|
|
29138
|
+
var parseImplementRoleOverrides = (input) => {
|
|
29139
|
+
const generator = parseRole("generator", input.generatorProvider, input.generatorModel);
|
|
29140
|
+
if (!generator.ok) return { ok: false, error: generator.error };
|
|
29141
|
+
const evaluator = parseRole("evaluator", input.evaluatorProvider, input.evaluatorModel);
|
|
29142
|
+
if (!evaluator.ok) return { ok: false, error: evaluator.error };
|
|
29143
|
+
if (generator.row === void 0 && evaluator.row === void 0) {
|
|
29144
|
+
return { ok: true, overrides: void 0 };
|
|
29145
|
+
}
|
|
29146
|
+
return {
|
|
29147
|
+
ok: true,
|
|
29148
|
+
overrides: {
|
|
29149
|
+
...generator.row !== void 0 ? { generator: generator.row } : {},
|
|
29150
|
+
...evaluator.row !== void 0 ? { evaluator: evaluator.row } : {}
|
|
29151
|
+
}
|
|
29152
|
+
};
|
|
29153
|
+
};
|
|
29154
|
+
|
|
28239
29155
|
// src/integration/observability/sinks/null-sink.ts
|
|
28240
29156
|
var nullSink = () => ({
|
|
28241
29157
|
emit() {
|
|
@@ -28423,6 +29339,15 @@ var registerDoctorCommand = (program) => {
|
|
|
28423
29339
|
};
|
|
28424
29340
|
|
|
28425
29341
|
// src/application/ui/cli/commands/settings.ts
|
|
29342
|
+
var AI_PROVIDERS3 = ["claude-code", "github-copilot", "openai-codex"];
|
|
29343
|
+
var isAiProvider3 = (raw) => AI_PROVIDERS3.includes(raw);
|
|
29344
|
+
var parseProviderKey = (key) => {
|
|
29345
|
+
const implementMatch = /^ai\.implement\.(generator|evaluator)\.provider$/.exec(key);
|
|
29346
|
+
if (implementMatch !== null) return { flow: "implement", role: implementMatch[1] };
|
|
29347
|
+
const flatMatch = /^ai\.(refine|plan|readiness|ideate)\.provider$/.exec(key);
|
|
29348
|
+
if (flatMatch !== null) return { flow: flatMatch[1] };
|
|
29349
|
+
return void 0;
|
|
29350
|
+
};
|
|
28426
29351
|
var registerSettingsCommand = (program) => {
|
|
28427
29352
|
const settings = program.command("settings").description("inspect and mutate ralphctl settings");
|
|
28428
29353
|
settings.command("show").description("print the current settings as JSON").action(async () => {
|
|
@@ -28440,6 +29365,36 @@ var registerSettingsCommand = (program) => {
|
|
|
28440
29365
|
});
|
|
28441
29366
|
settings.command("set <key> <value>").description("mutate one setting and persist (read-modify-write, schema-validated)").action(async (key, value) => {
|
|
28442
29367
|
const { deps } = await bootstrapCli();
|
|
29368
|
+
const providerKey = parseProviderKey(key);
|
|
29369
|
+
if (providerKey !== void 0) {
|
|
29370
|
+
if (!isAiProvider3(value)) {
|
|
29371
|
+
process.stderr.write(
|
|
29372
|
+
`error: '${value}' is not a recognised provider (expected one of: ${AI_PROVIDERS3.join(", ")})
|
|
29373
|
+
`
|
|
29374
|
+
);
|
|
29375
|
+
process.exit(1);
|
|
29376
|
+
return;
|
|
29377
|
+
}
|
|
29378
|
+
const providerFlow = createSettingsSetProviderFlow({ settingsRepo: deps.settingsRepo });
|
|
29379
|
+
const saved2 = await providerFlow.execute({
|
|
29380
|
+
input: {
|
|
29381
|
+
flow: providerKey.flow,
|
|
29382
|
+
provider: value,
|
|
29383
|
+
...providerKey.role !== void 0 ? { role: providerKey.role } : {}
|
|
29384
|
+
}
|
|
29385
|
+
});
|
|
29386
|
+
if (!saved2.ok) {
|
|
29387
|
+
const err = saved2.error.error;
|
|
29388
|
+
const hint = "hint" in err && typeof err.hint === "string" ? ` (${err.hint})` : "";
|
|
29389
|
+
process.stderr.write(`error: ${err.message}${hint}
|
|
29390
|
+
`);
|
|
29391
|
+
process.exit(1);
|
|
29392
|
+
return;
|
|
29393
|
+
}
|
|
29394
|
+
process.stdout.write(`${key} = ${value}
|
|
29395
|
+
`);
|
|
29396
|
+
return;
|
|
29397
|
+
}
|
|
28443
29398
|
const showFlow = createSettingsShowFlow({ settingsRepo: deps.settingsRepo });
|
|
28444
29399
|
const current = await showFlow.execute({ input: void 0 });
|
|
28445
29400
|
if (!current.ok) {
|
|
@@ -29568,8 +30523,32 @@ var isYes = (answer) => {
|
|
|
29568
30523
|
// src/application/ui/cli/cli.ts
|
|
29569
30524
|
var runCli2 = async (argv) => {
|
|
29570
30525
|
const program = new Command();
|
|
29571
|
-
program.name("ralphctl").description("ralphctl \u2014 interactive TUI and CLI").version(CLI_METADATA.currentVersion, "-v, --version", "show version").
|
|
29572
|
-
|
|
30526
|
+
program.name("ralphctl").description("ralphctl \u2014 interactive TUI and CLI").version(CLI_METADATA.currentVersion, "-v, --version", "show version").option(
|
|
30527
|
+
"--implement-generator-provider <provider>",
|
|
30528
|
+
"override settings.ai.implement.generator.provider for this launch (requires --implement-generator-model)"
|
|
30529
|
+
).option(
|
|
30530
|
+
"--implement-generator-model <model>",
|
|
30531
|
+
"override settings.ai.implement.generator.model for this launch (requires --implement-generator-provider)"
|
|
30532
|
+
).option(
|
|
30533
|
+
"--implement-evaluator-provider <provider>",
|
|
30534
|
+
"override settings.ai.implement.evaluator.provider for this launch (requires --implement-evaluator-model)"
|
|
30535
|
+
).option(
|
|
30536
|
+
"--implement-evaluator-model <model>",
|
|
30537
|
+
"override settings.ai.implement.evaluator.model for this launch (requires --implement-evaluator-provider)"
|
|
30538
|
+
).action(async (opts) => {
|
|
30539
|
+
const parsed = parseImplementRoleOverrides({
|
|
30540
|
+
...typeof opts.implementGeneratorProvider === "string" ? { generatorProvider: opts.implementGeneratorProvider } : {},
|
|
30541
|
+
...typeof opts.implementGeneratorModel === "string" ? { generatorModel: opts.implementGeneratorModel } : {},
|
|
30542
|
+
...typeof opts.implementEvaluatorProvider === "string" ? { evaluatorProvider: opts.implementEvaluatorProvider } : {},
|
|
30543
|
+
...typeof opts.implementEvaluatorModel === "string" ? { evaluatorModel: opts.implementEvaluatorModel } : {}
|
|
30544
|
+
});
|
|
30545
|
+
if (!parsed.ok) {
|
|
30546
|
+
process.stderr.write(`ralphctl: ${parsed.error}
|
|
30547
|
+
`);
|
|
30548
|
+
process.exitCode = 1;
|
|
30549
|
+
return;
|
|
30550
|
+
}
|
|
30551
|
+
await launchTui({ ...parsed.overrides !== void 0 ? { implementRoleOverrides: parsed.overrides } : {} });
|
|
29573
30552
|
});
|
|
29574
30553
|
registerExportRequirementsCommand(program);
|
|
29575
30554
|
registerExportContextCommand(program);
|