@promptbook/cli 0.114.0-19 → 0.114.0-20
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/README.md +5 -5
- package/esm/index.es.js +1586 -1213
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/common/CoderRunControlFeedback.d.ts +54 -0
- package/esm/scripts/run-codex-prompts/common/applyCoderRunControlKey.d.ts +14 -0
- package/esm/scripts/run-codex-prompts/common/buildCoderRunControlKeyBadge.d.ts +7 -0
- package/esm/scripts/run-codex-prompts/common/formatCoderRunControlFeedback.d.ts +13 -0
- package/esm/scripts/run-codex-prompts/common/listenForCoderRunControls.d.ts +17 -0
- package/esm/scripts/run-codex-prompts/common/waitForPause.d.ts +0 -8
- package/esm/scripts/run-codex-prompts/common/waitForSkippableWorldTimeDeadline.d.ts +15 -0
- package/esm/scripts/run-codex-prompts/prompts/buildPromptLabel.d.ts +6 -0
- package/esm/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +7 -0
- package/esm/scripts/run-codex-prompts/prompts/listPromptsByStatus.d.ts +7 -0
- package/esm/scripts/run-codex-prompts/prompts/markPromptDone.d.ts +15 -4
- package/esm/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +34 -1
- package/esm/scripts/run-codex-prompts/prompts/promptRunnerAttribution.d.ts +24 -0
- package/esm/scripts/run-codex-prompts/prompts/resolveInterruptedPrompt.d.ts +9 -0
- package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +15 -1
- package/esm/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +1 -1
- package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +6 -1
- package/esm/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +11 -0
- package/esm/src/cli/cli-commands/coder/GitChangesMode.d.ts +28 -0
- package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -2
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/cli/cli-commands/agents-server/startAgentsServer/createLocalAgentRunOptions.ts +1 -1
- package/src/cli/cli-commands/coder/GitChangesMode.ts +35 -0
- package/src/cli/cli-commands/coder/run.ts +3 -1
- package/src/cli/cli-commands/coder/server.ts +1 -1
- package/src/cli/cli-commands/common/promptRunnerCliOptions.ts +22 -4
- package/src/other/templates/getTemplatesPipelineCollection.ts +778 -706
- package/src/utils/agents/terminalAgentAvatarVisual.ts +1 -1
- package/src/version.ts +2 -2
- package/src/versions.txt +1 -0
- package/umd/index.umd.js +1586 -1213
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/common/CoderRunControlFeedback.d.ts +54 -0
- package/umd/scripts/run-codex-prompts/common/applyCoderRunControlKey.d.ts +14 -0
- package/umd/scripts/run-codex-prompts/common/buildCoderRunControlKeyBadge.d.ts +7 -0
- package/umd/scripts/run-codex-prompts/common/formatCoderRunControlFeedback.d.ts +13 -0
- package/umd/scripts/run-codex-prompts/common/listenForCoderRunControls.d.ts +17 -0
- package/umd/scripts/run-codex-prompts/common/waitForPause.d.ts +0 -8
- package/umd/scripts/run-codex-prompts/common/waitForSkippableWorldTimeDeadline.d.ts +15 -0
- package/umd/scripts/run-codex-prompts/prompts/buildPromptLabel.d.ts +6 -0
- package/umd/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +7 -0
- package/umd/scripts/run-codex-prompts/prompts/listPromptsByStatus.d.ts +7 -0
- package/umd/scripts/run-codex-prompts/prompts/markPromptDone.d.ts +15 -4
- package/umd/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +34 -1
- package/umd/scripts/run-codex-prompts/prompts/promptRunnerAttribution.d.ts +24 -0
- package/umd/scripts/run-codex-prompts/prompts/resolveInterruptedPrompt.d.ts +9 -0
- package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +15 -1
- package/umd/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +1 -1
- package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +6 -1
- package/umd/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +11 -0
- package/umd/src/cli/cli-commands/coder/GitChangesMode.d.ts +28 -0
- package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -2
- package/umd/src/version.d.ts +1 -1
- package/esm/scripts/run-codex-prompts/ui/buildCoderRunOctopusVisual.d.ts +0 -13
- package/umd/scripts/run-codex-prompts/ui/buildCoderRunOctopusVisual.d.ts +0 -13
package/umd/index.umd.js
CHANGED
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
* @generated
|
|
59
59
|
* @see https://github.com/webgptorg/promptbook
|
|
60
60
|
*/
|
|
61
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.114.0-
|
|
61
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.114.0-20';
|
|
62
62
|
/**
|
|
63
63
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
64
64
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -1760,6 +1760,32 @@
|
|
|
1760
1760
|
*/
|
|
1761
1761
|
const PTBK_THINKING_LEVEL_ENV = 'PTBK_THINKING_LEVEL';
|
|
1762
1762
|
|
|
1763
|
+
/**
|
|
1764
|
+
* Supported values of the `--git-changes` option, which decides what happens with a dirty working tree.
|
|
1765
|
+
*
|
|
1766
|
+
* - `fail` — refuse to start while the working tree has uncommitted changes
|
|
1767
|
+
* - `ignore` — start anyway and leave the uncommitted changes where they are
|
|
1768
|
+
* - `continue` — resume the single prompt which was left in the middle of its implementation
|
|
1769
|
+
*
|
|
1770
|
+
* @private internal shared utility of `ptbk coder run`
|
|
1771
|
+
*/
|
|
1772
|
+
const GIT_CHANGES_MODE_VALUES = ['fail', 'ignore', 'continue'];
|
|
1773
|
+
/**
|
|
1774
|
+
* Mode applied when `--git-changes` is not used at all.
|
|
1775
|
+
*
|
|
1776
|
+
* @private internal shared utility of `ptbk coder run`
|
|
1777
|
+
*/
|
|
1778
|
+
const DEFAULT_GIT_CHANGES_MODE = 'fail';
|
|
1779
|
+
/**
|
|
1780
|
+
* Checks whether a value is a supported `--git-changes` mode.
|
|
1781
|
+
*
|
|
1782
|
+
* @private internal shared utility of `ptbk coder run`
|
|
1783
|
+
*/
|
|
1784
|
+
function isGitChangesMode(value) {
|
|
1785
|
+
return GIT_CHANGES_MODE_VALUES.includes(value);
|
|
1786
|
+
}
|
|
1787
|
+
// Note: [🟡] Code for CLI command [run](src/cli/cli-commands/coder/run.ts) should never be published outside of `@promptbook/cli`
|
|
1788
|
+
|
|
1763
1789
|
/**
|
|
1764
1790
|
* Supported thinking-level CLI values for coding-agent runners with configurable reasoning effort.
|
|
1765
1791
|
*
|
|
@@ -1818,6 +1844,17 @@
|
|
|
1818
1844
|
|
|
1819
1845
|
OpenAI examples: gpt-5.2-codex, default
|
|
1820
1846
|
Gemini examples: gemini-3-flash-preview, default
|
|
1847
|
+
`);
|
|
1848
|
+
/**
|
|
1849
|
+
* Commander description for the `--git-changes` option.
|
|
1850
|
+
*
|
|
1851
|
+
* @private internal utility of `promptbookCli`
|
|
1852
|
+
*/
|
|
1853
|
+
const GIT_CHANGES_OPTION_DESCRIPTION = _spaceTrim.spaceTrim(`
|
|
1854
|
+
Decide what happens when the working tree has uncommitted changes before a prompt starts:
|
|
1855
|
+
- fail: refuse to start and ask for a commit or a stash (the default)
|
|
1856
|
+
- ignore: start anyway and leave the uncommitted changes where they are
|
|
1857
|
+
- continue: resume the single prompt which was left in the middle of its implementation
|
|
1821
1858
|
`);
|
|
1822
1859
|
/**
|
|
1823
1860
|
* Registers runner selection flags on a command.
|
|
@@ -1848,7 +1885,9 @@
|
|
|
1848
1885
|
function addPromptRunnerExecutionOptions(command) {
|
|
1849
1886
|
addPromptRunnerRuntimeOptions(command);
|
|
1850
1887
|
command.option('--no-commit', 'Leave successful changes in the working directory instead of creating git commits');
|
|
1851
|
-
command.
|
|
1888
|
+
command.addOption(new commander.Option('--git-changes <mode>', GIT_CHANGES_OPTION_DESCRIPTION)
|
|
1889
|
+
.choices([...GIT_CHANGES_MODE_VALUES])
|
|
1890
|
+
.default(DEFAULT_GIT_CHANGES_MODE));
|
|
1852
1891
|
command.option('--no-normalize-line-endings', 'Disable automatic LF normalization for files changed in each coding round');
|
|
1853
1892
|
command.option('--auto-push', 'Automatically git push after each commit', false);
|
|
1854
1893
|
command.option('--auto-pull', 'Automatically git pull before the first and each subsequent prompt', false);
|
|
@@ -1859,11 +1898,12 @@
|
|
|
1859
1898
|
* @private internal utility of `promptbookCli`
|
|
1860
1899
|
*/
|
|
1861
1900
|
function normalizePromptRunnerCliOptions(cliOptions, options) {
|
|
1901
|
+
var _a;
|
|
1862
1902
|
const selectionOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, options);
|
|
1863
1903
|
return {
|
|
1864
1904
|
...selectionOptions,
|
|
1865
1905
|
noCommit: !cliOptions.commit,
|
|
1866
|
-
|
|
1906
|
+
gitChanges: (_a = cliOptions.gitChanges) !== null && _a !== void 0 ? _a : DEFAULT_GIT_CHANGES_MODE,
|
|
1867
1907
|
normalizeLineEndings: cliOptions.normalizeLineEndings,
|
|
1868
1908
|
autoPush: cliOptions.autoPush,
|
|
1869
1909
|
autoPull: cliOptions.autoPull,
|
|
@@ -22762,7 +22802,7 @@
|
|
|
22762
22802
|
Git working tree has changes outside of \`${normalizeGitPath(AGENT_QUEUED_MESSAGES_DIRECTORY_PATH)}/\`.
|
|
22763
22803
|
|
|
22764
22804
|
Please commit or stash unrelated changes before running \`ptbk agent-folder\`
|
|
22765
|
-
OR run with \`--
|
|
22805
|
+
OR run with \`--git-changes ignore\`.
|
|
22766
22806
|
|
|
22767
22807
|
Unrelated changed files:
|
|
22768
22808
|
${unrelatedChangedFiles.map((relativePath) => `- \`${relativePath}\``).join('\n')}
|
|
@@ -22818,7 +22858,7 @@
|
|
|
22818
22858
|
* Runs the clean working tree guard unless the user explicitly disabled it.
|
|
22819
22859
|
*/
|
|
22820
22860
|
async function ensureCleanQueueIfNeeded(projectPath, options) {
|
|
22821
|
-
if (options.
|
|
22861
|
+
if (options.gitChanges !== 'fail') {
|
|
22822
22862
|
return;
|
|
22823
22863
|
}
|
|
22824
22864
|
await ensureWorkingTreeCleanForAgentQueue(projectPath);
|
|
@@ -23340,6 +23380,58 @@
|
|
|
23340
23380
|
*/
|
|
23341
23381
|
const WAITING_FOR_MESSAGE_LABEL = 'Waiting for the message';
|
|
23342
23382
|
|
|
23383
|
+
/**
|
|
23384
|
+
* Colors of the key badges, so one control key looks the same in its pill and in its answer.
|
|
23385
|
+
*
|
|
23386
|
+
* @private internal constant of `ptbk coder` terminal controls
|
|
23387
|
+
*/
|
|
23388
|
+
const CODER_RUN_CONTROL_KEY_BADGE_COLORIZERS = {
|
|
23389
|
+
P: (badgeText) => colors__default["default"].bgYellow.black(badgeText),
|
|
23390
|
+
S: (badgeText) => colors__default["default"].bgCyan.black(badgeText),
|
|
23391
|
+
X: (badgeText) => colors__default["default"].bgBlue.white(badgeText),
|
|
23392
|
+
};
|
|
23393
|
+
/**
|
|
23394
|
+
* Builds the colored badge of one control key, for example ` S `.
|
|
23395
|
+
*
|
|
23396
|
+
* @private internal utility of `ptbk coder` terminal controls
|
|
23397
|
+
*/
|
|
23398
|
+
function buildCoderRunControlKeyBadge(controlKey) {
|
|
23399
|
+
return CODER_RUN_CONTROL_KEY_BADGE_COLORIZERS[controlKey](` ${controlKey} `);
|
|
23400
|
+
}
|
|
23401
|
+
|
|
23402
|
+
/**
|
|
23403
|
+
* Marker printed before every control answer so it stands out from the runner output.
|
|
23404
|
+
*
|
|
23405
|
+
* @private internal constant of `ptbk coder` terminal controls
|
|
23406
|
+
*/
|
|
23407
|
+
const CODER_RUN_CONTROL_FEEDBACK_MARKER = '»';
|
|
23408
|
+
/**
|
|
23409
|
+
* Colors of the control answers by their meaning.
|
|
23410
|
+
*
|
|
23411
|
+
* @private internal constant of `ptbk coder` terminal controls
|
|
23412
|
+
*/
|
|
23413
|
+
const CODER_RUN_CONTROL_FEEDBACK_COLORIZERS = {
|
|
23414
|
+
success: (message) => colors__default["default"].green(message),
|
|
23415
|
+
info: (message) => colors__default["default"].cyan(message),
|
|
23416
|
+
warning: (message) => colors__default["default"].yellow(message),
|
|
23417
|
+
};
|
|
23418
|
+
/**
|
|
23419
|
+
* Formats one control answer as a single colored terminal line.
|
|
23420
|
+
*
|
|
23421
|
+
* The same line is printed by the plain console listener and rendered inside the `Controls` box of the
|
|
23422
|
+
* rich terminal UI, so both modes answer a pressed control key identically.
|
|
23423
|
+
*
|
|
23424
|
+
* @param feedback Answer of the runner to the pressed control key.
|
|
23425
|
+
* @param repeatCount How many times this exact answer was given in a row, rendered from the second time on.
|
|
23426
|
+
*
|
|
23427
|
+
* @private internal utility of `ptbk coder` terminal controls
|
|
23428
|
+
*/
|
|
23429
|
+
function formatCoderRunControlFeedback(feedback, repeatCount = 1) {
|
|
23430
|
+
const colorizeMessage = CODER_RUN_CONTROL_FEEDBACK_COLORIZERS[feedback.tone];
|
|
23431
|
+
const repeatSuffix = repeatCount > 1 ? colors__default["default"].gray(` (×${repeatCount})`) : '';
|
|
23432
|
+
return `${colors__default["default"].gray(CODER_RUN_CONTROL_FEEDBACK_MARKER)} ${buildCoderRunControlKeyBadge(feedback.controlKey)} ${colorizeMessage(feedback.message)}${repeatSuffix}`;
|
|
23433
|
+
}
|
|
23434
|
+
|
|
23343
23435
|
/**
|
|
23344
23436
|
* Centers an ANSI-colored line within the available frame width.
|
|
23345
23437
|
*
|
|
@@ -23514,7 +23606,8 @@
|
|
|
23514
23606
|
return {
|
|
23515
23607
|
badge: buildRunningPhaseBadge(phase),
|
|
23516
23608
|
stateMessage: statusMessage,
|
|
23517
|
-
|
|
23609
|
+
// Note: The two paused variants above deliberately recolor the `P` badge, everything else uses the shared one
|
|
23610
|
+
pauseControl: buildCoderRunControlKeyBadge('P') + colors__default["default"].white(' Pause'),
|
|
23518
23611
|
};
|
|
23519
23612
|
}
|
|
23520
23613
|
/**
|
|
@@ -23555,20 +23648,34 @@
|
|
|
23555
23648
|
],
|
|
23556
23649
|
});
|
|
23557
23650
|
}
|
|
23651
|
+
/**
|
|
23652
|
+
* Builds the body of the `Controls` box.
|
|
23653
|
+
*
|
|
23654
|
+
* The pills are always shown, and right after a control key was pressed the answer of the runner to
|
|
23655
|
+
* that key is shown underneath, so no key press ever leaves the frame unchanged.
|
|
23656
|
+
*/
|
|
23657
|
+
function buildControlsBoxLines(options) {
|
|
23658
|
+
const { controlPills, controlFeedback } = options;
|
|
23659
|
+
const controlPillsLine = controlPills.join(' ');
|
|
23660
|
+
if (controlFeedback === undefined) {
|
|
23661
|
+
return [controlPillsLine];
|
|
23662
|
+
}
|
|
23663
|
+
return [controlPillsLine, formatCoderRunControlFeedback(controlFeedback, controlFeedback.repeatCount)];
|
|
23664
|
+
}
|
|
23558
23665
|
/**
|
|
23559
23666
|
* Builds the `S` wait-skip control label.
|
|
23560
23667
|
*/
|
|
23561
23668
|
function buildSkipCurrentWaitControl() {
|
|
23562
|
-
return
|
|
23669
|
+
return buildCoderRunControlKeyBadge('S') + colors__default["default"].white(' Skip current waiting');
|
|
23563
23670
|
}
|
|
23564
23671
|
/**
|
|
23565
23672
|
* Builds the dynamic `X` control label.
|
|
23566
23673
|
*/
|
|
23567
23674
|
function buildEndAfterCurrentPromptControl(isEndAfterCurrentPromptRequested, sessionTotal) {
|
|
23568
23675
|
if (isEndAfterCurrentPromptRequested) {
|
|
23569
|
-
return
|
|
23676
|
+
return buildCoderRunControlKeyBadge('X') + colors__default["default"].white(` Do all ${formatControlPromptCount(sessionTotal)}`);
|
|
23570
23677
|
}
|
|
23571
|
-
return
|
|
23678
|
+
return buildCoderRunControlKeyBadge('X') + colors__default["default"].white(' End with this prompt');
|
|
23572
23679
|
}
|
|
23573
23680
|
/**
|
|
23574
23681
|
* Formats one prompt count for the `X` control.
|
|
@@ -23786,10 +23893,13 @@
|
|
|
23786
23893
|
: [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
|
|
23787
23894
|
const userMessageBoxes = buildUserMessageBoxes(options, totalWidth);
|
|
23788
23895
|
const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
|
|
23789
|
-
const
|
|
23790
|
-
|
|
23791
|
-
|
|
23792
|
-
|
|
23896
|
+
const controlsBoxLines = buildControlsBoxLines({
|
|
23897
|
+
controlPills: buildControlPills({
|
|
23898
|
+
pauseControl: pausePresentation.pauseControl,
|
|
23899
|
+
pendingEnterLabel: options.pendingEnterLabel,
|
|
23900
|
+
}),
|
|
23901
|
+
controlFeedback: options.controlFeedback,
|
|
23902
|
+
});
|
|
23793
23903
|
const frame = [
|
|
23794
23904
|
...buildAgentRunInitialsVisual(options.config.localAgentName || 'Local Agent', totalWidth),
|
|
23795
23905
|
'',
|
|
@@ -23802,7 +23912,7 @@
|
|
|
23802
23912
|
if (options.errors.length > 0) {
|
|
23803
23913
|
frame.push(...renderBox('Errors', options.errors.map((errorLine) => `${colors__default["default"].red('✗')} ${errorLine}`), totalWidth, colors__default["default"].red.bold));
|
|
23804
23914
|
}
|
|
23805
|
-
frame.push(...renderBox('Controls',
|
|
23915
|
+
frame.push(...renderBox('Controls', controlsBoxLines, totalWidth, colors__default["default"].white.bold));
|
|
23806
23916
|
return frame;
|
|
23807
23917
|
}
|
|
23808
23918
|
/**
|
|
@@ -25124,6 +25234,254 @@
|
|
|
25124
25234
|
});
|
|
25125
25235
|
}
|
|
25126
25236
|
|
|
25237
|
+
/**
|
|
25238
|
+
* Default label used before the next pause checkpoint is known.
|
|
25239
|
+
*/
|
|
25240
|
+
const DEFAULT_PAUSE_TARGET_LABEL = 'the next task';
|
|
25241
|
+
/**
|
|
25242
|
+
* Current pause state.
|
|
25243
|
+
*/
|
|
25244
|
+
let pauseState = 'RUNNING';
|
|
25245
|
+
/**
|
|
25246
|
+
* Label of the next checkpoint where the requested pause will take effect.
|
|
25247
|
+
*/
|
|
25248
|
+
let pauseTargetLabel = DEFAULT_PAUSE_TARGET_LABEL;
|
|
25249
|
+
/**
|
|
25250
|
+
* Token of the currently active timed wait, if the runner is inside one.
|
|
25251
|
+
*/
|
|
25252
|
+
let activeSkippableWaitToken;
|
|
25253
|
+
/**
|
|
25254
|
+
* Whether the user requested the active timed wait to end immediately.
|
|
25255
|
+
*/
|
|
25256
|
+
let isSkipCurrentWaitRequested = false;
|
|
25257
|
+
/**
|
|
25258
|
+
* Whether the runner should stop after the current prompt round reaches its normal boundary.
|
|
25259
|
+
*/
|
|
25260
|
+
let isEndAfterCurrentPromptRequested$1 = false;
|
|
25261
|
+
/**
|
|
25262
|
+
* Promise resolvers waiting for the active timed wait to be skipped.
|
|
25263
|
+
*/
|
|
25264
|
+
const SKIP_CURRENT_WAIT_RESOLVERS = new Map();
|
|
25265
|
+
/**
|
|
25266
|
+
* Stores one new pause state in the shared runner controller.
|
|
25267
|
+
*/
|
|
25268
|
+
function setPauseState(nextPauseState) {
|
|
25269
|
+
pauseState = nextPauseState;
|
|
25270
|
+
}
|
|
25271
|
+
/**
|
|
25272
|
+
* Stores one new pause target label in the shared runner controller.
|
|
25273
|
+
*/
|
|
25274
|
+
function setPauseTargetLabel(nextPauseTargetLabel) {
|
|
25275
|
+
pauseTargetLabel = nextPauseTargetLabel.trim() || DEFAULT_PAUSE_TARGET_LABEL;
|
|
25276
|
+
}
|
|
25277
|
+
/**
|
|
25278
|
+
* Stores the dynamic end-after-current-prompt state.
|
|
25279
|
+
*/
|
|
25280
|
+
function setEndAfterCurrentPromptRequested(isRequested) {
|
|
25281
|
+
isEndAfterCurrentPromptRequested$1 = isRequested;
|
|
25282
|
+
}
|
|
25283
|
+
/**
|
|
25284
|
+
* Applies the same three-state toggle used by the `P` hotkey.
|
|
25285
|
+
*/
|
|
25286
|
+
function togglePauseState() {
|
|
25287
|
+
if (pauseState === 'RUNNING') {
|
|
25288
|
+
setPauseState('PAUSING');
|
|
25289
|
+
return 'REQUESTED_PAUSE';
|
|
25290
|
+
}
|
|
25291
|
+
if (pauseState === 'PAUSING') {
|
|
25292
|
+
setPauseState('RUNNING');
|
|
25293
|
+
resetPauseTargetLabel();
|
|
25294
|
+
return 'CANCELLED_PAUSE';
|
|
25295
|
+
}
|
|
25296
|
+
setPauseState('RUNNING');
|
|
25297
|
+
resetPauseTargetLabel();
|
|
25298
|
+
return 'RESUMED';
|
|
25299
|
+
}
|
|
25300
|
+
/**
|
|
25301
|
+
* Applies the two-state toggle used by the `X` hotkey.
|
|
25302
|
+
*/
|
|
25303
|
+
function toggleEndAfterCurrentPromptState() {
|
|
25304
|
+
if (!isEndAfterCurrentPromptRequested$1) {
|
|
25305
|
+
setEndAfterCurrentPromptRequested(true);
|
|
25306
|
+
return 'REQUESTED_END';
|
|
25307
|
+
}
|
|
25308
|
+
setEndAfterCurrentPromptRequested(false);
|
|
25309
|
+
return 'CANCELLED_END';
|
|
25310
|
+
}
|
|
25311
|
+
/**
|
|
25312
|
+
* Returns whether the dynamic end-after-current-prompt control is active.
|
|
25313
|
+
*/
|
|
25314
|
+
function getEndAfterCurrentPromptState() {
|
|
25315
|
+
return isEndAfterCurrentPromptRequested$1;
|
|
25316
|
+
}
|
|
25317
|
+
/**
|
|
25318
|
+
* Starts one timed wait which can be skipped by the `S` hotkey.
|
|
25319
|
+
*/
|
|
25320
|
+
function beginSkippableWait() {
|
|
25321
|
+
const waitToken = Symbol('coder-run-skippable-wait');
|
|
25322
|
+
activeSkippableWaitToken = waitToken;
|
|
25323
|
+
isSkipCurrentWaitRequested = false;
|
|
25324
|
+
SKIP_CURRENT_WAIT_RESOLVERS.clear();
|
|
25325
|
+
return waitToken;
|
|
25326
|
+
}
|
|
25327
|
+
/**
|
|
25328
|
+
* Finishes one timed wait and clears any skip request that belonged to it.
|
|
25329
|
+
*/
|
|
25330
|
+
function finishSkippableWait(waitToken) {
|
|
25331
|
+
if (activeSkippableWaitToken !== waitToken) {
|
|
25332
|
+
return;
|
|
25333
|
+
}
|
|
25334
|
+
activeSkippableWaitToken = undefined;
|
|
25335
|
+
isSkipCurrentWaitRequested = false;
|
|
25336
|
+
resolveSkipCurrentWaitResolvers(waitToken);
|
|
25337
|
+
SKIP_CURRENT_WAIT_RESOLVERS.delete(waitToken);
|
|
25338
|
+
}
|
|
25339
|
+
/**
|
|
25340
|
+
* Requests that the currently active timed wait ends immediately.
|
|
25341
|
+
*/
|
|
25342
|
+
function requestSkipCurrentWait() {
|
|
25343
|
+
if (activeSkippableWaitToken === undefined) {
|
|
25344
|
+
return 'NO_ACTIVE_WAIT';
|
|
25345
|
+
}
|
|
25346
|
+
isSkipCurrentWaitRequested = true;
|
|
25347
|
+
resolveSkipCurrentWaitResolvers(activeSkippableWaitToken);
|
|
25348
|
+
return 'REQUESTED_SKIP';
|
|
25349
|
+
}
|
|
25350
|
+
/**
|
|
25351
|
+
* Returns whether one timed wait should end early.
|
|
25352
|
+
*/
|
|
25353
|
+
function shouldSkipCurrentWait(waitToken) {
|
|
25354
|
+
return activeSkippableWaitToken === waitToken && isSkipCurrentWaitRequested;
|
|
25355
|
+
}
|
|
25356
|
+
/**
|
|
25357
|
+
* Waits for either a timeout or an `S` hotkey skip request.
|
|
25358
|
+
*/
|
|
25359
|
+
async function waitForSkippableMilliseconds(waitToken, durationMs) {
|
|
25360
|
+
if (durationMs <= 0 || shouldSkipCurrentWait(waitToken)) {
|
|
25361
|
+
return;
|
|
25362
|
+
}
|
|
25363
|
+
await new Promise((resolve) => {
|
|
25364
|
+
let timeout = setTimeout(resolveWait, durationMs);
|
|
25365
|
+
const resolver = () => resolveWait();
|
|
25366
|
+
const resolvers = getSkipCurrentWaitResolvers(waitToken);
|
|
25367
|
+
resolvers.add(resolver);
|
|
25368
|
+
function resolveWait() {
|
|
25369
|
+
if (timeout !== undefined) {
|
|
25370
|
+
clearTimeout(timeout);
|
|
25371
|
+
timeout = undefined;
|
|
25372
|
+
}
|
|
25373
|
+
resolvers.delete(resolver);
|
|
25374
|
+
if (resolvers.size === 0) {
|
|
25375
|
+
SKIP_CURRENT_WAIT_RESOLVERS.delete(waitToken);
|
|
25376
|
+
}
|
|
25377
|
+
resolve();
|
|
25378
|
+
}
|
|
25379
|
+
});
|
|
25380
|
+
}
|
|
25381
|
+
/**
|
|
25382
|
+
* Restores all shared terminal controls to their default state.
|
|
25383
|
+
*/
|
|
25384
|
+
function resetCoderRunControls() {
|
|
25385
|
+
setPauseState('RUNNING');
|
|
25386
|
+
resetPauseTargetLabel();
|
|
25387
|
+
setEndAfterCurrentPromptRequested(false);
|
|
25388
|
+
if (activeSkippableWaitToken !== undefined) {
|
|
25389
|
+
resolveSkipCurrentWaitResolvers(activeSkippableWaitToken);
|
|
25390
|
+
}
|
|
25391
|
+
activeSkippableWaitToken = undefined;
|
|
25392
|
+
isSkipCurrentWaitRequested = false;
|
|
25393
|
+
SKIP_CURRENT_WAIT_RESOLVERS.clear();
|
|
25394
|
+
}
|
|
25395
|
+
/**
|
|
25396
|
+
* If the execution is paused, it will wait until it is resumed.
|
|
25397
|
+
*
|
|
25398
|
+
* @param options.silent - When `true`, suppresses console output (used when the terminal UI handles display).
|
|
25399
|
+
* @param options.onPaused - Callback invoked when entering the PAUSED state.
|
|
25400
|
+
* @param options.onResumed - Callback invoked when leaving the PAUSED state.
|
|
25401
|
+
*/
|
|
25402
|
+
async function checkPause(options) {
|
|
25403
|
+
var _a, _b;
|
|
25404
|
+
if (pauseState === 'PAUSING') {
|
|
25405
|
+
setPauseState('PAUSED');
|
|
25406
|
+
if (!(options === null || options === void 0 ? void 0 : options.silent)) {
|
|
25407
|
+
console.log(colors__default["default"].bgWhite.black(`Paused before ${getPauseTargetLabel()}`) +
|
|
25408
|
+
colors__default["default"].gray(' (Press "p" to resume)'));
|
|
25409
|
+
}
|
|
25410
|
+
(_a = options === null || options === void 0 ? void 0 : options.onPaused) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
25411
|
+
while (getPauseState() === 'PAUSED') {
|
|
25412
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
25413
|
+
}
|
|
25414
|
+
(_b = options === null || options === void 0 ? void 0 : options.onResumed) === null || _b === void 0 ? void 0 : _b.call(options);
|
|
25415
|
+
if (!(options === null || options === void 0 ? void 0 : options.silent)) {
|
|
25416
|
+
console.log(colors__default["default"].green('Resuming...'));
|
|
25417
|
+
}
|
|
25418
|
+
}
|
|
25419
|
+
}
|
|
25420
|
+
/**
|
|
25421
|
+
* Returns the current pause state for external consumers such as the terminal UI.
|
|
25422
|
+
*/
|
|
25423
|
+
function getPauseState() {
|
|
25424
|
+
return pauseState;
|
|
25425
|
+
}
|
|
25426
|
+
/**
|
|
25427
|
+
* Returns the label of the next checkpoint where pausing will take effect.
|
|
25428
|
+
*/
|
|
25429
|
+
function getPauseTargetLabel() {
|
|
25430
|
+
return pauseTargetLabel;
|
|
25431
|
+
}
|
|
25432
|
+
/**
|
|
25433
|
+
* Updates the label of the next pause checkpoint.
|
|
25434
|
+
*/
|
|
25435
|
+
function announcePauseTargetLabel(nextPauseTargetLabel) {
|
|
25436
|
+
setPauseTargetLabel(nextPauseTargetLabel);
|
|
25437
|
+
}
|
|
25438
|
+
/**
|
|
25439
|
+
* Restores the default generic pause target label.
|
|
25440
|
+
*/
|
|
25441
|
+
function resetPauseTargetLabel() {
|
|
25442
|
+
setPauseTargetLabel(DEFAULT_PAUSE_TARGET_LABEL);
|
|
25443
|
+
}
|
|
25444
|
+
/**
|
|
25445
|
+
* Requests a pause from an external controller (e.g. the Ink UI).
|
|
25446
|
+
*/
|
|
25447
|
+
function requestPause() {
|
|
25448
|
+
if (pauseState === 'RUNNING') {
|
|
25449
|
+
setPauseState('PAUSING');
|
|
25450
|
+
}
|
|
25451
|
+
}
|
|
25452
|
+
/**
|
|
25453
|
+
* Resumes execution from an external controller after a pause.
|
|
25454
|
+
*/
|
|
25455
|
+
function requestResume() {
|
|
25456
|
+
setPauseState('RUNNING');
|
|
25457
|
+
resetPauseTargetLabel();
|
|
25458
|
+
}
|
|
25459
|
+
/**
|
|
25460
|
+
* Gets or creates the resolver set for one timed wait.
|
|
25461
|
+
*/
|
|
25462
|
+
function getSkipCurrentWaitResolvers(waitToken) {
|
|
25463
|
+
const existingResolvers = SKIP_CURRENT_WAIT_RESOLVERS.get(waitToken);
|
|
25464
|
+
if (existingResolvers !== undefined) {
|
|
25465
|
+
return existingResolvers;
|
|
25466
|
+
}
|
|
25467
|
+
const resolvers = new Set();
|
|
25468
|
+
SKIP_CURRENT_WAIT_RESOLVERS.set(waitToken, resolvers);
|
|
25469
|
+
return resolvers;
|
|
25470
|
+
}
|
|
25471
|
+
/**
|
|
25472
|
+
* Resolves all pending skip waiters for one timed wait.
|
|
25473
|
+
*/
|
|
25474
|
+
function resolveSkipCurrentWaitResolvers(waitToken) {
|
|
25475
|
+
const resolvers = SKIP_CURRENT_WAIT_RESOLVERS.get(waitToken);
|
|
25476
|
+
if (resolvers === undefined) {
|
|
25477
|
+
return;
|
|
25478
|
+
}
|
|
25479
|
+
for (const resolver of resolvers) {
|
|
25480
|
+
resolver();
|
|
25481
|
+
}
|
|
25482
|
+
resolvers.clear();
|
|
25483
|
+
}
|
|
25484
|
+
|
|
25127
25485
|
/**
|
|
25128
25486
|
* Minimum timer duration used to avoid a zero-millisecond polling loop.
|
|
25129
25487
|
*/
|
|
@@ -25173,6 +25531,32 @@
|
|
|
25173
25531
|
await new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
25174
25532
|
}
|
|
25175
25533
|
|
|
25534
|
+
/**
|
|
25535
|
+
* Waits until one wall-clock deadline has passed, or until the user skips the wait with the `S` control.
|
|
25536
|
+
*
|
|
25537
|
+
* This is the single way `ptbk coder` waits for a deadline the user is allowed to cut short, so every
|
|
25538
|
+
* wait which shows the `S Skip current waiting` control really reacts to it: the pacing waits between
|
|
25539
|
+
* prompts, the cool-down after an error, the harness session-limit waits and the server keep-alive poll.
|
|
25540
|
+
*
|
|
25541
|
+
* @private internal utility of `ptbk coder` wait handling
|
|
25542
|
+
*/
|
|
25543
|
+
async function waitForSkippableWorldTimeDeadline(options) {
|
|
25544
|
+
const { deadlineTimeMs, pollIntervalMs, onTick } = options;
|
|
25545
|
+
const waitToken = beginSkippableWait();
|
|
25546
|
+
try {
|
|
25547
|
+
await waitUntilWorldTimeDeadline({
|
|
25548
|
+
deadlineTimeMs,
|
|
25549
|
+
pollIntervalMs,
|
|
25550
|
+
onTick,
|
|
25551
|
+
shouldStopWaiting: () => shouldSkipCurrentWait(waitToken),
|
|
25552
|
+
waitForMilliseconds: (waitDurationMs) => waitForSkippableMilliseconds(waitToken, waitDurationMs),
|
|
25553
|
+
});
|
|
25554
|
+
}
|
|
25555
|
+
finally {
|
|
25556
|
+
finishSkippableWait(waitToken);
|
|
25557
|
+
}
|
|
25558
|
+
}
|
|
25559
|
+
|
|
25176
25560
|
/**
|
|
25177
25561
|
* Base delimiter used for passing large prompts through stdin.
|
|
25178
25562
|
*/
|
|
@@ -25580,6 +25964,10 @@
|
|
|
25580
25964
|
}
|
|
25581
25965
|
/**
|
|
25582
25966
|
* Waits until the Claude Code session can be resumed, keeping terminal status clear.
|
|
25967
|
+
*
|
|
25968
|
+
* The wait runs in the `waiting` phase, where the terminal UI offers `S Skip current waiting`, so it is
|
|
25969
|
+
* a skippable wait: pressing `S` resumes the session with `--resume` immediately instead of sitting out
|
|
25970
|
+
* the reset window.
|
|
25583
25971
|
*/
|
|
25584
25972
|
async function waitForClaudeCodeSessionLimitReset(sessionLimit, resurrectionCount, options) {
|
|
25585
25973
|
var _a, _b;
|
|
@@ -25590,7 +25978,7 @@
|
|
|
25590
25978
|
if ((_a = options.shouldPrintLiveOutput) !== null && _a !== void 0 ? _a : true) {
|
|
25591
25979
|
console.warn(colors__default["default"].yellow(`[claude-code] Session limit detected for ${sessionLimit.sessionId}. Resurrection #${resurrectionCount} will resume with --resume after ${formatDurationMs(delayMs)}. ${resetSummary}`));
|
|
25592
25980
|
}
|
|
25593
|
-
await
|
|
25981
|
+
await waitForSkippableWorldTimeDeadline({
|
|
25594
25982
|
deadlineTimeMs: resetDeadlineTimeMs,
|
|
25595
25983
|
pollIntervalMs: CLAUDE_CODE_SESSION_RESURRECTION_POLL_MS,
|
|
25596
25984
|
onTick: async (remainingDelayMs) => {
|
|
@@ -27029,292 +27417,100 @@
|
|
|
27029
27417
|
}
|
|
27030
27418
|
|
|
27031
27419
|
/**
|
|
27032
|
-
*
|
|
27033
|
-
|
|
27034
|
-
|
|
27035
|
-
|
|
27036
|
-
*
|
|
27037
|
-
|
|
27038
|
-
|
|
27039
|
-
|
|
27040
|
-
*
|
|
27041
|
-
|
|
27042
|
-
let pauseTargetLabel = DEFAULT_PAUSE_TARGET_LABEL;
|
|
27043
|
-
/**
|
|
27044
|
-
* Token of the currently active timed wait, if the runner is inside one.
|
|
27045
|
-
*/
|
|
27046
|
-
let activeSkippableWaitToken;
|
|
27047
|
-
/**
|
|
27048
|
-
* Whether the user requested the active timed wait to end immediately.
|
|
27049
|
-
*/
|
|
27050
|
-
let isSkipCurrentWaitRequested = false;
|
|
27051
|
-
/**
|
|
27052
|
-
* Whether the runner should stop after the current prompt round reaches its normal boundary.
|
|
27053
|
-
*/
|
|
27054
|
-
let isEndAfterCurrentPromptRequested$1 = false;
|
|
27055
|
-
/**
|
|
27056
|
-
* Promise resolvers waiting for the active timed wait to be skipped.
|
|
27057
|
-
*/
|
|
27058
|
-
const SKIP_CURRENT_WAIT_RESOLVERS = new Map();
|
|
27059
|
-
/**
|
|
27060
|
-
* Stores one new pause state in the shared runner controller.
|
|
27061
|
-
*/
|
|
27062
|
-
function setPauseState(nextPauseState) {
|
|
27063
|
-
pauseState = nextPauseState;
|
|
27064
|
-
}
|
|
27065
|
-
/**
|
|
27066
|
-
* Stores one new pause target label in the shared runner controller.
|
|
27067
|
-
*/
|
|
27068
|
-
function setPauseTargetLabel(nextPauseTargetLabel) {
|
|
27069
|
-
pauseTargetLabel = nextPauseTargetLabel.trim() || DEFAULT_PAUSE_TARGET_LABEL;
|
|
27070
|
-
}
|
|
27071
|
-
/**
|
|
27072
|
-
* Stores the dynamic end-after-current-prompt state.
|
|
27073
|
-
*/
|
|
27074
|
-
function setEndAfterCurrentPromptRequested(isRequested) {
|
|
27075
|
-
isEndAfterCurrentPromptRequested$1 = isRequested;
|
|
27076
|
-
}
|
|
27077
|
-
/**
|
|
27078
|
-
* Applies the same three-state toggle used by the `P` hotkey.
|
|
27079
|
-
*/
|
|
27080
|
-
function togglePauseState() {
|
|
27081
|
-
if (pauseState === 'RUNNING') {
|
|
27082
|
-
setPauseState('PAUSING');
|
|
27083
|
-
return 'REQUESTED_PAUSE';
|
|
27084
|
-
}
|
|
27085
|
-
if (pauseState === 'PAUSING') {
|
|
27086
|
-
setPauseState('RUNNING');
|
|
27087
|
-
resetPauseTargetLabel();
|
|
27088
|
-
return 'CANCELLED_PAUSE';
|
|
27089
|
-
}
|
|
27090
|
-
setPauseState('RUNNING');
|
|
27091
|
-
resetPauseTargetLabel();
|
|
27092
|
-
return 'RESUMED';
|
|
27093
|
-
}
|
|
27094
|
-
/**
|
|
27095
|
-
* Applies the two-state toggle used by the `X` hotkey.
|
|
27096
|
-
*/
|
|
27097
|
-
function toggleEndAfterCurrentPromptState() {
|
|
27098
|
-
if (!isEndAfterCurrentPromptRequested$1) {
|
|
27099
|
-
setEndAfterCurrentPromptRequested(true);
|
|
27100
|
-
return 'REQUESTED_END';
|
|
27101
|
-
}
|
|
27102
|
-
setEndAfterCurrentPromptRequested(false);
|
|
27103
|
-
return 'CANCELLED_END';
|
|
27104
|
-
}
|
|
27105
|
-
/**
|
|
27106
|
-
* Returns whether the dynamic end-after-current-prompt control is active.
|
|
27107
|
-
*/
|
|
27108
|
-
function getEndAfterCurrentPromptState() {
|
|
27109
|
-
return isEndAfterCurrentPromptRequested$1;
|
|
27110
|
-
}
|
|
27111
|
-
/**
|
|
27112
|
-
* Starts one timed wait which can be skipped by the `S` hotkey.
|
|
27113
|
-
*/
|
|
27114
|
-
function beginSkippableWait() {
|
|
27115
|
-
const waitToken = Symbol('coder-run-skippable-wait');
|
|
27116
|
-
activeSkippableWaitToken = waitToken;
|
|
27117
|
-
isSkipCurrentWaitRequested = false;
|
|
27118
|
-
SKIP_CURRENT_WAIT_RESOLVERS.clear();
|
|
27119
|
-
return waitToken;
|
|
27120
|
-
}
|
|
27121
|
-
/**
|
|
27122
|
-
* Finishes one timed wait and clears any skip request that belonged to it.
|
|
27123
|
-
*/
|
|
27124
|
-
function finishSkippableWait(waitToken) {
|
|
27125
|
-
if (activeSkippableWaitToken !== waitToken) {
|
|
27126
|
-
return;
|
|
27127
|
-
}
|
|
27128
|
-
activeSkippableWaitToken = undefined;
|
|
27129
|
-
isSkipCurrentWaitRequested = false;
|
|
27130
|
-
resolveSkipCurrentWaitResolvers(waitToken);
|
|
27131
|
-
SKIP_CURRENT_WAIT_RESOLVERS.delete(waitToken);
|
|
27132
|
-
}
|
|
27133
|
-
/**
|
|
27134
|
-
* Requests that the currently active timed wait ends immediately.
|
|
27135
|
-
*/
|
|
27136
|
-
function requestSkipCurrentWait() {
|
|
27137
|
-
if (activeSkippableWaitToken === undefined) {
|
|
27138
|
-
return 'NO_ACTIVE_WAIT';
|
|
27139
|
-
}
|
|
27140
|
-
isSkipCurrentWaitRequested = true;
|
|
27141
|
-
resolveSkipCurrentWaitResolvers(activeSkippableWaitToken);
|
|
27142
|
-
return 'REQUESTED_SKIP';
|
|
27143
|
-
}
|
|
27144
|
-
/**
|
|
27145
|
-
* Returns whether one timed wait should end early.
|
|
27146
|
-
*/
|
|
27147
|
-
function shouldSkipCurrentWait(waitToken) {
|
|
27148
|
-
return activeSkippableWaitToken === waitToken && isSkipCurrentWaitRequested;
|
|
27149
|
-
}
|
|
27150
|
-
/**
|
|
27151
|
-
* Waits for either a timeout or an `S` hotkey skip request.
|
|
27420
|
+
* Applies the terminal control bound to one pressed key and describes what that key press did.
|
|
27421
|
+
*
|
|
27422
|
+
* This is the single place where the `P`, `S` and `X` keys are turned into runner actions, so the
|
|
27423
|
+
* plain console listener and the rich terminal UI always perform the same action and always answer
|
|
27424
|
+
* the user, even when the pressed control changed nothing.
|
|
27425
|
+
*
|
|
27426
|
+
* @param keyName Name of the pressed key as reported by the `keypress` event.
|
|
27427
|
+
* @returns The answer shown to the user, or `undefined` when the key is not a control key.
|
|
27428
|
+
*
|
|
27429
|
+
* @private internal utility of `ptbk coder` terminal controls
|
|
27152
27430
|
*/
|
|
27153
|
-
|
|
27154
|
-
|
|
27155
|
-
|
|
27431
|
+
function applyCoderRunControlKey(keyName) {
|
|
27432
|
+
switch (keyName) {
|
|
27433
|
+
case 'p':
|
|
27434
|
+
return describePauseToggle(togglePauseState());
|
|
27435
|
+
case 's':
|
|
27436
|
+
return describeSkipCurrentWait(requestSkipCurrentWait());
|
|
27437
|
+
case 'x':
|
|
27438
|
+
return describeEndAfterCurrentPrompt(toggleEndAfterCurrentPromptState());
|
|
27439
|
+
default:
|
|
27440
|
+
return undefined;
|
|
27156
27441
|
}
|
|
27157
|
-
await new Promise((resolve) => {
|
|
27158
|
-
let timeout = setTimeout(resolveWait, durationMs);
|
|
27159
|
-
const resolver = () => resolveWait();
|
|
27160
|
-
const resolvers = getSkipCurrentWaitResolvers(waitToken);
|
|
27161
|
-
resolvers.add(resolver);
|
|
27162
|
-
function resolveWait() {
|
|
27163
|
-
if (timeout !== undefined) {
|
|
27164
|
-
clearTimeout(timeout);
|
|
27165
|
-
timeout = undefined;
|
|
27166
|
-
}
|
|
27167
|
-
resolvers.delete(resolver);
|
|
27168
|
-
if (resolvers.size === 0) {
|
|
27169
|
-
SKIP_CURRENT_WAIT_RESOLVERS.delete(waitToken);
|
|
27170
|
-
}
|
|
27171
|
-
resolve();
|
|
27172
|
-
}
|
|
27173
|
-
});
|
|
27174
27442
|
}
|
|
27175
27443
|
/**
|
|
27176
|
-
*
|
|
27444
|
+
* Describes what pressing `P` did.
|
|
27445
|
+
*
|
|
27446
|
+
* @private helper of `applyCoderRunControlKey`
|
|
27177
27447
|
*/
|
|
27178
|
-
function
|
|
27179
|
-
|
|
27180
|
-
|
|
27181
|
-
|
|
27182
|
-
|
|
27183
|
-
|
|
27448
|
+
function describePauseToggle(toggleResult) {
|
|
27449
|
+
if (toggleResult === 'REQUESTED_PAUSE') {
|
|
27450
|
+
return {
|
|
27451
|
+
controlKey: 'P',
|
|
27452
|
+
message: `Pause requested, pausing before ${getPauseTargetLabel()}`,
|
|
27453
|
+
tone: 'warning',
|
|
27454
|
+
};
|
|
27184
27455
|
}
|
|
27185
|
-
|
|
27186
|
-
|
|
27187
|
-
|
|
27188
|
-
|
|
27189
|
-
|
|
27190
|
-
|
|
27191
|
-
*/
|
|
27192
|
-
function listenForCoderRunControls() {
|
|
27193
|
-
if (!process.stdin.isTTY) {
|
|
27194
|
-
return;
|
|
27456
|
+
if (toggleResult === 'CANCELLED_PAUSE') {
|
|
27457
|
+
return {
|
|
27458
|
+
controlKey: 'P',
|
|
27459
|
+
message: 'Pause cancelled, the run continues',
|
|
27460
|
+
tone: 'success',
|
|
27461
|
+
};
|
|
27195
27462
|
}
|
|
27196
|
-
|
|
27197
|
-
|
|
27198
|
-
|
|
27199
|
-
|
|
27200
|
-
|
|
27201
|
-
}
|
|
27202
|
-
if (key.name === 'p') {
|
|
27203
|
-
const toggleResult = togglePauseState();
|
|
27204
|
-
if (toggleResult === 'REQUESTED_PAUSE') {
|
|
27205
|
-
// Note: Using console.log here which adds a new line.
|
|
27206
|
-
// This is intentional to prevent the message from being overwritten.
|
|
27207
|
-
console.log(colors__default["default"].bgWhite('Pausing...'));
|
|
27208
|
-
}
|
|
27209
|
-
else if (toggleResult === 'CANCELLED_PAUSE') {
|
|
27210
|
-
console.log(colors__default["default"].green('Pause cancelled. Resuming...'));
|
|
27211
|
-
}
|
|
27212
|
-
}
|
|
27213
|
-
if (key.name === 's') {
|
|
27214
|
-
if (requestSkipCurrentWait() === 'REQUESTED_SKIP') {
|
|
27215
|
-
console.log(colors__default["default"].green('Skipping current wait...'));
|
|
27216
|
-
}
|
|
27217
|
-
}
|
|
27218
|
-
if (key.name === 'x') {
|
|
27219
|
-
const toggleResult = toggleEndAfterCurrentPromptState();
|
|
27220
|
-
if (toggleResult === 'REQUESTED_END') {
|
|
27221
|
-
console.log(colors__default["default"].yellow('Will end after the current prompt finishes.'));
|
|
27222
|
-
}
|
|
27223
|
-
else {
|
|
27224
|
-
console.log(colors__default["default"].green('End request cancelled. Continuing all prompts.'));
|
|
27225
|
-
}
|
|
27226
|
-
}
|
|
27227
|
-
});
|
|
27463
|
+
return {
|
|
27464
|
+
controlKey: 'P',
|
|
27465
|
+
message: 'Resuming the run',
|
|
27466
|
+
tone: 'success',
|
|
27467
|
+
};
|
|
27228
27468
|
}
|
|
27229
27469
|
/**
|
|
27230
|
-
*
|
|
27470
|
+
* Describes what pressing `S` did.
|
|
27231
27471
|
*
|
|
27232
|
-
* @
|
|
27233
|
-
* @param options.onPaused - Callback invoked when entering the PAUSED state.
|
|
27234
|
-
* @param options.onResumed - Callback invoked when leaving the PAUSED state.
|
|
27235
|
-
*/
|
|
27236
|
-
async function checkPause(options) {
|
|
27237
|
-
var _a, _b;
|
|
27238
|
-
if (pauseState === 'PAUSING') {
|
|
27239
|
-
setPauseState('PAUSED');
|
|
27240
|
-
if (!(options === null || options === void 0 ? void 0 : options.silent)) {
|
|
27241
|
-
console.log(colors__default["default"].bgWhite.black(`Paused before ${getPauseTargetLabel()}`) +
|
|
27242
|
-
colors__default["default"].gray(' (Press "p" to resume)'));
|
|
27243
|
-
}
|
|
27244
|
-
(_a = options === null || options === void 0 ? void 0 : options.onPaused) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
27245
|
-
while (getPauseState() === 'PAUSED') {
|
|
27246
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
27247
|
-
}
|
|
27248
|
-
(_b = options === null || options === void 0 ? void 0 : options.onResumed) === null || _b === void 0 ? void 0 : _b.call(options);
|
|
27249
|
-
if (!(options === null || options === void 0 ? void 0 : options.silent)) {
|
|
27250
|
-
console.log(colors__default["default"].green('Resuming...'));
|
|
27251
|
-
}
|
|
27252
|
-
}
|
|
27253
|
-
}
|
|
27254
|
-
/**
|
|
27255
|
-
* Returns the current pause state for external consumers such as the terminal UI.
|
|
27472
|
+
* @private helper of `applyCoderRunControlKey`
|
|
27256
27473
|
*/
|
|
27257
|
-
function
|
|
27258
|
-
|
|
27259
|
-
|
|
27260
|
-
|
|
27261
|
-
|
|
27262
|
-
|
|
27263
|
-
|
|
27264
|
-
return pauseTargetLabel;
|
|
27265
|
-
}
|
|
27266
|
-
/**
|
|
27267
|
-
* Updates the label of the next pause checkpoint.
|
|
27268
|
-
*/
|
|
27269
|
-
function announcePauseTargetLabel(nextPauseTargetLabel) {
|
|
27270
|
-
setPauseTargetLabel(nextPauseTargetLabel);
|
|
27271
|
-
}
|
|
27272
|
-
/**
|
|
27273
|
-
* Restores the default generic pause target label.
|
|
27274
|
-
*/
|
|
27275
|
-
function resetPauseTargetLabel() {
|
|
27276
|
-
setPauseTargetLabel(DEFAULT_PAUSE_TARGET_LABEL);
|
|
27277
|
-
}
|
|
27278
|
-
/**
|
|
27279
|
-
* Requests a pause from an external controller (e.g. the Ink UI).
|
|
27280
|
-
*/
|
|
27281
|
-
function requestPause() {
|
|
27282
|
-
if (pauseState === 'RUNNING') {
|
|
27283
|
-
setPauseState('PAUSING');
|
|
27474
|
+
function describeSkipCurrentWait(skipResult) {
|
|
27475
|
+
if (skipResult === 'REQUESTED_SKIP') {
|
|
27476
|
+
return {
|
|
27477
|
+
controlKey: 'S',
|
|
27478
|
+
message: 'Skipping the current waiting, continuing right now',
|
|
27479
|
+
tone: 'success',
|
|
27480
|
+
};
|
|
27284
27481
|
}
|
|
27482
|
+
return {
|
|
27483
|
+
controlKey: 'S',
|
|
27484
|
+
message: 'Nothing to skip, the coder is not waiting right now',
|
|
27485
|
+
tone: 'info',
|
|
27486
|
+
};
|
|
27285
27487
|
}
|
|
27286
27488
|
/**
|
|
27287
|
-
*
|
|
27288
|
-
|
|
27289
|
-
|
|
27290
|
-
setPauseState('RUNNING');
|
|
27291
|
-
resetPauseTargetLabel();
|
|
27292
|
-
}
|
|
27293
|
-
/**
|
|
27294
|
-
* Gets or creates the resolver set for one timed wait.
|
|
27489
|
+
* Describes what pressing `X` did.
|
|
27490
|
+
*
|
|
27491
|
+
* @private helper of `applyCoderRunControlKey`
|
|
27295
27492
|
*/
|
|
27296
|
-
function
|
|
27297
|
-
|
|
27298
|
-
|
|
27299
|
-
|
|
27493
|
+
function describeEndAfterCurrentPrompt(toggleResult) {
|
|
27494
|
+
if (toggleResult === 'REQUESTED_END') {
|
|
27495
|
+
return {
|
|
27496
|
+
controlKey: 'X',
|
|
27497
|
+
message: 'Ending after the current prompt finishes',
|
|
27498
|
+
tone: 'warning',
|
|
27499
|
+
};
|
|
27300
27500
|
}
|
|
27301
|
-
|
|
27302
|
-
|
|
27303
|
-
|
|
27501
|
+
return {
|
|
27502
|
+
controlKey: 'X',
|
|
27503
|
+
message: 'End cancelled, the whole queue will be processed',
|
|
27504
|
+
tone: 'success',
|
|
27505
|
+
};
|
|
27304
27506
|
}
|
|
27507
|
+
|
|
27305
27508
|
/**
|
|
27306
|
-
*
|
|
27509
|
+
* How long one control answer stays visible in the rich terminal UI.
|
|
27510
|
+
*
|
|
27511
|
+
* @private internal constant of `ptbk coder` terminal controls
|
|
27307
27512
|
*/
|
|
27308
|
-
|
|
27309
|
-
const resolvers = SKIP_CURRENT_WAIT_RESOLVERS.get(waitToken);
|
|
27310
|
-
if (resolvers === undefined) {
|
|
27311
|
-
return;
|
|
27312
|
-
}
|
|
27313
|
-
for (const resolver of resolvers) {
|
|
27314
|
-
resolver();
|
|
27315
|
-
}
|
|
27316
|
-
resolvers.clear();
|
|
27317
|
-
}
|
|
27513
|
+
const CODER_RUN_CONTROL_FEEDBACK_DURATION_MS = 5000;
|
|
27318
27514
|
|
|
27319
27515
|
/**
|
|
27320
27516
|
* Human-readable label for a priority scope without any bounds.
|
|
@@ -27439,85 +27635,696 @@
|
|
|
27439
27635
|
// Note: [🟡] Code for priority filtering [priorityFilter](scripts/run-codex-prompts/prompts/priorityFilter.ts) should never be published outside of `@promptbook/cli`
|
|
27440
27636
|
|
|
27441
27637
|
/**
|
|
27442
|
-
*
|
|
27638
|
+
* Minimum width used for the rich coder-run frame.
|
|
27443
27639
|
*/
|
|
27444
|
-
const
|
|
27445
|
-
colors__default["default"].magenta.bold(' .-""""-.'),
|
|
27446
|
-
colors__default["default"].magenta.bold(" .' .-. '."),
|
|
27447
|
-
`${colors__default["default"].magenta.bold(' / (')}${colors__default["default"].yellow.bold('o o')}${colors__default["default"].magenta.bold(') \\')}`,
|
|
27448
|
-
colors__default["default"].magenta.bold(' | ^ |'),
|
|
27449
|
-
colors__default["default"].magenta.bold(' | \\___/ |'),
|
|
27450
|
-
colors__default["default"].magenta.bold(' \\___________/'),
|
|
27451
|
-
];
|
|
27640
|
+
const MIN_FRAME_WIDTH = 56;
|
|
27452
27641
|
/**
|
|
27453
|
-
*
|
|
27642
|
+
* Maximum width used for the rich coder-run frame.
|
|
27454
27643
|
*/
|
|
27455
|
-
const
|
|
27644
|
+
const MAX_FRAME_WIDTH = 96;
|
|
27456
27645
|
/**
|
|
27457
|
-
*
|
|
27646
|
+
* Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
|
|
27458
27647
|
*/
|
|
27459
|
-
|
|
27460
|
-
|
|
27461
|
-
|
|
27462
|
-
|
|
27463
|
-
|
|
27464
|
-
|
|
27465
|
-
|
|
27466
|
-
|
|
27467
|
-
|
|
27468
|
-
|
|
27469
|
-
|
|
27470
|
-
|
|
27471
|
-
|
|
27472
|
-
|
|
27473
|
-
|
|
27474
|
-
|
|
27475
|
-
|
|
27476
|
-
|
|
27477
|
-
|
|
27478
|
-
|
|
27479
|
-
|
|
27480
|
-
|
|
27481
|
-
|
|
27482
|
-
|
|
27483
|
-
|
|
27484
|
-
|
|
27485
|
-
|
|
27486
|
-
colors__default["default"].
|
|
27487
|
-
colors__default["default"].
|
|
27488
|
-
colors__default["default"].
|
|
27489
|
-
|
|
27490
|
-
|
|
27491
|
-
|
|
27492
|
-
|
|
27648
|
+
function buildCoderRunUiFrame(options) {
|
|
27649
|
+
const totalWidth = Math.max(MIN_FRAME_WIDTH, Math.min(options.terminalWidth, MAX_FRAME_WIDTH));
|
|
27650
|
+
const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
|
|
27651
|
+
const promptStatusPrefix = isPromptActive ? `${colors__default["default"].yellow(`${options.spinner} `)}` : '';
|
|
27652
|
+
const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.pauseTargetLabel, options.statusMessage);
|
|
27653
|
+
const sessionLines = buildSessionLines(options, totalWidth, pausePresentation);
|
|
27654
|
+
const currentTaskLines = options.currentPromptLabel
|
|
27655
|
+
? [
|
|
27656
|
+
`${promptStatusPrefix}${colors__default["default"].bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
|
|
27657
|
+
`Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
|
|
27658
|
+
...options.detailLines.map((detailLine) => `• ${detailLine}`),
|
|
27659
|
+
]
|
|
27660
|
+
: [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
|
|
27661
|
+
const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
|
|
27662
|
+
const controlsBoxLines = buildControlsBoxLines({
|
|
27663
|
+
controlPills: buildCoderRunControlPills({
|
|
27664
|
+
phase: options.phase,
|
|
27665
|
+
pauseControl: pausePresentation.pauseControl,
|
|
27666
|
+
pendingEnterLabel: options.pendingEnterLabel,
|
|
27667
|
+
isEndAfterCurrentPromptRequested: options.isEndAfterCurrentPromptRequested,
|
|
27668
|
+
sessionTotal: options.progress.sessionTotal,
|
|
27669
|
+
}),
|
|
27670
|
+
controlFeedback: options.controlFeedback,
|
|
27671
|
+
});
|
|
27672
|
+
const frame = [
|
|
27673
|
+
...buildFrameHeaderVisual(options, totalWidth),
|
|
27674
|
+
'',
|
|
27675
|
+
...renderBox('Session', sessionLines, totalWidth, colors__default["default"].yellow.bold),
|
|
27676
|
+
...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors__default["default"].magenta.bold),
|
|
27677
|
+
...renderBox('Live output', visibleOutputLines, totalWidth, colors__default["default"].green.bold),
|
|
27678
|
+
];
|
|
27679
|
+
if (options.errors.length > 0) {
|
|
27680
|
+
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
27681
|
+
frame.push(...renderBox('Errors', buildErrorDisplayLines(options.errors, bodyWidth), totalWidth, colors__default["default"].red.bold));
|
|
27682
|
+
}
|
|
27683
|
+
frame.push(...renderBox('Controls', controlsBoxLines, totalWidth, colors__default["default"].white.bold));
|
|
27684
|
+
return frame;
|
|
27685
|
+
}
|
|
27493
27686
|
/**
|
|
27494
|
-
* Builds the
|
|
27687
|
+
* Builds the header visual above the dashboard boxes.
|
|
27688
|
+
*
|
|
27689
|
+
* Shows the ASCII-art `--agent` avatar visual as soon as one is available. Until the avatar is
|
|
27690
|
+
* loaded - and when no avatar can be rendered at all - the header stays empty, because the
|
|
27691
|
+
* placeholder ASCII `PTBK.IO` wordmark only added terminal noise while nothing was rendered yet.
|
|
27692
|
+
*/
|
|
27693
|
+
function buildFrameHeaderVisual(options, totalWidth) {
|
|
27694
|
+
if (options.agentVisualLines !== undefined && options.agentVisualLines.length > 0) {
|
|
27695
|
+
return options.agentVisualLines.map((agentVisualLine) => centerAnsiText(agentVisualLine, totalWidth));
|
|
27696
|
+
}
|
|
27697
|
+
if (options.agentVisual !== undefined) {
|
|
27698
|
+
const agentVisualLines = options.agentVisual.renderFrame({
|
|
27699
|
+
animationTimeMs: options.animationTimeMs,
|
|
27700
|
+
});
|
|
27701
|
+
if (agentVisualLines.length > 0) {
|
|
27702
|
+
return agentVisualLines.map((agentVisualLine) => centerAnsiText(agentVisualLine, totalWidth));
|
|
27703
|
+
}
|
|
27704
|
+
}
|
|
27705
|
+
return [];
|
|
27706
|
+
}
|
|
27707
|
+
/**
|
|
27708
|
+
* Builds the structured session lines that combine state, runner, queue, and timing metadata.
|
|
27709
|
+
*/
|
|
27710
|
+
function buildSessionLines(options, totalWidth, pausePresentation) {
|
|
27711
|
+
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
27712
|
+
return buildSessionRows(options, bodyWidth, pausePresentation).map((sessionRow) => buildLabeledSessionLine(sessionRow.label, sessionRow.value, bodyWidth));
|
|
27713
|
+
}
|
|
27714
|
+
/**
|
|
27715
|
+
* Builds the session rows so the renderer can keep one consistent structure without duplicating labels.
|
|
27716
|
+
*/
|
|
27717
|
+
function buildSessionRows(options, bodyWidth, pausePresentation) {
|
|
27718
|
+
const runnerParts = [options.config.agentName || 'No agent selected'];
|
|
27719
|
+
if (options.config.modelName) {
|
|
27720
|
+
runnerParts.push(options.config.modelName);
|
|
27721
|
+
}
|
|
27722
|
+
if (options.config.thinkingLevel) {
|
|
27723
|
+
runnerParts.push(`thinking ${options.config.thinkingLevel}`);
|
|
27724
|
+
}
|
|
27725
|
+
const configurationRows = [
|
|
27726
|
+
...buildOptionalSessionRow('Context', options.config.context),
|
|
27727
|
+
...buildOptionalSessionRow('Server', options.config.serverUrl
|
|
27728
|
+
? buildTerminalUrlLink(options.config.serverUrl, bodyWidth - SESSION_LABEL_WIDTH - 1)
|
|
27729
|
+
: undefined),
|
|
27730
|
+
...buildOptionalSessionRow('Test', options.config.testCommand),
|
|
27731
|
+
];
|
|
27732
|
+
return [
|
|
27733
|
+
{
|
|
27734
|
+
label: 'State',
|
|
27735
|
+
value: `${pausePresentation.badge} ${pausePresentation.stateMessage}`,
|
|
27736
|
+
},
|
|
27737
|
+
{
|
|
27738
|
+
label: 'Runner',
|
|
27739
|
+
value: runnerParts.join(' · '),
|
|
27740
|
+
},
|
|
27741
|
+
...configurationRows,
|
|
27742
|
+
...buildScriptPathSessionRows(options.currentScriptPaths || [], bodyWidth),
|
|
27743
|
+
{
|
|
27744
|
+
label: 'This run',
|
|
27745
|
+
value: buildThisRunSummary(options.progress),
|
|
27746
|
+
},
|
|
27747
|
+
{
|
|
27748
|
+
label: 'Backlog',
|
|
27749
|
+
value: buildBacklogSummary(options.progress),
|
|
27750
|
+
},
|
|
27751
|
+
{
|
|
27752
|
+
label: 'Scope',
|
|
27753
|
+
value: buildScopeSummary(options.progress, options.config),
|
|
27754
|
+
},
|
|
27755
|
+
{
|
|
27756
|
+
label: 'Timing',
|
|
27757
|
+
value: buildTimingSummary(options.progress),
|
|
27758
|
+
},
|
|
27759
|
+
{
|
|
27760
|
+
label: 'Progress',
|
|
27761
|
+
value: buildProgressBar$1(options.progress.percentage, bodyWidth - SESSION_LABEL_WIDTH - 1, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
|
|
27762
|
+
},
|
|
27763
|
+
];
|
|
27764
|
+
}
|
|
27765
|
+
/**
|
|
27766
|
+
* Builds zero or one structured session row for optional metadata.
|
|
27767
|
+
*/
|
|
27768
|
+
function buildOptionalSessionRow(label, value) {
|
|
27769
|
+
if (!value) {
|
|
27770
|
+
return [];
|
|
27771
|
+
}
|
|
27772
|
+
return [{ label, value }];
|
|
27773
|
+
}
|
|
27774
|
+
/**
|
|
27775
|
+
* Builds the active-session summary shown in the session box.
|
|
27776
|
+
*/
|
|
27777
|
+
function buildThisRunSummary(progress) {
|
|
27778
|
+
if (progress.sessionTotal === 0) {
|
|
27779
|
+
return 'No runnable prompts in current scope';
|
|
27780
|
+
}
|
|
27781
|
+
return `Task ${progress.currentPromptIndex}/${progress.sessionTotal} · ${progress.sessionDone} done · ${progress.sessionRemaining} left`;
|
|
27782
|
+
}
|
|
27783
|
+
/**
|
|
27784
|
+
* Builds the backlog/filter summary shown in the session box.
|
|
27785
|
+
*/
|
|
27786
|
+
function buildBacklogSummary(progress) {
|
|
27787
|
+
const parts = [`Repo ${progress.totalPrompts} total`];
|
|
27788
|
+
if (progress.skippedPrompts > 0) {
|
|
27789
|
+
parts.push(`${formatPromptCount$1(progress.skippedPrompts)} outside priority scope`);
|
|
27790
|
+
}
|
|
27791
|
+
return parts.join(' · ');
|
|
27792
|
+
}
|
|
27793
|
+
/**
|
|
27794
|
+
* Builds the priority/write-order summary shown in the session box.
|
|
27795
|
+
*/
|
|
27796
|
+
function buildScopeSummary(progress, config) {
|
|
27797
|
+
var _a;
|
|
27798
|
+
const parts = [formatPriorityFilter((_a = config.priorityFilter) !== null && _a !== void 0 ? _a : {})];
|
|
27799
|
+
if (config.limit !== undefined) {
|
|
27800
|
+
parts.push(`Limit ${formatPromptRunCount(config.limit)}`);
|
|
27801
|
+
}
|
|
27802
|
+
if (progress.toBeWrittenPrompts > 0) {
|
|
27803
|
+
parts.push(`Write ${formatPromptCount$1(progress.toBeWrittenPrompts)} first`);
|
|
27804
|
+
}
|
|
27805
|
+
return parts.join(' · ');
|
|
27806
|
+
}
|
|
27807
|
+
/**
|
|
27808
|
+
* Builds the elapsed/estimate summary shown in the session box.
|
|
27809
|
+
*/
|
|
27810
|
+
function buildTimingSummary(progress) {
|
|
27811
|
+
return `Elapsed ${progress.elapsedText} · Total ${progress.estimatedTotalText} · ETA ${progress.estimatedLabel}`;
|
|
27812
|
+
}
|
|
27813
|
+
/**
|
|
27814
|
+
* Formats a prompt count with singular/plural wording.
|
|
27815
|
+
*/
|
|
27816
|
+
function formatPromptCount$1(count) {
|
|
27817
|
+
return `${count} prompt${count === 1 ? '' : 's'}`;
|
|
27818
|
+
}
|
|
27819
|
+
/**
|
|
27820
|
+
* Formats a configured successful prompt-run limit with singular/plural wording.
|
|
27821
|
+
*/
|
|
27822
|
+
function formatPromptRunCount(count) {
|
|
27823
|
+
return `${count} prompt run${count === 1 ? '' : 's'}`;
|
|
27824
|
+
}
|
|
27825
|
+
|
|
27826
|
+
/**
|
|
27827
|
+
* ANSI sequence that clears the current terminal row.
|
|
27828
|
+
*
|
|
27829
|
+
* @private internal constant of coder run UI
|
|
27830
|
+
*/
|
|
27831
|
+
const CLEAR_CURRENT_LINE = '\x1b[2K';
|
|
27832
|
+
/**
|
|
27833
|
+
* ANSI control character that moves the cursor to the start of its current row.
|
|
27834
|
+
*
|
|
27835
|
+
* @private internal constant of coder run UI
|
|
27836
|
+
*/
|
|
27837
|
+
const MOVE_CURSOR_TO_LINE_START = '\r';
|
|
27838
|
+
/**
|
|
27839
|
+
* Builds one atomic ANSI update that changes the displayed coder-run terminal frame.
|
|
27840
|
+
*
|
|
27841
|
+
* A change in frame height requires a full rewrite to reserve or clear rows correctly. Frames with
|
|
27842
|
+
* the same height only rewrite contiguous groups of changed rows, returning to the frame bottom
|
|
27843
|
+
* between groups so each group can be assembled into the same output payload safely.
|
|
27844
|
+
*
|
|
27845
|
+
* @returns One terminal output payload, or `undefined` when the frame is already up to date.
|
|
27495
27846
|
*
|
|
27496
27847
|
* @private internal utility of coder run UI
|
|
27497
27848
|
*/
|
|
27498
|
-
function
|
|
27499
|
-
const
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
27505
|
-
|
|
27506
|
-
|
|
27507
|
-
|
|
27508
|
-
|
|
27509
|
-
|
|
27510
|
-
|
|
27511
|
-
|
|
27512
|
-
|
|
27513
|
-
|
|
27514
|
-
|
|
27515
|
-
|
|
27516
|
-
|
|
27517
|
-
|
|
27518
|
-
|
|
27519
|
-
.
|
|
27520
|
-
|
|
27849
|
+
function buildCoderRunUiTerminalFrameUpdate(options) {
|
|
27850
|
+
const { previousFrameLines, nextFrameLines } = options;
|
|
27851
|
+
if (previousFrameLines.length === 0 && nextFrameLines.length === 0) {
|
|
27852
|
+
return undefined;
|
|
27853
|
+
}
|
|
27854
|
+
if (previousFrameLines.length === 0 || previousFrameLines.length !== nextFrameLines.length) {
|
|
27855
|
+
return buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
|
|
27856
|
+
}
|
|
27857
|
+
return buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
|
|
27858
|
+
}
|
|
27859
|
+
/**
|
|
27860
|
+
* Builds an ANSI update which completely rewrites the reserved terminal frame area.
|
|
27861
|
+
*
|
|
27862
|
+
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
27863
|
+
*/
|
|
27864
|
+
function buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
|
|
27865
|
+
var _a;
|
|
27866
|
+
const previousFrameLineCount = previousFrameLines.length;
|
|
27867
|
+
const lineCountToRewrite = Math.max(previousFrameLineCount, nextFrameLines.length);
|
|
27868
|
+
const outputParts = [];
|
|
27869
|
+
if (previousFrameLineCount > 1) {
|
|
27870
|
+
outputParts.push(moveCursorUp(previousFrameLineCount - 1));
|
|
27871
|
+
}
|
|
27872
|
+
for (let lineIndex = 0; lineIndex < lineCountToRewrite; lineIndex++) {
|
|
27873
|
+
outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, (_a = nextFrameLines[lineIndex]) !== null && _a !== void 0 ? _a : '');
|
|
27874
|
+
if (lineIndex < lineCountToRewrite - 1) {
|
|
27875
|
+
outputParts.push('\n');
|
|
27876
|
+
}
|
|
27877
|
+
}
|
|
27878
|
+
const clearedTrailingLineCount = lineCountToRewrite - nextFrameLines.length;
|
|
27879
|
+
if (clearedTrailingLineCount > 0) {
|
|
27880
|
+
outputParts.push(moveCursorUp(clearedTrailingLineCount));
|
|
27881
|
+
}
|
|
27882
|
+
outputParts.push(MOVE_CURSOR_TO_LINE_START);
|
|
27883
|
+
return outputParts.join('');
|
|
27884
|
+
}
|
|
27885
|
+
/**
|
|
27886
|
+
* Builds an ANSI update that rewrites only contiguous groups of rows which changed.
|
|
27887
|
+
*
|
|
27888
|
+
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
27889
|
+
*/
|
|
27890
|
+
function buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
|
|
27891
|
+
const outputParts = [];
|
|
27892
|
+
let lineIndex = 0;
|
|
27893
|
+
while (lineIndex < nextFrameLines.length) {
|
|
27894
|
+
if (previousFrameLines[lineIndex] === nextFrameLines[lineIndex]) {
|
|
27895
|
+
lineIndex++;
|
|
27896
|
+
continue;
|
|
27897
|
+
}
|
|
27898
|
+
const changedRangeStartLineIndex = lineIndex;
|
|
27899
|
+
while (lineIndex + 1 < nextFrameLines.length &&
|
|
27900
|
+
previousFrameLines[lineIndex + 1] !== nextFrameLines[lineIndex + 1]) {
|
|
27901
|
+
lineIndex++;
|
|
27902
|
+
}
|
|
27903
|
+
appendChangedCoderRunUiTerminalFrameRange({
|
|
27904
|
+
outputParts,
|
|
27905
|
+
frameLineCount: nextFrameLines.length,
|
|
27906
|
+
changedRangeStartLineIndex,
|
|
27907
|
+
changedRangeEndLineIndex: lineIndex,
|
|
27908
|
+
nextFrameLines,
|
|
27909
|
+
});
|
|
27910
|
+
lineIndex++;
|
|
27911
|
+
}
|
|
27912
|
+
return outputParts.length === 0 ? undefined : outputParts.join('');
|
|
27913
|
+
}
|
|
27914
|
+
/**
|
|
27915
|
+
* Appends the cursor movements and row rewrites for one contiguous changed frame range.
|
|
27916
|
+
*
|
|
27917
|
+
* @private helper of `buildChangedCoderRunUiTerminalFrameUpdate`
|
|
27918
|
+
*/
|
|
27919
|
+
function appendChangedCoderRunUiTerminalFrameRange(options) {
|
|
27920
|
+
const { outputParts, frameLineCount, changedRangeStartLineIndex, changedRangeEndLineIndex, nextFrameLines } = options;
|
|
27921
|
+
const linesUpFromFrameBottom = frameLineCount - 1 - changedRangeStartLineIndex;
|
|
27922
|
+
if (linesUpFromFrameBottom > 0) {
|
|
27923
|
+
outputParts.push(moveCursorUp(linesUpFromFrameBottom));
|
|
27924
|
+
}
|
|
27925
|
+
for (let lineIndex = changedRangeStartLineIndex; lineIndex <= changedRangeEndLineIndex; lineIndex++) {
|
|
27926
|
+
outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, nextFrameLines[lineIndex]);
|
|
27927
|
+
if (lineIndex < changedRangeEndLineIndex) {
|
|
27928
|
+
outputParts.push('\n');
|
|
27929
|
+
}
|
|
27930
|
+
}
|
|
27931
|
+
const linesDownToFrameBottom = frameLineCount - 1 - changedRangeEndLineIndex;
|
|
27932
|
+
if (linesDownToFrameBottom > 0) {
|
|
27933
|
+
outputParts.push(moveCursorDown(linesDownToFrameBottom));
|
|
27934
|
+
}
|
|
27935
|
+
outputParts.push(MOVE_CURSOR_TO_LINE_START);
|
|
27936
|
+
}
|
|
27937
|
+
/**
|
|
27938
|
+
* Builds an ANSI sequence which moves the terminal cursor up by a positive number of rows.
|
|
27939
|
+
*
|
|
27940
|
+
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
27941
|
+
*/
|
|
27942
|
+
function moveCursorUp(lineCount) {
|
|
27943
|
+
return `\x1b[${lineCount}A`;
|
|
27944
|
+
}
|
|
27945
|
+
/**
|
|
27946
|
+
* Builds an ANSI sequence which moves the terminal cursor down by a positive number of rows.
|
|
27947
|
+
*
|
|
27948
|
+
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
27949
|
+
*/
|
|
27950
|
+
function moveCursorDown(lineCount) {
|
|
27951
|
+
return `\x1b[${lineCount}B`;
|
|
27952
|
+
}
|
|
27953
|
+
|
|
27954
|
+
/**
|
|
27955
|
+
* Calendar formats used when displaying the estimated completion time.
|
|
27956
|
+
*
|
|
27957
|
+
* Uses 24-hour format (`HH:mm`) so estimates are unambiguous (for example `17:30` instead of `5:30`).
|
|
27958
|
+
*/
|
|
27959
|
+
const ESTIMATED_DONE_CALENDAR_FORMATS = {
|
|
27960
|
+
sameDay: '[Today] HH:mm',
|
|
27961
|
+
nextDay: '[Tomorrow] HH:mm',
|
|
27962
|
+
nextWeek: 'dddd HH:mm',
|
|
27963
|
+
lastDay: '[Yesterday] HH:mm',
|
|
27964
|
+
lastWeek: 'dddd HH:mm',
|
|
27965
|
+
sameElse: 'MMM D HH:mm',
|
|
27966
|
+
};
|
|
27967
|
+
/**
|
|
27968
|
+
* Formats a duration into a compact string such as "3h 12m" or "45s".
|
|
27969
|
+
*/
|
|
27970
|
+
function formatDurationBrief(duration) {
|
|
27971
|
+
const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
|
|
27972
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
27973
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
27974
|
+
const seconds = totalSeconds % 60;
|
|
27975
|
+
const parts = [];
|
|
27976
|
+
if (hours > 0) {
|
|
27977
|
+
parts.push(`${hours}h`);
|
|
27978
|
+
}
|
|
27979
|
+
if (minutes > 0) {
|
|
27980
|
+
parts.push(`${minutes}m`);
|
|
27981
|
+
}
|
|
27982
|
+
if (!parts.length && seconds > 0) {
|
|
27983
|
+
parts.push(`${seconds}s`);
|
|
27984
|
+
}
|
|
27985
|
+
if (!parts.length) {
|
|
27986
|
+
parts.push('0s');
|
|
27987
|
+
}
|
|
27988
|
+
return parts.join(' ');
|
|
27989
|
+
}
|
|
27990
|
+
|
|
27991
|
+
/**
|
|
27992
|
+
* Builds a session-scoped progress snapshot from prompt stats and elapsed active time.
|
|
27993
|
+
*
|
|
27994
|
+
* When the current session has not finished any prompt yet but a `cachedAveragePromptDurationMs` is
|
|
27995
|
+
* provided (loaded from the per-config estimate cache stored in the temp folder), the snapshot uses
|
|
27996
|
+
* that historical average to project the remaining work instead of waiting for the first completion.
|
|
27997
|
+
*/
|
|
27998
|
+
function buildCoderRunProgressSnapshot(stats, elapsedDuration, initialDone, cachedAveragePromptDurationMs, runLimit) {
|
|
27999
|
+
const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
|
|
28000
|
+
const completedSessionPrompts = Math.max(0, stats.done - initialDone);
|
|
28001
|
+
const availableSessionTotal = completedSessionPrompts + stats.forAgent;
|
|
28002
|
+
const sessionTotal = resolveSessionTotal(availableSessionTotal, runLimit);
|
|
28003
|
+
const sessionDone = Math.min(completedSessionPrompts, sessionTotal);
|
|
28004
|
+
const sessionRemaining = Math.max(0, sessionTotal - sessionDone);
|
|
28005
|
+
const currentPromptIndex = sessionTotal > 0 ? Math.min(sessionDone + 1, sessionTotal) : 0;
|
|
28006
|
+
const percentage = sessionTotal > 0 ? Math.round((sessionDone / sessionTotal) * 100) : 0;
|
|
28007
|
+
const elapsedText = formatDurationBrief(elapsedDuration);
|
|
28008
|
+
const elapsedMs = elapsedDuration.asMilliseconds();
|
|
28009
|
+
let estimatedTotalText = 'estimating...';
|
|
28010
|
+
let estimatedLabel = 'after first completion';
|
|
28011
|
+
let isEstimatedTotalKnown = false;
|
|
28012
|
+
let estimatedTotalMs;
|
|
28013
|
+
if (sessionTotal > 0 && sessionDone > 0) {
|
|
28014
|
+
estimatedTotalMs = (elapsedMs * sessionTotal) / sessionDone;
|
|
28015
|
+
}
|
|
28016
|
+
else if (sessionTotal > 0 &&
|
|
28017
|
+
cachedAveragePromptDurationMs !== undefined &&
|
|
28018
|
+
cachedAveragePromptDurationMs > 0) {
|
|
28019
|
+
const projectedRemainingMs = sessionRemaining * cachedAveragePromptDurationMs;
|
|
28020
|
+
estimatedTotalMs = elapsedMs + projectedRemainingMs;
|
|
28021
|
+
}
|
|
28022
|
+
if (estimatedTotalMs !== undefined) {
|
|
28023
|
+
const estimatedRemainingMs = Math.max(0, estimatedTotalMs - elapsedMs);
|
|
28024
|
+
const estimatedTotalDuration = moment__default["default"].duration(estimatedTotalMs);
|
|
28025
|
+
const estimatedCompletion = moment__default["default"]().add(estimatedRemainingMs, 'milliseconds');
|
|
28026
|
+
estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
|
|
28027
|
+
estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
|
|
28028
|
+
isEstimatedTotalKnown = true;
|
|
28029
|
+
}
|
|
28030
|
+
return {
|
|
28031
|
+
totalPrompts,
|
|
28032
|
+
sessionDone,
|
|
28033
|
+
sessionRemaining,
|
|
28034
|
+
sessionTotal,
|
|
28035
|
+
currentPromptIndex,
|
|
28036
|
+
skippedPrompts: stats.outsidePriorityRange,
|
|
28037
|
+
toBeWrittenPrompts: stats.toBeWritten,
|
|
28038
|
+
percentage,
|
|
28039
|
+
elapsedText,
|
|
28040
|
+
estimatedTotalText,
|
|
28041
|
+
estimatedLabel,
|
|
28042
|
+
isEstimatedTotalKnown,
|
|
28043
|
+
};
|
|
28044
|
+
}
|
|
28045
|
+
/**
|
|
28046
|
+
* Resolves the number of prompt runs represented by the current session progress.
|
|
28047
|
+
*/
|
|
28048
|
+
function resolveSessionTotal(availableSessionTotal, runLimit) {
|
|
28049
|
+
if (runLimit === undefined) {
|
|
28050
|
+
return availableSessionTotal;
|
|
28051
|
+
}
|
|
28052
|
+
return Math.min(availableSessionTotal, runLimit);
|
|
28053
|
+
}
|
|
28054
|
+
|
|
28055
|
+
/**
|
|
28056
|
+
* Tracks active coder-run time while excluding pauses and user-confirmation waits.
|
|
28057
|
+
*/
|
|
28058
|
+
class CoderRunTimer {
|
|
28059
|
+
/**
|
|
28060
|
+
* Creates a timer anchored at the provided start time.
|
|
28061
|
+
*/
|
|
28062
|
+
constructor(startTime, isPausedInitially = false) {
|
|
28063
|
+
this.startTime = startTime;
|
|
28064
|
+
/**
|
|
28065
|
+
* Total milliseconds spent in paused state across the run.
|
|
28066
|
+
*/
|
|
28067
|
+
this.pausedMs = 0;
|
|
28068
|
+
if (isPausedInitially) {
|
|
28069
|
+
this.pausedSince = startTime.clone();
|
|
28070
|
+
}
|
|
28071
|
+
}
|
|
28072
|
+
/**
|
|
28073
|
+
* Pauses active-time tracking until `resume()` is called.
|
|
28074
|
+
*/
|
|
28075
|
+
pause() {
|
|
28076
|
+
if (this.pausedSince === undefined) {
|
|
28077
|
+
this.pausedSince = moment__default["default"]();
|
|
28078
|
+
}
|
|
28079
|
+
}
|
|
28080
|
+
/**
|
|
28081
|
+
* Resumes active-time tracking after a pause.
|
|
28082
|
+
*/
|
|
28083
|
+
resume() {
|
|
28084
|
+
if (this.pausedSince !== undefined) {
|
|
28085
|
+
this.pausedMs += moment__default["default"]().diff(this.pausedSince);
|
|
28086
|
+
this.pausedSince = undefined;
|
|
28087
|
+
}
|
|
28088
|
+
}
|
|
28089
|
+
/**
|
|
28090
|
+
* Returns the currently accumulated active duration.
|
|
28091
|
+
*/
|
|
28092
|
+
getElapsedDuration() {
|
|
28093
|
+
const wallMs = moment__default["default"]().diff(this.startTime);
|
|
28094
|
+
const currentPauseMs = this.pausedSince !== undefined ? moment__default["default"]().diff(this.pausedSince) : 0;
|
|
28095
|
+
return moment__default["default"].duration(Math.max(0, wallMs - this.pausedMs - currentPauseMs));
|
|
28096
|
+
}
|
|
28097
|
+
}
|
|
28098
|
+
|
|
28099
|
+
/**
|
|
28100
|
+
* Maximum number of agent output lines kept in the scrolling output area.
|
|
28101
|
+
*
|
|
28102
|
+
* @private internal constant of coder run UI
|
|
28103
|
+
*/
|
|
28104
|
+
const MAX_AGENT_OUTPUT_LINES = 12;
|
|
28105
|
+
/**
|
|
28106
|
+
* Reactive state manager for the coder run terminal UI.
|
|
28107
|
+
*
|
|
28108
|
+
* Holds all data the Ink components need and emits `'change'` events
|
|
28109
|
+
* whenever any property is updated so the UI can re-render.
|
|
28110
|
+
*
|
|
28111
|
+
* @private internal utility of coder run UI
|
|
28112
|
+
*/
|
|
28113
|
+
class CoderRunUiState extends events.EventEmitter {
|
|
28114
|
+
constructor(startTime) {
|
|
28115
|
+
super();
|
|
28116
|
+
this.config = { agentName: '' };
|
|
28117
|
+
this.currentPromptLabel = '';
|
|
28118
|
+
this.currentScriptPaths = [];
|
|
28119
|
+
this.currentAttempt = 1;
|
|
28120
|
+
this.maxAttempts = 3;
|
|
28121
|
+
this.detailLines = [];
|
|
28122
|
+
this.messagePreviewLines = [];
|
|
28123
|
+
this.messagePreviewSections = [];
|
|
28124
|
+
this.agentStatusLines = [];
|
|
28125
|
+
this.agentStatusTableRows = [];
|
|
28126
|
+
this.agentOutputLines = [];
|
|
28127
|
+
this.phase = 'initializing';
|
|
28128
|
+
this.statusMessage = 'Initializing...';
|
|
28129
|
+
this.errors = [];
|
|
28130
|
+
this.stats = { done: 0, forAgent: 0, outsidePriorityRange: 0, toBeWritten: 0 };
|
|
28131
|
+
// Match the plain CLI progress header and count active run time from startup.
|
|
28132
|
+
// Explicit waits/pauses are excluded later through `pauseTimer()` / `resumeTimer()`.
|
|
28133
|
+
this.timer = new CoderRunTimer(startTime);
|
|
28134
|
+
}
|
|
28135
|
+
/**
|
|
28136
|
+
* Pauses the elapsed timer (e.g. while waiting for user input or paused state).
|
|
28137
|
+
*/
|
|
28138
|
+
pauseTimer() {
|
|
28139
|
+
this.timer.pause();
|
|
28140
|
+
this.emitChange();
|
|
28141
|
+
}
|
|
28142
|
+
/**
|
|
28143
|
+
* Resumes the elapsed timer after a pause.
|
|
28144
|
+
*/
|
|
28145
|
+
resumeTimer() {
|
|
28146
|
+
this.timer.resume();
|
|
28147
|
+
this.emitChange();
|
|
28148
|
+
}
|
|
28149
|
+
/**
|
|
28150
|
+
* Replaces the configuration shown in the UI header.
|
|
28151
|
+
*/
|
|
28152
|
+
setConfig(config) {
|
|
28153
|
+
this.config = config;
|
|
28154
|
+
this.emitChange();
|
|
28155
|
+
}
|
|
28156
|
+
/**
|
|
28157
|
+
* Replaces the ASCII-art agent visual renderer shown above the dashboard boxes.
|
|
28158
|
+
*/
|
|
28159
|
+
setAgentVisual(agentVisual) {
|
|
28160
|
+
this.agentVisual = agentVisual;
|
|
28161
|
+
this.emitChange();
|
|
28162
|
+
}
|
|
28163
|
+
/**
|
|
28164
|
+
* Feeds new prompt statistics from the main loop so the progress bar updates.
|
|
28165
|
+
*/
|
|
28166
|
+
updateProgress(stats) {
|
|
28167
|
+
if (this.initialDone === undefined && (stats.done > 0 || stats.forAgent > 0 || stats.toBeWritten > 0)) {
|
|
28168
|
+
this.initialDone = stats.done;
|
|
28169
|
+
}
|
|
28170
|
+
this.stats = stats;
|
|
28171
|
+
this.emitChange();
|
|
28172
|
+
}
|
|
28173
|
+
/**
|
|
28174
|
+
* Sets the cached average prompt duration (in milliseconds) loaded from the temp-folder estimate cache.
|
|
28175
|
+
*
|
|
28176
|
+
* Used to project completion estimates before the first prompt of the current session finishes.
|
|
28177
|
+
*/
|
|
28178
|
+
setCachedAveragePromptDurationMs(cachedAveragePromptDurationMs) {
|
|
28179
|
+
this.cachedAveragePromptDurationMs = cachedAveragePromptDurationMs;
|
|
28180
|
+
this.emitChange();
|
|
28181
|
+
}
|
|
28182
|
+
/**
|
|
28183
|
+
* Computes a progress snapshot on demand so elapsed time ticks with periodic re-renders.
|
|
28184
|
+
*/
|
|
28185
|
+
getProgress() {
|
|
28186
|
+
var _a;
|
|
28187
|
+
return buildCoderRunProgressSnapshot(this.stats, this.timer.getElapsedDuration(), (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done, this.cachedAveragePromptDurationMs, this.config.limit);
|
|
28188
|
+
}
|
|
28189
|
+
/**
|
|
28190
|
+
* Sets the label of the prompt currently being processed and resets per-prompt state.
|
|
28191
|
+
*/
|
|
28192
|
+
setCurrentPrompt(label) {
|
|
28193
|
+
this.currentPromptLabel = label;
|
|
28194
|
+
this.currentScriptPaths = [];
|
|
28195
|
+
this.detailLines = [];
|
|
28196
|
+
this.messagePreviewLines = [];
|
|
28197
|
+
this.messagePreviewSections = [];
|
|
28198
|
+
this.pendingEnterLabel = undefined;
|
|
28199
|
+
this.agentOutputLines = [];
|
|
28200
|
+
this.currentAttempt = 1;
|
|
28201
|
+
this.emitChange();
|
|
28202
|
+
}
|
|
28203
|
+
/**
|
|
28204
|
+
* Sets the temporary shell script currently used by the active prompt.
|
|
28205
|
+
*/
|
|
28206
|
+
setCurrentScriptPath(scriptPath) {
|
|
28207
|
+
this.currentScriptPaths = scriptPath ? [scriptPath] : [];
|
|
28208
|
+
this.emitChange();
|
|
28209
|
+
}
|
|
28210
|
+
/**
|
|
28211
|
+
* Sets multiple temporary shell scripts used by a shared multi-agent session.
|
|
28212
|
+
*/
|
|
28213
|
+
setCurrentScriptPaths(scriptPaths) {
|
|
28214
|
+
this.currentScriptPaths = [...scriptPaths];
|
|
28215
|
+
this.emitChange();
|
|
28216
|
+
}
|
|
28217
|
+
/**
|
|
28218
|
+
* Updates the current retry attempt number.
|
|
28219
|
+
*/
|
|
28220
|
+
setAttempt(attempt) {
|
|
28221
|
+
this.currentAttempt = attempt;
|
|
28222
|
+
this.emitChange();
|
|
28223
|
+
}
|
|
28224
|
+
/**
|
|
28225
|
+
* Appends raw agent output text, keeping only the last `MAX_AGENT_OUTPUT_LINES`.
|
|
28226
|
+
*/
|
|
28227
|
+
addAgentOutput(text) {
|
|
28228
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim() !== '');
|
|
28229
|
+
if (lines.length === 0) {
|
|
28230
|
+
return;
|
|
28231
|
+
}
|
|
28232
|
+
this.agentOutputLines.push(...lines);
|
|
28233
|
+
if (this.agentOutputLines.length > MAX_AGENT_OUTPUT_LINES) {
|
|
28234
|
+
this.agentOutputLines = this.agentOutputLines.slice(-MAX_AGENT_OUTPUT_LINES);
|
|
28235
|
+
}
|
|
28236
|
+
this.emitChange();
|
|
28237
|
+
}
|
|
28238
|
+
/**
|
|
28239
|
+
* Transitions the execution phase shown in the UI.
|
|
28240
|
+
*/
|
|
28241
|
+
setPhase(phase) {
|
|
28242
|
+
this.phase = phase;
|
|
28243
|
+
this.emitChange();
|
|
28244
|
+
}
|
|
28245
|
+
/**
|
|
28246
|
+
* Updates the status message line beneath the current prompt label.
|
|
28247
|
+
*/
|
|
28248
|
+
setStatusMessage(message) {
|
|
28249
|
+
this.statusMessage = message;
|
|
28250
|
+
this.emitChange();
|
|
28251
|
+
}
|
|
28252
|
+
/**
|
|
28253
|
+
* Replaces the contextual detail lines shown beneath the current prompt status.
|
|
28254
|
+
*/
|
|
28255
|
+
setDetailLines(detailLines) {
|
|
28256
|
+
this.detailLines = detailLines.filter((detailLine) => detailLine.trim() !== '');
|
|
28257
|
+
this.emitChange();
|
|
28258
|
+
}
|
|
28259
|
+
/**
|
|
28260
|
+
* Replaces the exact user-message preview lines shown in agent-specific panels.
|
|
28261
|
+
*/
|
|
28262
|
+
setMessagePreviewLines(messagePreviewLines) {
|
|
28263
|
+
this.messagePreviewLines = [...messagePreviewLines];
|
|
28264
|
+
this.emitChange();
|
|
28265
|
+
}
|
|
28266
|
+
/**
|
|
28267
|
+
* Replaces the structured user-message panels shown in agent-specific dashboards.
|
|
28268
|
+
*/
|
|
28269
|
+
setMessagePreviewSections(messagePreviewSections) {
|
|
28270
|
+
this.messagePreviewSections = [...messagePreviewSections];
|
|
28271
|
+
this.emitChange();
|
|
28272
|
+
}
|
|
28273
|
+
/**
|
|
28274
|
+
* Replaces agent status rows shown by agent-specific terminal frames.
|
|
28275
|
+
*/
|
|
28276
|
+
setAgentStatusLines(agentStatusLines) {
|
|
28277
|
+
this.agentStatusLines = [...agentStatusLines];
|
|
28278
|
+
this.emitChange();
|
|
28279
|
+
}
|
|
28280
|
+
/**
|
|
28281
|
+
* Replaces structured agent status rows shown by agent-specific terminal frames.
|
|
28282
|
+
*/
|
|
28283
|
+
setAgentStatusTableRows(agentStatusTableRows) {
|
|
28284
|
+
this.agentStatusTableRows = [...agentStatusTableRows];
|
|
28285
|
+
this.emitChange();
|
|
28286
|
+
}
|
|
28287
|
+
/**
|
|
28288
|
+
* Sets or clears the Enter-key action label shown in the controls panel.
|
|
28289
|
+
*/
|
|
28290
|
+
setPendingEnterLabel(pendingEnterLabel) {
|
|
28291
|
+
this.pendingEnterLabel = pendingEnterLabel;
|
|
28292
|
+
this.emitChange();
|
|
28293
|
+
}
|
|
28294
|
+
/**
|
|
28295
|
+
* Shows the answer of the runner to one pressed control key, or clears the currently shown one.
|
|
28296
|
+
*
|
|
28297
|
+
* Pressing one control twice in a row can produce the very same answer, for example when there is
|
|
28298
|
+
* nothing to skip. The repeat count is therefore tracked here, so that even such a press changes the
|
|
28299
|
+
* rendered frame and the user always sees that the key arrived.
|
|
28300
|
+
*/
|
|
28301
|
+
setControlFeedback(controlFeedback) {
|
|
28302
|
+
this.controlFeedback =
|
|
28303
|
+
controlFeedback === undefined
|
|
28304
|
+
? undefined
|
|
28305
|
+
: { ...controlFeedback, repeatCount: this.countControlFeedbackRepeat(controlFeedback) };
|
|
28306
|
+
this.emitChange();
|
|
28307
|
+
}
|
|
28308
|
+
/**
|
|
28309
|
+
* Appends an error message to the error list shown in the UI.
|
|
28310
|
+
*/
|
|
28311
|
+
addError(errorMessage) {
|
|
28312
|
+
this.errors.push(errorMessage);
|
|
28313
|
+
this.emitChange();
|
|
28314
|
+
}
|
|
28315
|
+
/**
|
|
28316
|
+
* Counts how many times the shown answer repeats the previously shown one.
|
|
28317
|
+
*/
|
|
28318
|
+
countControlFeedbackRepeat(controlFeedback) {
|
|
28319
|
+
const previousControlFeedback = this.controlFeedback;
|
|
28320
|
+
const isSameControlFeedback = previousControlFeedback !== undefined &&
|
|
28321
|
+
previousControlFeedback.controlKey === controlFeedback.controlKey &&
|
|
28322
|
+
previousControlFeedback.message === controlFeedback.message;
|
|
28323
|
+
return isSameControlFeedback ? previousControlFeedback.repeatCount + 1 : 1;
|
|
28324
|
+
}
|
|
28325
|
+
emitChange() {
|
|
28326
|
+
this.emit('change');
|
|
28327
|
+
}
|
|
27521
28328
|
}
|
|
27522
28329
|
|
|
27523
28330
|
/**
|
|
@@ -28187,807 +28994,139 @@
|
|
|
28187
28994
|
* @param options Agent source, canvas factory, and optional terminal color settings.
|
|
28188
28995
|
* @returns Runtime terminal avatar visual renderer.
|
|
28189
28996
|
*
|
|
28190
|
-
* @private shared helper for terminal avatar rendering
|
|
28191
|
-
*/
|
|
28192
|
-
function createTerminalAgentAvatarVisual(options) {
|
|
28193
|
-
const renderInputs = createTerminalAgentAvatarVisualRenderInputs(options.agentSource, options.defaultAvatarVisualId || DEFAULT_AGENT_AVATAR_VISUAL_ID);
|
|
28194
|
-
return {
|
|
28195
|
-
isAnimated: renderInputs.resolvedAvatarRenderDefinition.avatarVisual.isAnimated,
|
|
28196
|
-
renderFrame({ animationTimeMs }) {
|
|
28197
|
-
return renderTerminalAgentAvatarVisualFrame({
|
|
28198
|
-
...renderInputs,
|
|
28199
|
-
animationTimeMs,
|
|
28200
|
-
colorDepth: options.colorDepth,
|
|
28201
|
-
createCanvas: options.createCanvas,
|
|
28202
|
-
});
|
|
28203
|
-
},
|
|
28204
|
-
};
|
|
28205
|
-
}
|
|
28206
|
-
/**
|
|
28207
|
-
* Creates stable avatar render inputs from one agent source.
|
|
28208
|
-
*
|
|
28209
|
-
* @private shared helper for terminal avatar rendering
|
|
28210
|
-
*/
|
|
28211
|
-
function createTerminalAgentAvatarVisualRenderInputs(agentSource, defaultAvatarVisualId) {
|
|
28212
|
-
const agentBasicInformation = parseAgentSource(agentSource);
|
|
28213
|
-
const avatarDefinition = createAvatarDefinitionFromAgentBasicInformation(agentBasicInformation);
|
|
28214
|
-
const avatarVisualId = resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId);
|
|
28215
|
-
const resolvedAvatarRenderDefinition = resolveAvatarRenderDefinition({
|
|
28216
|
-
avatarDefinition,
|
|
28217
|
-
visualId: avatarVisualId,
|
|
28218
|
-
surface: 'transparent',
|
|
28219
|
-
});
|
|
28220
|
-
return {
|
|
28221
|
-
avatarDefinition,
|
|
28222
|
-
avatarVisualId,
|
|
28223
|
-
resolvedAvatarRenderDefinition,
|
|
28224
|
-
};
|
|
28225
|
-
}
|
|
28226
|
-
/**
|
|
28227
|
-
* Resolves the terminal avatar visual id from already parsed agent information.
|
|
28228
|
-
*
|
|
28229
|
-
* @private shared helper for terminal avatar rendering
|
|
28230
|
-
*/
|
|
28231
|
-
function resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId) {
|
|
28232
|
-
return resolveAgentAvatarVisualId(agentBasicInformation, resolveAvatarVisualId(agentBasicInformation.meta.visual) || defaultAvatarVisualId);
|
|
28233
|
-
}
|
|
28234
|
-
/**
|
|
28235
|
-
* Renders one terminal avatar frame through the shared avatar-to-ASCII pipeline.
|
|
28236
|
-
*
|
|
28237
|
-
* Visuals which are made of characters themselves, for example `AsciiOctopus`, paint the terminal
|
|
28238
|
-
* character grid directly, because rasterizing them into half-block ASCII art averages every glyph
|
|
28239
|
-
* away and makes them indistinguishable from the other blob-shaped visuals.
|
|
28240
|
-
*
|
|
28241
|
-
* @private shared helper for terminal avatar rendering
|
|
28242
|
-
*/
|
|
28243
|
-
function renderTerminalAgentAvatarVisualFrame(options) {
|
|
28244
|
-
const terminalTextLines = renderAvatarVisualTerminalTextLines({
|
|
28245
|
-
avatarDefinition: options.avatarDefinition,
|
|
28246
|
-
visualId: options.avatarVisualId,
|
|
28247
|
-
surface: 'transparent',
|
|
28248
|
-
columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
|
|
28249
|
-
rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
|
|
28250
|
-
colorDepth: options.colorDepth,
|
|
28251
|
-
timeMs: options.animationTimeMs,
|
|
28252
|
-
resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
|
|
28253
|
-
});
|
|
28254
|
-
if (terminalTextLines !== null) {
|
|
28255
|
-
return terminalTextLines;
|
|
28256
|
-
}
|
|
28257
|
-
if (options.createCanvas === undefined) {
|
|
28258
|
-
// Note: A pixel-based visual cannot be rasterized without a canvas, so the caller keeps its default banner
|
|
28259
|
-
return [];
|
|
28260
|
-
}
|
|
28261
|
-
return renderAvatarVisualAsciiArt({
|
|
28262
|
-
avatarDefinition: options.avatarDefinition,
|
|
28263
|
-
visualId: options.avatarVisualId,
|
|
28264
|
-
surface: 'transparent',
|
|
28265
|
-
columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
|
|
28266
|
-
rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
|
|
28267
|
-
canvasWidth: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_WIDTH,
|
|
28268
|
-
canvasHeight: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_HEIGHT,
|
|
28269
|
-
colorDepth: options.colorDepth,
|
|
28270
|
-
timeMs: options.animationTimeMs,
|
|
28271
|
-
createCanvas: options.createCanvas,
|
|
28272
|
-
resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
|
|
28273
|
-
});
|
|
28274
|
-
}
|
|
28275
|
-
|
|
28276
|
-
/**
|
|
28277
|
-
* Refresh cadence used only while the rich coder UI needs animated updates.
|
|
28278
|
-
*
|
|
28279
|
-
* @private internal constant of coder run UI
|
|
28280
|
-
*/
|
|
28281
|
-
const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = TERMINAL_AGENT_AVATAR_VISUAL_REFRESH_INTERVAL_MS;
|
|
28282
|
-
/**
|
|
28283
|
-
* Phases that still benefit from automatic refreshes because the frame can change
|
|
28284
|
-
* over time even without new runner output.
|
|
28285
|
-
*
|
|
28286
|
-
* @private internal constant of coder run UI
|
|
28287
|
-
*/
|
|
28288
|
-
const AUTO_REFRESH_PHASES = ['initializing', 'loading', 'running', 'verifying'];
|
|
28289
|
-
/**
|
|
28290
|
-
* Phases where an animated `--agent` visual should keep rendering like it does on the web.
|
|
28291
|
-
*
|
|
28292
|
-
* @private internal constant of coder run UI
|
|
28293
|
-
*/
|
|
28294
|
-
const AGENT_VISUAL_AUTO_REFRESH_PHASES = [...AUTO_REFRESH_PHASES, 'waiting'];
|
|
28295
|
-
/**
|
|
28296
|
-
* Returns whether the rich coder UI should keep animating on its own.
|
|
28297
|
-
*
|
|
28298
|
-
* @private internal utility of coder run UI
|
|
28299
|
-
*/
|
|
28300
|
-
function isCoderRunUiAutoRefreshing(phase, pauseState, isAgentVisualAnimated = false) {
|
|
28301
|
-
// `PAUSING` still means the current task is winding down, so keep active
|
|
28302
|
-
// animations/timers running until the runner reaches the fully paused state.
|
|
28303
|
-
if (pauseState === 'PAUSED') {
|
|
28304
|
-
return false;
|
|
28305
|
-
}
|
|
28306
|
-
if (isAgentVisualAnimated) {
|
|
28307
|
-
return AGENT_VISUAL_AUTO_REFRESH_PHASES.includes(phase);
|
|
28308
|
-
}
|
|
28309
|
-
return AUTO_REFRESH_PHASES.includes(phase);
|
|
28310
|
-
}
|
|
28311
|
-
/**
|
|
28312
|
-
* Returns the automatic refresh interval for the current UI state.
|
|
28313
|
-
*
|
|
28314
|
-
* Waiting, paused, and completed states return `undefined` so the rich UI stays
|
|
28315
|
-
* perfectly still until actual state changes arrive.
|
|
28316
|
-
*
|
|
28317
|
-
* @private internal utility of coder run UI
|
|
28318
|
-
*/
|
|
28319
|
-
function getCoderRunUiAutoRefreshInterval(phase, pauseState, isAgentVisualAnimated = false) {
|
|
28320
|
-
return isCoderRunUiAutoRefreshing(phase, pauseState, isAgentVisualAnimated)
|
|
28321
|
-
? ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS
|
|
28322
|
-
: undefined;
|
|
28323
|
-
}
|
|
28324
|
-
|
|
28325
|
-
/**
|
|
28326
|
-
* Minimum width used for the rich coder-run frame.
|
|
28327
|
-
*/
|
|
28328
|
-
const MIN_FRAME_WIDTH = 56;
|
|
28329
|
-
/**
|
|
28330
|
-
* Maximum width used for the rich coder-run frame.
|
|
28331
|
-
*/
|
|
28332
|
-
const MAX_FRAME_WIDTH = 96;
|
|
28333
|
-
/**
|
|
28334
|
-
* Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
|
|
28335
|
-
*/
|
|
28336
|
-
function buildCoderRunUiFrame(options) {
|
|
28337
|
-
const totalWidth = Math.max(MIN_FRAME_WIDTH, Math.min(options.terminalWidth, MAX_FRAME_WIDTH));
|
|
28338
|
-
const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
|
|
28339
|
-
const promptStatusPrefix = isPromptActive ? `${colors__default["default"].yellow(`${options.spinner} `)}` : '';
|
|
28340
|
-
const octopusAnimationFrame = isCoderRunUiAutoRefreshing(options.phase, options.pauseState)
|
|
28341
|
-
? options.animationFrame
|
|
28342
|
-
: 0;
|
|
28343
|
-
const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.pauseTargetLabel, options.statusMessage);
|
|
28344
|
-
const sessionLines = buildSessionLines(options, totalWidth, pausePresentation);
|
|
28345
|
-
const currentTaskLines = options.currentPromptLabel
|
|
28346
|
-
? [
|
|
28347
|
-
`${promptStatusPrefix}${colors__default["default"].bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
|
|
28348
|
-
`Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
|
|
28349
|
-
...options.detailLines.map((detailLine) => `• ${detailLine}`),
|
|
28350
|
-
]
|
|
28351
|
-
: [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
|
|
28352
|
-
const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
|
|
28353
|
-
const controls = buildCoderRunControlPills({
|
|
28354
|
-
phase: options.phase,
|
|
28355
|
-
pauseControl: pausePresentation.pauseControl,
|
|
28356
|
-
pendingEnterLabel: options.pendingEnterLabel,
|
|
28357
|
-
isEndAfterCurrentPromptRequested: options.isEndAfterCurrentPromptRequested,
|
|
28358
|
-
sessionTotal: options.progress.sessionTotal,
|
|
28359
|
-
}).join(' ');
|
|
28360
|
-
const frame = [
|
|
28361
|
-
...buildFrameHeaderVisual(options, totalWidth, octopusAnimationFrame),
|
|
28362
|
-
'',
|
|
28363
|
-
...renderBox('Session', sessionLines, totalWidth, colors__default["default"].yellow.bold),
|
|
28364
|
-
...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors__default["default"].magenta.bold),
|
|
28365
|
-
...renderBox('Live output', visibleOutputLines, totalWidth, colors__default["default"].green.bold),
|
|
28366
|
-
];
|
|
28367
|
-
if (options.errors.length > 0) {
|
|
28368
|
-
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
28369
|
-
frame.push(...renderBox('Errors', buildErrorDisplayLines(options.errors, bodyWidth), totalWidth, colors__default["default"].red.bold));
|
|
28370
|
-
}
|
|
28371
|
-
frame.push(...renderBox('Controls', [controls], totalWidth, colors__default["default"].white.bold));
|
|
28372
|
-
return frame;
|
|
28373
|
-
}
|
|
28374
|
-
/**
|
|
28375
|
-
* Builds the header visual above the dashboard boxes.
|
|
28376
|
-
*
|
|
28377
|
-
* Shows the ASCII-art `--agent` avatar visual when one is available and
|
|
28378
|
-
* falls back to the default brand banner otherwise.
|
|
28379
|
-
*/
|
|
28380
|
-
function buildFrameHeaderVisual(options, totalWidth, octopusAnimationFrame) {
|
|
28381
|
-
if (options.agentVisualLines !== undefined && options.agentVisualLines.length > 0) {
|
|
28382
|
-
return options.agentVisualLines.map((agentVisualLine) => centerAnsiText(agentVisualLine, totalWidth));
|
|
28383
|
-
}
|
|
28384
|
-
if (options.agentVisual !== undefined) {
|
|
28385
|
-
const agentVisualLines = options.agentVisual.renderFrame({
|
|
28386
|
-
animationTimeMs: options.animationTimeMs,
|
|
28387
|
-
});
|
|
28388
|
-
if (agentVisualLines.length > 0) {
|
|
28389
|
-
return agentVisualLines.map((agentVisualLine) => centerAnsiText(agentVisualLine, totalWidth));
|
|
28390
|
-
}
|
|
28391
|
-
}
|
|
28392
|
-
return buildCoderRunOctopusVisual({ totalWidth, animationFrame: octopusAnimationFrame });
|
|
28393
|
-
}
|
|
28394
|
-
/**
|
|
28395
|
-
* Builds the structured session lines that combine state, runner, queue, and timing metadata.
|
|
28396
|
-
*/
|
|
28397
|
-
function buildSessionLines(options, totalWidth, pausePresentation) {
|
|
28398
|
-
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
28399
|
-
return buildSessionRows(options, bodyWidth, pausePresentation).map((sessionRow) => buildLabeledSessionLine(sessionRow.label, sessionRow.value, bodyWidth));
|
|
28400
|
-
}
|
|
28401
|
-
/**
|
|
28402
|
-
* Builds the session rows so the renderer can keep one consistent structure without duplicating labels.
|
|
28403
|
-
*/
|
|
28404
|
-
function buildSessionRows(options, bodyWidth, pausePresentation) {
|
|
28405
|
-
const runnerParts = [options.config.agentName || 'No agent selected'];
|
|
28406
|
-
if (options.config.modelName) {
|
|
28407
|
-
runnerParts.push(options.config.modelName);
|
|
28408
|
-
}
|
|
28409
|
-
if (options.config.thinkingLevel) {
|
|
28410
|
-
runnerParts.push(`thinking ${options.config.thinkingLevel}`);
|
|
28411
|
-
}
|
|
28412
|
-
const configurationRows = [
|
|
28413
|
-
...buildOptionalSessionRow('Context', options.config.context),
|
|
28414
|
-
...buildOptionalSessionRow('Server', options.config.serverUrl
|
|
28415
|
-
? buildTerminalUrlLink(options.config.serverUrl, bodyWidth - SESSION_LABEL_WIDTH - 1)
|
|
28416
|
-
: undefined),
|
|
28417
|
-
...buildOptionalSessionRow('Test', options.config.testCommand),
|
|
28418
|
-
];
|
|
28419
|
-
return [
|
|
28420
|
-
{
|
|
28421
|
-
label: 'State',
|
|
28422
|
-
value: `${pausePresentation.badge} ${pausePresentation.stateMessage}`,
|
|
28423
|
-
},
|
|
28424
|
-
{
|
|
28425
|
-
label: 'Runner',
|
|
28426
|
-
value: runnerParts.join(' · '),
|
|
28427
|
-
},
|
|
28428
|
-
...configurationRows,
|
|
28429
|
-
...buildScriptPathSessionRows(options.currentScriptPaths || [], bodyWidth),
|
|
28430
|
-
{
|
|
28431
|
-
label: 'This run',
|
|
28432
|
-
value: buildThisRunSummary(options.progress),
|
|
28433
|
-
},
|
|
28434
|
-
{
|
|
28435
|
-
label: 'Backlog',
|
|
28436
|
-
value: buildBacklogSummary(options.progress),
|
|
28437
|
-
},
|
|
28438
|
-
{
|
|
28439
|
-
label: 'Scope',
|
|
28440
|
-
value: buildScopeSummary(options.progress, options.config),
|
|
28441
|
-
},
|
|
28442
|
-
{
|
|
28443
|
-
label: 'Timing',
|
|
28444
|
-
value: buildTimingSummary(options.progress),
|
|
28445
|
-
},
|
|
28446
|
-
{
|
|
28447
|
-
label: 'Progress',
|
|
28448
|
-
value: buildProgressBar$1(options.progress.percentage, bodyWidth - SESSION_LABEL_WIDTH - 1, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
|
|
28449
|
-
},
|
|
28450
|
-
];
|
|
28451
|
-
}
|
|
28452
|
-
/**
|
|
28453
|
-
* Builds zero or one structured session row for optional metadata.
|
|
28454
|
-
*/
|
|
28455
|
-
function buildOptionalSessionRow(label, value) {
|
|
28456
|
-
if (!value) {
|
|
28457
|
-
return [];
|
|
28458
|
-
}
|
|
28459
|
-
return [{ label, value }];
|
|
28460
|
-
}
|
|
28461
|
-
/**
|
|
28462
|
-
* Builds the active-session summary shown in the session box.
|
|
28463
|
-
*/
|
|
28464
|
-
function buildThisRunSummary(progress) {
|
|
28465
|
-
if (progress.sessionTotal === 0) {
|
|
28466
|
-
return 'No runnable prompts in current scope';
|
|
28467
|
-
}
|
|
28468
|
-
return `Task ${progress.currentPromptIndex}/${progress.sessionTotal} · ${progress.sessionDone} done · ${progress.sessionRemaining} left`;
|
|
28469
|
-
}
|
|
28470
|
-
/**
|
|
28471
|
-
* Builds the backlog/filter summary shown in the session box.
|
|
28472
|
-
*/
|
|
28473
|
-
function buildBacklogSummary(progress) {
|
|
28474
|
-
const parts = [`Repo ${progress.totalPrompts} total`];
|
|
28475
|
-
if (progress.skippedPrompts > 0) {
|
|
28476
|
-
parts.push(`${formatPromptCount$1(progress.skippedPrompts)} outside priority scope`);
|
|
28477
|
-
}
|
|
28478
|
-
return parts.join(' · ');
|
|
28479
|
-
}
|
|
28480
|
-
/**
|
|
28481
|
-
* Builds the priority/write-order summary shown in the session box.
|
|
28482
|
-
*/
|
|
28483
|
-
function buildScopeSummary(progress, config) {
|
|
28484
|
-
var _a;
|
|
28485
|
-
const parts = [formatPriorityFilter((_a = config.priorityFilter) !== null && _a !== void 0 ? _a : {})];
|
|
28486
|
-
if (config.limit !== undefined) {
|
|
28487
|
-
parts.push(`Limit ${formatPromptRunCount(config.limit)}`);
|
|
28488
|
-
}
|
|
28489
|
-
if (progress.toBeWrittenPrompts > 0) {
|
|
28490
|
-
parts.push(`Write ${formatPromptCount$1(progress.toBeWrittenPrompts)} first`);
|
|
28491
|
-
}
|
|
28492
|
-
return parts.join(' · ');
|
|
28493
|
-
}
|
|
28494
|
-
/**
|
|
28495
|
-
* Builds the elapsed/estimate summary shown in the session box.
|
|
28496
|
-
*/
|
|
28497
|
-
function buildTimingSummary(progress) {
|
|
28498
|
-
return `Elapsed ${progress.elapsedText} · Total ${progress.estimatedTotalText} · ETA ${progress.estimatedLabel}`;
|
|
28499
|
-
}
|
|
28500
|
-
/**
|
|
28501
|
-
* Formats a prompt count with singular/plural wording.
|
|
28502
|
-
*/
|
|
28503
|
-
function formatPromptCount$1(count) {
|
|
28504
|
-
return `${count} prompt${count === 1 ? '' : 's'}`;
|
|
28505
|
-
}
|
|
28506
|
-
/**
|
|
28507
|
-
* Formats a configured successful prompt-run limit with singular/plural wording.
|
|
28508
|
-
*/
|
|
28509
|
-
function formatPromptRunCount(count) {
|
|
28510
|
-
return `${count} prompt run${count === 1 ? '' : 's'}`;
|
|
28511
|
-
}
|
|
28512
|
-
|
|
28513
|
-
/**
|
|
28514
|
-
* ANSI sequence that clears the current terminal row.
|
|
28515
|
-
*
|
|
28516
|
-
* @private internal constant of coder run UI
|
|
28517
|
-
*/
|
|
28518
|
-
const CLEAR_CURRENT_LINE = '\x1b[2K';
|
|
28519
|
-
/**
|
|
28520
|
-
* ANSI control character that moves the cursor to the start of its current row.
|
|
28521
|
-
*
|
|
28522
|
-
* @private internal constant of coder run UI
|
|
28523
|
-
*/
|
|
28524
|
-
const MOVE_CURSOR_TO_LINE_START = '\r';
|
|
28525
|
-
/**
|
|
28526
|
-
* Builds one atomic ANSI update that changes the displayed coder-run terminal frame.
|
|
28527
|
-
*
|
|
28528
|
-
* A change in frame height requires a full rewrite to reserve or clear rows correctly. Frames with
|
|
28529
|
-
* the same height only rewrite contiguous groups of changed rows, returning to the frame bottom
|
|
28530
|
-
* between groups so each group can be assembled into the same output payload safely.
|
|
28531
|
-
*
|
|
28532
|
-
* @returns One terminal output payload, or `undefined` when the frame is already up to date.
|
|
28533
|
-
*
|
|
28534
|
-
* @private internal utility of coder run UI
|
|
28535
|
-
*/
|
|
28536
|
-
function buildCoderRunUiTerminalFrameUpdate(options) {
|
|
28537
|
-
const { previousFrameLines, nextFrameLines } = options;
|
|
28538
|
-
if (previousFrameLines.length === 0 && nextFrameLines.length === 0) {
|
|
28539
|
-
return undefined;
|
|
28540
|
-
}
|
|
28541
|
-
if (previousFrameLines.length === 0 || previousFrameLines.length !== nextFrameLines.length) {
|
|
28542
|
-
return buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
|
|
28543
|
-
}
|
|
28544
|
-
return buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
|
|
28545
|
-
}
|
|
28546
|
-
/**
|
|
28547
|
-
* Builds an ANSI update which completely rewrites the reserved terminal frame area.
|
|
28548
|
-
*
|
|
28549
|
-
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
28550
|
-
*/
|
|
28551
|
-
function buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
|
|
28552
|
-
var _a;
|
|
28553
|
-
const previousFrameLineCount = previousFrameLines.length;
|
|
28554
|
-
const lineCountToRewrite = Math.max(previousFrameLineCount, nextFrameLines.length);
|
|
28555
|
-
const outputParts = [];
|
|
28556
|
-
if (previousFrameLineCount > 1) {
|
|
28557
|
-
outputParts.push(moveCursorUp(previousFrameLineCount - 1));
|
|
28558
|
-
}
|
|
28559
|
-
for (let lineIndex = 0; lineIndex < lineCountToRewrite; lineIndex++) {
|
|
28560
|
-
outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, (_a = nextFrameLines[lineIndex]) !== null && _a !== void 0 ? _a : '');
|
|
28561
|
-
if (lineIndex < lineCountToRewrite - 1) {
|
|
28562
|
-
outputParts.push('\n');
|
|
28563
|
-
}
|
|
28564
|
-
}
|
|
28565
|
-
const clearedTrailingLineCount = lineCountToRewrite - nextFrameLines.length;
|
|
28566
|
-
if (clearedTrailingLineCount > 0) {
|
|
28567
|
-
outputParts.push(moveCursorUp(clearedTrailingLineCount));
|
|
28568
|
-
}
|
|
28569
|
-
outputParts.push(MOVE_CURSOR_TO_LINE_START);
|
|
28570
|
-
return outputParts.join('');
|
|
28571
|
-
}
|
|
28572
|
-
/**
|
|
28573
|
-
* Builds an ANSI update that rewrites only contiguous groups of rows which changed.
|
|
28574
|
-
*
|
|
28575
|
-
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
28576
|
-
*/
|
|
28577
|
-
function buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
|
|
28578
|
-
const outputParts = [];
|
|
28579
|
-
let lineIndex = 0;
|
|
28580
|
-
while (lineIndex < nextFrameLines.length) {
|
|
28581
|
-
if (previousFrameLines[lineIndex] === nextFrameLines[lineIndex]) {
|
|
28582
|
-
lineIndex++;
|
|
28583
|
-
continue;
|
|
28584
|
-
}
|
|
28585
|
-
const changedRangeStartLineIndex = lineIndex;
|
|
28586
|
-
while (lineIndex + 1 < nextFrameLines.length &&
|
|
28587
|
-
previousFrameLines[lineIndex + 1] !== nextFrameLines[lineIndex + 1]) {
|
|
28588
|
-
lineIndex++;
|
|
28589
|
-
}
|
|
28590
|
-
appendChangedCoderRunUiTerminalFrameRange({
|
|
28591
|
-
outputParts,
|
|
28592
|
-
frameLineCount: nextFrameLines.length,
|
|
28593
|
-
changedRangeStartLineIndex,
|
|
28594
|
-
changedRangeEndLineIndex: lineIndex,
|
|
28595
|
-
nextFrameLines,
|
|
28596
|
-
});
|
|
28597
|
-
lineIndex++;
|
|
28598
|
-
}
|
|
28599
|
-
return outputParts.length === 0 ? undefined : outputParts.join('');
|
|
28600
|
-
}
|
|
28601
|
-
/**
|
|
28602
|
-
* Appends the cursor movements and row rewrites for one contiguous changed frame range.
|
|
28603
|
-
*
|
|
28604
|
-
* @private helper of `buildChangedCoderRunUiTerminalFrameUpdate`
|
|
28605
|
-
*/
|
|
28606
|
-
function appendChangedCoderRunUiTerminalFrameRange(options) {
|
|
28607
|
-
const { outputParts, frameLineCount, changedRangeStartLineIndex, changedRangeEndLineIndex, nextFrameLines } = options;
|
|
28608
|
-
const linesUpFromFrameBottom = frameLineCount - 1 - changedRangeStartLineIndex;
|
|
28609
|
-
if (linesUpFromFrameBottom > 0) {
|
|
28610
|
-
outputParts.push(moveCursorUp(linesUpFromFrameBottom));
|
|
28611
|
-
}
|
|
28612
|
-
for (let lineIndex = changedRangeStartLineIndex; lineIndex <= changedRangeEndLineIndex; lineIndex++) {
|
|
28613
|
-
outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, nextFrameLines[lineIndex]);
|
|
28614
|
-
if (lineIndex < changedRangeEndLineIndex) {
|
|
28615
|
-
outputParts.push('\n');
|
|
28616
|
-
}
|
|
28617
|
-
}
|
|
28618
|
-
const linesDownToFrameBottom = frameLineCount - 1 - changedRangeEndLineIndex;
|
|
28619
|
-
if (linesDownToFrameBottom > 0) {
|
|
28620
|
-
outputParts.push(moveCursorDown(linesDownToFrameBottom));
|
|
28621
|
-
}
|
|
28622
|
-
outputParts.push(MOVE_CURSOR_TO_LINE_START);
|
|
28623
|
-
}
|
|
28624
|
-
/**
|
|
28625
|
-
* Builds an ANSI sequence which moves the terminal cursor up by a positive number of rows.
|
|
28626
|
-
*
|
|
28627
|
-
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
28628
|
-
*/
|
|
28629
|
-
function moveCursorUp(lineCount) {
|
|
28630
|
-
return `\x1b[${lineCount}A`;
|
|
28631
|
-
}
|
|
28632
|
-
/**
|
|
28633
|
-
* Builds an ANSI sequence which moves the terminal cursor down by a positive number of rows.
|
|
28634
|
-
*
|
|
28635
|
-
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
28636
|
-
*/
|
|
28637
|
-
function moveCursorDown(lineCount) {
|
|
28638
|
-
return `\x1b[${lineCount}B`;
|
|
28639
|
-
}
|
|
28640
|
-
|
|
28641
|
-
/**
|
|
28642
|
-
* Calendar formats used when displaying the estimated completion time.
|
|
28643
|
-
*
|
|
28644
|
-
* Uses 24-hour format (`HH:mm`) so estimates are unambiguous (for example `17:30` instead of `5:30`).
|
|
28645
|
-
*/
|
|
28646
|
-
const ESTIMATED_DONE_CALENDAR_FORMATS = {
|
|
28647
|
-
sameDay: '[Today] HH:mm',
|
|
28648
|
-
nextDay: '[Tomorrow] HH:mm',
|
|
28649
|
-
nextWeek: 'dddd HH:mm',
|
|
28650
|
-
lastDay: '[Yesterday] HH:mm',
|
|
28651
|
-
lastWeek: 'dddd HH:mm',
|
|
28652
|
-
sameElse: 'MMM D HH:mm',
|
|
28653
|
-
};
|
|
28654
|
-
/**
|
|
28655
|
-
* Formats a duration into a compact string such as "3h 12m" or "45s".
|
|
28656
|
-
*/
|
|
28657
|
-
function formatDurationBrief(duration) {
|
|
28658
|
-
const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
|
|
28659
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
28660
|
-
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
28661
|
-
const seconds = totalSeconds % 60;
|
|
28662
|
-
const parts = [];
|
|
28663
|
-
if (hours > 0) {
|
|
28664
|
-
parts.push(`${hours}h`);
|
|
28665
|
-
}
|
|
28666
|
-
if (minutes > 0) {
|
|
28667
|
-
parts.push(`${minutes}m`);
|
|
28668
|
-
}
|
|
28669
|
-
if (!parts.length && seconds > 0) {
|
|
28670
|
-
parts.push(`${seconds}s`);
|
|
28671
|
-
}
|
|
28672
|
-
if (!parts.length) {
|
|
28673
|
-
parts.push('0s');
|
|
28674
|
-
}
|
|
28675
|
-
return parts.join(' ');
|
|
28676
|
-
}
|
|
28677
|
-
|
|
28678
|
-
/**
|
|
28679
|
-
* Builds a session-scoped progress snapshot from prompt stats and elapsed active time.
|
|
28680
|
-
*
|
|
28681
|
-
* When the current session has not finished any prompt yet but a `cachedAveragePromptDurationMs` is
|
|
28682
|
-
* provided (loaded from the per-config estimate cache stored in the temp folder), the snapshot uses
|
|
28683
|
-
* that historical average to project the remaining work instead of waiting for the first completion.
|
|
28684
|
-
*/
|
|
28685
|
-
function buildCoderRunProgressSnapshot(stats, elapsedDuration, initialDone, cachedAveragePromptDurationMs, runLimit) {
|
|
28686
|
-
const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
|
|
28687
|
-
const completedSessionPrompts = Math.max(0, stats.done - initialDone);
|
|
28688
|
-
const availableSessionTotal = completedSessionPrompts + stats.forAgent;
|
|
28689
|
-
const sessionTotal = resolveSessionTotal(availableSessionTotal, runLimit);
|
|
28690
|
-
const sessionDone = Math.min(completedSessionPrompts, sessionTotal);
|
|
28691
|
-
const sessionRemaining = Math.max(0, sessionTotal - sessionDone);
|
|
28692
|
-
const currentPromptIndex = sessionTotal > 0 ? Math.min(sessionDone + 1, sessionTotal) : 0;
|
|
28693
|
-
const percentage = sessionTotal > 0 ? Math.round((sessionDone / sessionTotal) * 100) : 0;
|
|
28694
|
-
const elapsedText = formatDurationBrief(elapsedDuration);
|
|
28695
|
-
const elapsedMs = elapsedDuration.asMilliseconds();
|
|
28696
|
-
let estimatedTotalText = 'estimating...';
|
|
28697
|
-
let estimatedLabel = 'after first completion';
|
|
28698
|
-
let isEstimatedTotalKnown = false;
|
|
28699
|
-
let estimatedTotalMs;
|
|
28700
|
-
if (sessionTotal > 0 && sessionDone > 0) {
|
|
28701
|
-
estimatedTotalMs = (elapsedMs * sessionTotal) / sessionDone;
|
|
28702
|
-
}
|
|
28703
|
-
else if (sessionTotal > 0 &&
|
|
28704
|
-
cachedAveragePromptDurationMs !== undefined &&
|
|
28705
|
-
cachedAveragePromptDurationMs > 0) {
|
|
28706
|
-
const projectedRemainingMs = sessionRemaining * cachedAveragePromptDurationMs;
|
|
28707
|
-
estimatedTotalMs = elapsedMs + projectedRemainingMs;
|
|
28708
|
-
}
|
|
28709
|
-
if (estimatedTotalMs !== undefined) {
|
|
28710
|
-
const estimatedRemainingMs = Math.max(0, estimatedTotalMs - elapsedMs);
|
|
28711
|
-
const estimatedTotalDuration = moment__default["default"].duration(estimatedTotalMs);
|
|
28712
|
-
const estimatedCompletion = moment__default["default"]().add(estimatedRemainingMs, 'milliseconds');
|
|
28713
|
-
estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
|
|
28714
|
-
estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
|
|
28715
|
-
isEstimatedTotalKnown = true;
|
|
28716
|
-
}
|
|
28997
|
+
* @private shared helper for terminal avatar rendering
|
|
28998
|
+
*/
|
|
28999
|
+
function createTerminalAgentAvatarVisual(options) {
|
|
29000
|
+
const renderInputs = createTerminalAgentAvatarVisualRenderInputs(options.agentSource, options.defaultAvatarVisualId || DEFAULT_AGENT_AVATAR_VISUAL_ID);
|
|
28717
29001
|
return {
|
|
28718
|
-
|
|
28719
|
-
|
|
28720
|
-
|
|
28721
|
-
|
|
28722
|
-
|
|
28723
|
-
|
|
28724
|
-
|
|
28725
|
-
|
|
28726
|
-
|
|
28727
|
-
estimatedTotalText,
|
|
28728
|
-
estimatedLabel,
|
|
28729
|
-
isEstimatedTotalKnown,
|
|
29002
|
+
isAnimated: renderInputs.resolvedAvatarRenderDefinition.avatarVisual.isAnimated,
|
|
29003
|
+
renderFrame({ animationTimeMs }) {
|
|
29004
|
+
return renderTerminalAgentAvatarVisualFrame({
|
|
29005
|
+
...renderInputs,
|
|
29006
|
+
animationTimeMs,
|
|
29007
|
+
colorDepth: options.colorDepth,
|
|
29008
|
+
createCanvas: options.createCanvas,
|
|
29009
|
+
});
|
|
29010
|
+
},
|
|
28730
29011
|
};
|
|
28731
29012
|
}
|
|
28732
29013
|
/**
|
|
28733
|
-
*
|
|
29014
|
+
* Creates stable avatar render inputs from one agent source.
|
|
29015
|
+
*
|
|
29016
|
+
* @private shared helper for terminal avatar rendering
|
|
28734
29017
|
*/
|
|
28735
|
-
function
|
|
28736
|
-
|
|
28737
|
-
|
|
28738
|
-
|
|
28739
|
-
|
|
29018
|
+
function createTerminalAgentAvatarVisualRenderInputs(agentSource, defaultAvatarVisualId) {
|
|
29019
|
+
const agentBasicInformation = parseAgentSource(agentSource);
|
|
29020
|
+
const avatarDefinition = createAvatarDefinitionFromAgentBasicInformation(agentBasicInformation);
|
|
29021
|
+
const avatarVisualId = resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId);
|
|
29022
|
+
const resolvedAvatarRenderDefinition = resolveAvatarRenderDefinition({
|
|
29023
|
+
avatarDefinition,
|
|
29024
|
+
visualId: avatarVisualId,
|
|
29025
|
+
surface: 'transparent',
|
|
29026
|
+
});
|
|
29027
|
+
return {
|
|
29028
|
+
avatarDefinition,
|
|
29029
|
+
avatarVisualId,
|
|
29030
|
+
resolvedAvatarRenderDefinition,
|
|
29031
|
+
};
|
|
28740
29032
|
}
|
|
28741
|
-
|
|
28742
29033
|
/**
|
|
28743
|
-
*
|
|
29034
|
+
* Resolves the terminal avatar visual id from already parsed agent information.
|
|
29035
|
+
*
|
|
29036
|
+
* @private shared helper for terminal avatar rendering
|
|
28744
29037
|
*/
|
|
28745
|
-
|
|
28746
|
-
|
|
28747
|
-
|
|
28748
|
-
|
|
28749
|
-
|
|
28750
|
-
|
|
28751
|
-
|
|
28752
|
-
|
|
28753
|
-
|
|
28754
|
-
|
|
28755
|
-
|
|
28756
|
-
|
|
28757
|
-
|
|
28758
|
-
|
|
28759
|
-
|
|
28760
|
-
|
|
28761
|
-
|
|
28762
|
-
|
|
28763
|
-
|
|
28764
|
-
|
|
28765
|
-
|
|
28766
|
-
|
|
28767
|
-
|
|
28768
|
-
|
|
28769
|
-
|
|
28770
|
-
resume() {
|
|
28771
|
-
if (this.pausedSince !== undefined) {
|
|
28772
|
-
this.pausedMs += moment__default["default"]().diff(this.pausedSince);
|
|
28773
|
-
this.pausedSince = undefined;
|
|
28774
|
-
}
|
|
29038
|
+
function resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId) {
|
|
29039
|
+
return resolveAgentAvatarVisualId(agentBasicInformation, resolveAvatarVisualId(agentBasicInformation.meta.visual) || defaultAvatarVisualId);
|
|
29040
|
+
}
|
|
29041
|
+
/**
|
|
29042
|
+
* Renders one terminal avatar frame through the shared avatar-to-ASCII pipeline.
|
|
29043
|
+
*
|
|
29044
|
+
* Visuals which are made of characters themselves, for example `AsciiOctopus`, paint the terminal
|
|
29045
|
+
* character grid directly, because rasterizing them into half-block ASCII art averages every glyph
|
|
29046
|
+
* away and makes them indistinguishable from the other blob-shaped visuals.
|
|
29047
|
+
*
|
|
29048
|
+
* @private shared helper for terminal avatar rendering
|
|
29049
|
+
*/
|
|
29050
|
+
function renderTerminalAgentAvatarVisualFrame(options) {
|
|
29051
|
+
const terminalTextLines = renderAvatarVisualTerminalTextLines({
|
|
29052
|
+
avatarDefinition: options.avatarDefinition,
|
|
29053
|
+
visualId: options.avatarVisualId,
|
|
29054
|
+
surface: 'transparent',
|
|
29055
|
+
columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
|
|
29056
|
+
rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
|
|
29057
|
+
colorDepth: options.colorDepth,
|
|
29058
|
+
timeMs: options.animationTimeMs,
|
|
29059
|
+
resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
|
|
29060
|
+
});
|
|
29061
|
+
if (terminalTextLines !== null) {
|
|
29062
|
+
return terminalTextLines;
|
|
28775
29063
|
}
|
|
28776
|
-
|
|
28777
|
-
|
|
28778
|
-
|
|
28779
|
-
getElapsedDuration() {
|
|
28780
|
-
const wallMs = moment__default["default"]().diff(this.startTime);
|
|
28781
|
-
const currentPauseMs = this.pausedSince !== undefined ? moment__default["default"]().diff(this.pausedSince) : 0;
|
|
28782
|
-
return moment__default["default"].duration(Math.max(0, wallMs - this.pausedMs - currentPauseMs));
|
|
29064
|
+
if (options.createCanvas === undefined) {
|
|
29065
|
+
// Note: A pixel-based visual cannot be rasterized without a canvas, so the caller renders no avatar
|
|
29066
|
+
return [];
|
|
28783
29067
|
}
|
|
29068
|
+
return renderAvatarVisualAsciiArt({
|
|
29069
|
+
avatarDefinition: options.avatarDefinition,
|
|
29070
|
+
visualId: options.avatarVisualId,
|
|
29071
|
+
surface: 'transparent',
|
|
29072
|
+
columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
|
|
29073
|
+
rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
|
|
29074
|
+
canvasWidth: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_WIDTH,
|
|
29075
|
+
canvasHeight: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_HEIGHT,
|
|
29076
|
+
colorDepth: options.colorDepth,
|
|
29077
|
+
timeMs: options.animationTimeMs,
|
|
29078
|
+
createCanvas: options.createCanvas,
|
|
29079
|
+
resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
|
|
29080
|
+
});
|
|
28784
29081
|
}
|
|
28785
29082
|
|
|
28786
29083
|
/**
|
|
28787
|
-
*
|
|
29084
|
+
* Refresh cadence used only while the rich coder UI needs animated updates.
|
|
28788
29085
|
*
|
|
28789
29086
|
* @private internal constant of coder run UI
|
|
28790
29087
|
*/
|
|
28791
|
-
const
|
|
29088
|
+
const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = TERMINAL_AGENT_AVATAR_VISUAL_REFRESH_INTERVAL_MS;
|
|
28792
29089
|
/**
|
|
28793
|
-
*
|
|
29090
|
+
* Phases that still benefit from automatic refreshes because the frame can change
|
|
29091
|
+
* over time even without new runner output.
|
|
28794
29092
|
*
|
|
28795
|
-
*
|
|
28796
|
-
|
|
29093
|
+
* @private internal constant of coder run UI
|
|
29094
|
+
*/
|
|
29095
|
+
const AUTO_REFRESH_PHASES = ['initializing', 'loading', 'running', 'verifying'];
|
|
29096
|
+
/**
|
|
29097
|
+
* Phases where an animated `--agent` visual should keep rendering like it does on the web.
|
|
29098
|
+
*
|
|
29099
|
+
* @private internal constant of coder run UI
|
|
29100
|
+
*/
|
|
29101
|
+
const AGENT_VISUAL_AUTO_REFRESH_PHASES = [...AUTO_REFRESH_PHASES, 'waiting'];
|
|
29102
|
+
/**
|
|
29103
|
+
* Returns whether the rich coder UI should keep animating on its own.
|
|
28797
29104
|
*
|
|
28798
29105
|
* @private internal utility of coder run UI
|
|
28799
29106
|
*/
|
|
28800
|
-
|
|
28801
|
-
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
this.currentScriptPaths = [];
|
|
28806
|
-
this.currentAttempt = 1;
|
|
28807
|
-
this.maxAttempts = 3;
|
|
28808
|
-
this.detailLines = [];
|
|
28809
|
-
this.messagePreviewLines = [];
|
|
28810
|
-
this.messagePreviewSections = [];
|
|
28811
|
-
this.agentStatusLines = [];
|
|
28812
|
-
this.agentStatusTableRows = [];
|
|
28813
|
-
this.agentOutputLines = [];
|
|
28814
|
-
this.phase = 'initializing';
|
|
28815
|
-
this.statusMessage = 'Initializing...';
|
|
28816
|
-
this.errors = [];
|
|
28817
|
-
this.stats = { done: 0, forAgent: 0, outsidePriorityRange: 0, toBeWritten: 0 };
|
|
28818
|
-
// Match the plain CLI progress header and count active run time from startup.
|
|
28819
|
-
// Explicit waits/pauses are excluded later through `pauseTimer()` / `resumeTimer()`.
|
|
28820
|
-
this.timer = new CoderRunTimer(startTime);
|
|
28821
|
-
}
|
|
28822
|
-
/**
|
|
28823
|
-
* Pauses the elapsed timer (e.g. while waiting for user input or paused state).
|
|
28824
|
-
*/
|
|
28825
|
-
pauseTimer() {
|
|
28826
|
-
this.timer.pause();
|
|
28827
|
-
this.emitChange();
|
|
28828
|
-
}
|
|
28829
|
-
/**
|
|
28830
|
-
* Resumes the elapsed timer after a pause.
|
|
28831
|
-
*/
|
|
28832
|
-
resumeTimer() {
|
|
28833
|
-
this.timer.resume();
|
|
28834
|
-
this.emitChange();
|
|
28835
|
-
}
|
|
28836
|
-
/**
|
|
28837
|
-
* Replaces the configuration shown in the UI header.
|
|
28838
|
-
*/
|
|
28839
|
-
setConfig(config) {
|
|
28840
|
-
this.config = config;
|
|
28841
|
-
this.emitChange();
|
|
28842
|
-
}
|
|
28843
|
-
/**
|
|
28844
|
-
* Replaces the ASCII-art agent visual renderer shown instead of the default brand banner.
|
|
28845
|
-
*/
|
|
28846
|
-
setAgentVisual(agentVisual) {
|
|
28847
|
-
this.agentVisual = agentVisual;
|
|
28848
|
-
this.emitChange();
|
|
28849
|
-
}
|
|
28850
|
-
/**
|
|
28851
|
-
* Feeds new prompt statistics from the main loop so the progress bar updates.
|
|
28852
|
-
*/
|
|
28853
|
-
updateProgress(stats) {
|
|
28854
|
-
if (this.initialDone === undefined && (stats.done > 0 || stats.forAgent > 0 || stats.toBeWritten > 0)) {
|
|
28855
|
-
this.initialDone = stats.done;
|
|
28856
|
-
}
|
|
28857
|
-
this.stats = stats;
|
|
28858
|
-
this.emitChange();
|
|
28859
|
-
}
|
|
28860
|
-
/**
|
|
28861
|
-
* Sets the cached average prompt duration (in milliseconds) loaded from the temp-folder estimate cache.
|
|
28862
|
-
*
|
|
28863
|
-
* Used to project completion estimates before the first prompt of the current session finishes.
|
|
28864
|
-
*/
|
|
28865
|
-
setCachedAveragePromptDurationMs(cachedAveragePromptDurationMs) {
|
|
28866
|
-
this.cachedAveragePromptDurationMs = cachedAveragePromptDurationMs;
|
|
28867
|
-
this.emitChange();
|
|
28868
|
-
}
|
|
28869
|
-
/**
|
|
28870
|
-
* Computes a progress snapshot on demand so elapsed time ticks with periodic re-renders.
|
|
28871
|
-
*/
|
|
28872
|
-
getProgress() {
|
|
28873
|
-
var _a;
|
|
28874
|
-
return buildCoderRunProgressSnapshot(this.stats, this.timer.getElapsedDuration(), (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done, this.cachedAveragePromptDurationMs, this.config.limit);
|
|
28875
|
-
}
|
|
28876
|
-
/**
|
|
28877
|
-
* Sets the label of the prompt currently being processed and resets per-prompt state.
|
|
28878
|
-
*/
|
|
28879
|
-
setCurrentPrompt(label) {
|
|
28880
|
-
this.currentPromptLabel = label;
|
|
28881
|
-
this.currentScriptPaths = [];
|
|
28882
|
-
this.detailLines = [];
|
|
28883
|
-
this.messagePreviewLines = [];
|
|
28884
|
-
this.messagePreviewSections = [];
|
|
28885
|
-
this.pendingEnterLabel = undefined;
|
|
28886
|
-
this.agentOutputLines = [];
|
|
28887
|
-
this.currentAttempt = 1;
|
|
28888
|
-
this.emitChange();
|
|
28889
|
-
}
|
|
28890
|
-
/**
|
|
28891
|
-
* Sets the temporary shell script currently used by the active prompt.
|
|
28892
|
-
*/
|
|
28893
|
-
setCurrentScriptPath(scriptPath) {
|
|
28894
|
-
this.currentScriptPaths = scriptPath ? [scriptPath] : [];
|
|
28895
|
-
this.emitChange();
|
|
28896
|
-
}
|
|
28897
|
-
/**
|
|
28898
|
-
* Sets multiple temporary shell scripts used by a shared multi-agent session.
|
|
28899
|
-
*/
|
|
28900
|
-
setCurrentScriptPaths(scriptPaths) {
|
|
28901
|
-
this.currentScriptPaths = [...scriptPaths];
|
|
28902
|
-
this.emitChange();
|
|
28903
|
-
}
|
|
28904
|
-
/**
|
|
28905
|
-
* Updates the current retry attempt number.
|
|
28906
|
-
*/
|
|
28907
|
-
setAttempt(attempt) {
|
|
28908
|
-
this.currentAttempt = attempt;
|
|
28909
|
-
this.emitChange();
|
|
28910
|
-
}
|
|
28911
|
-
/**
|
|
28912
|
-
* Appends raw agent output text, keeping only the last `MAX_AGENT_OUTPUT_LINES`.
|
|
28913
|
-
*/
|
|
28914
|
-
addAgentOutput(text) {
|
|
28915
|
-
const lines = text.split(/\r?\n/).filter((line) => line.trim() !== '');
|
|
28916
|
-
if (lines.length === 0) {
|
|
28917
|
-
return;
|
|
28918
|
-
}
|
|
28919
|
-
this.agentOutputLines.push(...lines);
|
|
28920
|
-
if (this.agentOutputLines.length > MAX_AGENT_OUTPUT_LINES) {
|
|
28921
|
-
this.agentOutputLines = this.agentOutputLines.slice(-MAX_AGENT_OUTPUT_LINES);
|
|
28922
|
-
}
|
|
28923
|
-
this.emitChange();
|
|
28924
|
-
}
|
|
28925
|
-
/**
|
|
28926
|
-
* Transitions the execution phase shown in the UI.
|
|
28927
|
-
*/
|
|
28928
|
-
setPhase(phase) {
|
|
28929
|
-
this.phase = phase;
|
|
28930
|
-
this.emitChange();
|
|
28931
|
-
}
|
|
28932
|
-
/**
|
|
28933
|
-
* Updates the status message line beneath the current prompt label.
|
|
28934
|
-
*/
|
|
28935
|
-
setStatusMessage(message) {
|
|
28936
|
-
this.statusMessage = message;
|
|
28937
|
-
this.emitChange();
|
|
28938
|
-
}
|
|
28939
|
-
/**
|
|
28940
|
-
* Replaces the contextual detail lines shown beneath the current prompt status.
|
|
28941
|
-
*/
|
|
28942
|
-
setDetailLines(detailLines) {
|
|
28943
|
-
this.detailLines = detailLines.filter((detailLine) => detailLine.trim() !== '');
|
|
28944
|
-
this.emitChange();
|
|
28945
|
-
}
|
|
28946
|
-
/**
|
|
28947
|
-
* Replaces the exact user-message preview lines shown in agent-specific panels.
|
|
28948
|
-
*/
|
|
28949
|
-
setMessagePreviewLines(messagePreviewLines) {
|
|
28950
|
-
this.messagePreviewLines = [...messagePreviewLines];
|
|
28951
|
-
this.emitChange();
|
|
28952
|
-
}
|
|
28953
|
-
/**
|
|
28954
|
-
* Replaces the structured user-message panels shown in agent-specific dashboards.
|
|
28955
|
-
*/
|
|
28956
|
-
setMessagePreviewSections(messagePreviewSections) {
|
|
28957
|
-
this.messagePreviewSections = [...messagePreviewSections];
|
|
28958
|
-
this.emitChange();
|
|
28959
|
-
}
|
|
28960
|
-
/**
|
|
28961
|
-
* Replaces agent status rows shown by agent-specific terminal frames.
|
|
28962
|
-
*/
|
|
28963
|
-
setAgentStatusLines(agentStatusLines) {
|
|
28964
|
-
this.agentStatusLines = [...agentStatusLines];
|
|
28965
|
-
this.emitChange();
|
|
28966
|
-
}
|
|
28967
|
-
/**
|
|
28968
|
-
* Replaces structured agent status rows shown by agent-specific terminal frames.
|
|
28969
|
-
*/
|
|
28970
|
-
setAgentStatusTableRows(agentStatusTableRows) {
|
|
28971
|
-
this.agentStatusTableRows = [...agentStatusTableRows];
|
|
28972
|
-
this.emitChange();
|
|
28973
|
-
}
|
|
28974
|
-
/**
|
|
28975
|
-
* Sets or clears the Enter-key action label shown in the controls panel.
|
|
28976
|
-
*/
|
|
28977
|
-
setPendingEnterLabel(pendingEnterLabel) {
|
|
28978
|
-
this.pendingEnterLabel = pendingEnterLabel;
|
|
28979
|
-
this.emitChange();
|
|
28980
|
-
}
|
|
28981
|
-
/**
|
|
28982
|
-
* Appends an error message to the error list shown in the UI.
|
|
28983
|
-
*/
|
|
28984
|
-
addError(errorMessage) {
|
|
28985
|
-
this.errors.push(errorMessage);
|
|
28986
|
-
this.emitChange();
|
|
29107
|
+
function isCoderRunUiAutoRefreshing(phase, pauseState, isAgentVisualAnimated = false) {
|
|
29108
|
+
// `PAUSING` still means the current task is winding down, so keep active
|
|
29109
|
+
// animations/timers running until the runner reaches the fully paused state.
|
|
29110
|
+
if (pauseState === 'PAUSED') {
|
|
29111
|
+
return false;
|
|
28987
29112
|
}
|
|
28988
|
-
|
|
28989
|
-
|
|
29113
|
+
if (isAgentVisualAnimated) {
|
|
29114
|
+
return AGENT_VISUAL_AUTO_REFRESH_PHASES.includes(phase);
|
|
28990
29115
|
}
|
|
29116
|
+
return AUTO_REFRESH_PHASES.includes(phase);
|
|
29117
|
+
}
|
|
29118
|
+
/**
|
|
29119
|
+
* Returns the automatic refresh interval for the current UI state.
|
|
29120
|
+
*
|
|
29121
|
+
* Waiting, paused, and completed states return `undefined` so the rich UI stays
|
|
29122
|
+
* perfectly still until actual state changes arrive.
|
|
29123
|
+
*
|
|
29124
|
+
* @private internal utility of coder run UI
|
|
29125
|
+
*/
|
|
29126
|
+
function getCoderRunUiAutoRefreshInterval(phase, pauseState, isAgentVisualAnimated = false) {
|
|
29127
|
+
return isCoderRunUiAutoRefreshing(phase, pauseState, isAgentVisualAnimated)
|
|
29128
|
+
? ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS
|
|
29129
|
+
: undefined;
|
|
28991
29130
|
}
|
|
28992
29131
|
|
|
28993
29132
|
/**
|
|
@@ -29085,6 +29224,7 @@
|
|
|
29085
29224
|
let isRendering = false;
|
|
29086
29225
|
let renderScheduled = false;
|
|
29087
29226
|
let autoRefreshTimeout;
|
|
29227
|
+
let controlFeedbackTimeout;
|
|
29088
29228
|
let isDisposed = false;
|
|
29089
29229
|
/**
|
|
29090
29230
|
* Schedules a render on the next tick if one isn't already pending.
|
|
@@ -29150,9 +29290,32 @@
|
|
|
29150
29290
|
pendingEnterLabel: state.pendingEnterLabel,
|
|
29151
29291
|
agentOutputLines: state.agentOutputLines,
|
|
29152
29292
|
errors: state.errors,
|
|
29293
|
+
controlFeedback: state.controlFeedback,
|
|
29153
29294
|
progress: state.getProgress(),
|
|
29154
29295
|
});
|
|
29155
29296
|
}
|
|
29297
|
+
/**
|
|
29298
|
+
* Answers one pressed control key in the frame and hides that answer again after a short moment.
|
|
29299
|
+
*/
|
|
29300
|
+
function showControlFeedback(controlFeedback) {
|
|
29301
|
+
clearControlFeedbackTimeout();
|
|
29302
|
+
// Note: Every state change re-renders the frame, so the answer appears with the very next frame
|
|
29303
|
+
state.setControlFeedback(controlFeedback);
|
|
29304
|
+
controlFeedbackTimeout = setTimeout(() => {
|
|
29305
|
+
controlFeedbackTimeout = undefined;
|
|
29306
|
+
state.setControlFeedback(undefined);
|
|
29307
|
+
}, CODER_RUN_CONTROL_FEEDBACK_DURATION_MS);
|
|
29308
|
+
}
|
|
29309
|
+
/**
|
|
29310
|
+
* Stops the timer which hides the currently shown control answer.
|
|
29311
|
+
*/
|
|
29312
|
+
function clearControlFeedbackTimeout() {
|
|
29313
|
+
if (controlFeedbackTimeout === undefined) {
|
|
29314
|
+
return;
|
|
29315
|
+
}
|
|
29316
|
+
clearTimeout(controlFeedbackTimeout);
|
|
29317
|
+
controlFeedbackTimeout = undefined;
|
|
29318
|
+
}
|
|
29156
29319
|
/**
|
|
29157
29320
|
* Clears previously rendered lines and writes a new frame only where needed.
|
|
29158
29321
|
*/
|
|
@@ -29185,19 +29348,10 @@
|
|
|
29185
29348
|
cleanup();
|
|
29186
29349
|
process.exit(0);
|
|
29187
29350
|
}
|
|
29188
|
-
|
|
29189
|
-
|
|
29190
|
-
|
|
29191
|
-
|
|
29192
|
-
}
|
|
29193
|
-
if (key.name === 's') {
|
|
29194
|
-
requestSkipCurrentWait();
|
|
29195
|
-
scheduleRender();
|
|
29196
|
-
return;
|
|
29197
|
-
}
|
|
29198
|
-
if (key.name === 'x') {
|
|
29199
|
-
toggleEndAfterCurrentPromptState();
|
|
29200
|
-
scheduleRender();
|
|
29351
|
+
// Note: [🏹] The very same key handling is shared with the plain console mode, see `applyCoderRunControlKey`
|
|
29352
|
+
const controlFeedback = applyCoderRunControlKey(key.name);
|
|
29353
|
+
if (controlFeedback !== undefined) {
|
|
29354
|
+
showControlFeedback(controlFeedback);
|
|
29201
29355
|
return;
|
|
29202
29356
|
}
|
|
29203
29357
|
if ((key.name === 'return' || key.name === 'enter') && pendingEnterResolver) {
|
|
@@ -29223,6 +29377,7 @@
|
|
|
29223
29377
|
clearTimeout(autoRefreshTimeout);
|
|
29224
29378
|
autoRefreshTimeout = undefined;
|
|
29225
29379
|
}
|
|
29380
|
+
clearControlFeedbackTimeout();
|
|
29226
29381
|
state.off('change', scheduleRender);
|
|
29227
29382
|
process.stdin.off('keypress', keypressHandler);
|
|
29228
29383
|
process.stdout.off('resize', scheduleRender);
|
|
@@ -29283,7 +29438,7 @@
|
|
|
29283
29438
|
waitBetweenPrompts: 0,
|
|
29284
29439
|
waitAfterError: 0,
|
|
29285
29440
|
noCommit: options.noCommit,
|
|
29286
|
-
|
|
29441
|
+
gitChanges: options.gitChanges,
|
|
29287
29442
|
normalizeLineEndings: options.normalizeLineEndings,
|
|
29288
29443
|
allowCredits: options.allowCredits,
|
|
29289
29444
|
autoMigrate: false,
|
|
@@ -39851,11 +40006,11 @@
|
|
|
39851
40006
|
* Validates constraints specific to never-ending agent watch loops.
|
|
39852
40007
|
*/
|
|
39853
40008
|
function validateAgentWatchOptions(commandDisplayName, options) {
|
|
39854
|
-
if (!options.noCommit || options.
|
|
40009
|
+
if (!options.noCommit || options.gitChanges !== 'fail') {
|
|
39855
40010
|
return;
|
|
39856
40011
|
}
|
|
39857
40012
|
throw new NotAllowed(_spaceTrim.spaceTrim(`
|
|
39858
|
-
Flag \`--no-commit\` requires \`--
|
|
40013
|
+
Flag \`--no-commit\` requires \`--git-changes ignore\` when used with \`${commandDisplayName}\`.
|
|
39859
40014
|
|
|
39860
40015
|
Without commits, answered messages remain in the working tree and the next message round would fail the clean
|
|
39861
40016
|
working tree check.
|
|
@@ -40168,7 +40323,7 @@
|
|
|
40168
40323
|
noUi: options.noUi,
|
|
40169
40324
|
thinkingLevel: options.thinkingLevel,
|
|
40170
40325
|
noCommit: true,
|
|
40171
|
-
|
|
40326
|
+
gitChanges: 'ignore',
|
|
40172
40327
|
normalizeLineEndings: false,
|
|
40173
40328
|
allowCredits: options.allowCredits,
|
|
40174
40329
|
autoPush: false,
|
|
@@ -43766,6 +43921,8 @@
|
|
|
43766
43921
|
Features:
|
|
43767
43922
|
- Automatically stages and commits changes with agent identity unless --no-commit is used
|
|
43768
43923
|
- Commits only the prompt file and the files the coding agent has changed, leaving unrelated changes alone
|
|
43924
|
+
- Refuses to start on a dirty working tree unless --git-changes says what should happen with the changes
|
|
43925
|
+
- Optional --git-changes continue resumes the single prompt an interrupted coder left in the [^] status
|
|
43769
43926
|
- Optional post-commit git push with explicit --auto-push opt-in
|
|
43770
43927
|
- Optional pre-prompt git pull with explicit --auto-pull opt-in
|
|
43771
43928
|
- Optional --isolate runs every prompt in its own temporary git worktree and merges it back when verified
|
|
@@ -43846,7 +44003,7 @@
|
|
|
43846
44003
|
waitBetweenPrompts,
|
|
43847
44004
|
waitAfterError,
|
|
43848
44005
|
noCommit: runnerOptions.noCommit,
|
|
43849
|
-
|
|
44006
|
+
gitChanges: runnerOptions.gitChanges,
|
|
43850
44007
|
agentName: runnerOptions.agentName,
|
|
43851
44008
|
model: runnerOptions.model,
|
|
43852
44009
|
agent,
|
|
@@ -43987,7 +44144,7 @@
|
|
|
43987
44144
|
waitBetweenPrompts,
|
|
43988
44145
|
waitAfterError,
|
|
43989
44146
|
noCommit: runnerOptions.noCommit,
|
|
43990
|
-
|
|
44147
|
+
gitChanges: runnerOptions.gitChanges,
|
|
43991
44148
|
agentName: runnerOptions.agentName,
|
|
43992
44149
|
model: runnerOptions.model,
|
|
43993
44150
|
agent,
|
|
@@ -67418,7 +67575,7 @@
|
|
|
67418
67575
|
waitBetweenPrompts: 0,
|
|
67419
67576
|
waitAfterError: 0,
|
|
67420
67577
|
noCommit: true,
|
|
67421
|
-
|
|
67578
|
+
gitChanges: 'ignore',
|
|
67422
67579
|
normalizeLineEndings: false,
|
|
67423
67580
|
allowCredits: options.allowCredits,
|
|
67424
67581
|
isVerbose: options.isVerbose,
|
|
@@ -73211,13 +73368,13 @@
|
|
|
73211
73368
|
}
|
|
73212
73369
|
|
|
73213
73370
|
/**
|
|
73214
|
-
* Lists
|
|
73371
|
+
* Lists prompts in one checklist status across all files.
|
|
73215
73372
|
*/
|
|
73216
|
-
function
|
|
73373
|
+
function listPromptsByStatus(files, status) {
|
|
73217
73374
|
const prompts = [];
|
|
73218
73375
|
for (const file of files) {
|
|
73219
73376
|
for (const section of file.sections) {
|
|
73220
|
-
if (section.status ===
|
|
73377
|
+
if (section.status === status) {
|
|
73221
73378
|
prompts.push({ file, section });
|
|
73222
73379
|
}
|
|
73223
73380
|
}
|
|
@@ -73225,6 +73382,13 @@
|
|
|
73225
73382
|
return prompts;
|
|
73226
73383
|
}
|
|
73227
73384
|
|
|
73385
|
+
/**
|
|
73386
|
+
* Lists todo prompts across all files.
|
|
73387
|
+
*/
|
|
73388
|
+
function listTodoPrompts(files) {
|
|
73389
|
+
return listPromptsByStatus(files, 'todo');
|
|
73390
|
+
}
|
|
73391
|
+
|
|
73228
73392
|
/**
|
|
73229
73393
|
* Lists todo prompts that still contain authoring placeholders.
|
|
73230
73394
|
*/
|
|
@@ -73802,7 +73966,7 @@
|
|
|
73802
73966
|
/**
|
|
73803
73967
|
* CLI usage text for this script.
|
|
73804
73968
|
*/
|
|
73805
|
-
const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--test-before <no|yes-and-fail|yes-and-fix>] [--preserve-logs] [--isolate] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--min-priority <minimum-priority>] [--max-priority <maximum-priority>] [--limit <run-count>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--wait-after-prompt <duration>] [--wait-between-prompts <duration>] [--wait-after-error <duration>] [--no-auto] [--no-commit] [--
|
|
73969
|
+
const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--test-before <no|yes-and-fail|yes-and-fix>] [--preserve-logs] [--isolate] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--min-priority <minimum-priority>] [--max-priority <maximum-priority>] [--limit <run-count>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--wait-after-prompt <duration>] [--wait-between-prompts <duration>] [--wait-after-error <duration>] [--no-auto] [--no-commit] [--git-changes <fail|ignore|continue>] [--no-normalize-line-endings] [--auto-push] [--auto-pull]';
|
|
73806
73970
|
/**
|
|
73807
73971
|
* Top-level flags supported by this command.
|
|
73808
73972
|
*/
|
|
@@ -73829,7 +73993,7 @@
|
|
|
73829
73993
|
'--wait-after-error',
|
|
73830
73994
|
'--no-auto',
|
|
73831
73995
|
'--no-commit',
|
|
73832
|
-
'--
|
|
73996
|
+
'--git-changes',
|
|
73833
73997
|
'--no-normalize-line-endings',
|
|
73834
73998
|
'--auto-push',
|
|
73835
73999
|
'--auto-pull',
|
|
@@ -73873,7 +74037,8 @@
|
|
|
73873
74037
|
const hasLimitFlag = args.includes('--limit');
|
|
73874
74038
|
const limit = parseLimit(readOptionValue(args, '--limit'), hasLimitFlag);
|
|
73875
74039
|
const noCommit = args.includes('--no-commit');
|
|
73876
|
-
const
|
|
74040
|
+
const hasGitChangesFlag = args.includes('--git-changes');
|
|
74041
|
+
const gitChanges = parseGitChangesOption(readOptionValue(args, '--git-changes'), hasGitChangesFlag);
|
|
73877
74042
|
const normalizeLineEndings = !args.includes('--no-normalize-line-endings');
|
|
73878
74043
|
const allowCredits = args.includes('--allow-credits');
|
|
73879
74044
|
const autoMigrate = args.includes('--auto-migrate');
|
|
@@ -73911,7 +74076,7 @@
|
|
|
73911
74076
|
waitBetweenPrompts,
|
|
73912
74077
|
waitAfterError,
|
|
73913
74078
|
noCommit,
|
|
73914
|
-
|
|
74079
|
+
gitChanges,
|
|
73915
74080
|
normalizeLineEndings,
|
|
73916
74081
|
allowCredits,
|
|
73917
74082
|
autoMigrate,
|
|
@@ -73949,6 +74114,21 @@
|
|
|
73949
74114
|
}
|
|
73950
74115
|
return value;
|
|
73951
74116
|
}
|
|
74117
|
+
/**
|
|
74118
|
+
* Parses and validates the requested behavior for a dirty working tree.
|
|
74119
|
+
*/
|
|
74120
|
+
function parseGitChangesOption(value, hasGitChangesFlag) {
|
|
74121
|
+
if (value === undefined) {
|
|
74122
|
+
if (hasGitChangesFlag) {
|
|
74123
|
+
exitWithUsageError(`Missing value for --git-changes. Use one of: ${GIT_CHANGES_MODE_VALUES.join(', ')}.`);
|
|
74124
|
+
}
|
|
74125
|
+
return DEFAULT_GIT_CHANGES_MODE;
|
|
74126
|
+
}
|
|
74127
|
+
if (!isGitChangesMode(value)) {
|
|
74128
|
+
exitWithUsageError(`Invalid value for --git-changes: "${value}". Use one of: ${GIT_CHANGES_MODE_VALUES.join(', ')}.`);
|
|
74129
|
+
}
|
|
74130
|
+
return value;
|
|
74131
|
+
}
|
|
73952
74132
|
/**
|
|
73953
74133
|
* Reads a duration-typed CLI flag, applying the provided default when the flag is absent.
|
|
73954
74134
|
*/
|
|
@@ -74406,27 +74586,19 @@
|
|
|
74406
74586
|
return;
|
|
74407
74587
|
}
|
|
74408
74588
|
const deadlineTimeMs = (_a = options.deadlineTimeMs) !== null && _a !== void 0 ? _a : Date.now() + durationMs;
|
|
74409
|
-
|
|
74410
|
-
|
|
74411
|
-
|
|
74412
|
-
|
|
74413
|
-
|
|
74414
|
-
|
|
74415
|
-
|
|
74416
|
-
|
|
74417
|
-
|
|
74418
|
-
|
|
74419
|
-
|
|
74420
|
-
|
|
74421
|
-
|
|
74422
|
-
}
|
|
74423
|
-
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`${statusMessage}...`);
|
|
74424
|
-
},
|
|
74425
|
-
});
|
|
74426
|
-
}
|
|
74427
|
-
finally {
|
|
74428
|
-
finishSkippableWait(waitToken);
|
|
74429
|
-
}
|
|
74589
|
+
await waitForSkippableWorldTimeDeadline({
|
|
74590
|
+
deadlineTimeMs,
|
|
74591
|
+
pollIntervalMs: WAIT_COUNTDOWN_UPDATE_INTERVAL_MS,
|
|
74592
|
+
onTick: (remainingDurationMs) => {
|
|
74593
|
+
const visibleRemainingDurationMs = Math.min(remainingDurationMs, durationMs);
|
|
74594
|
+
const statusMessage = describeCoderRunWait(waitKind, visibleRemainingDurationMs, durationMs);
|
|
74595
|
+
if (!isRichUiEnabled) {
|
|
74596
|
+
console.info(colors__default["default"].gray(`${statusMessage}...`));
|
|
74597
|
+
return;
|
|
74598
|
+
}
|
|
74599
|
+
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`${statusMessage}...`);
|
|
74600
|
+
},
|
|
74601
|
+
});
|
|
74430
74602
|
}
|
|
74431
74603
|
|
|
74432
74604
|
/**
|
|
@@ -74441,6 +74613,36 @@
|
|
|
74441
74613
|
});
|
|
74442
74614
|
}
|
|
74443
74615
|
|
|
74616
|
+
/**
|
|
74617
|
+
* Listens for the terminal control keys while the plain console output is used.
|
|
74618
|
+
*
|
|
74619
|
+
* Every recognized key press is answered with one printed line, because the plain mode has no frame
|
|
74620
|
+
* which could show the new state on its own. The rich terminal UI installs its own listener in
|
|
74621
|
+
* [`renderCoderRunUi`](../ui/renderCoderRunUi.ts) and shares the very same key handling through
|
|
74622
|
+
* [`applyCoderRunControlKey`](./applyCoderRunControlKey.ts).
|
|
74623
|
+
*
|
|
74624
|
+
* @private internal utility of `ptbk coder` terminal controls
|
|
74625
|
+
*/
|
|
74626
|
+
function listenForCoderRunControls() {
|
|
74627
|
+
if (!process.stdin.isTTY) {
|
|
74628
|
+
return;
|
|
74629
|
+
}
|
|
74630
|
+
readline__namespace.emitKeypressEvents(process.stdin);
|
|
74631
|
+
process.stdin.setRawMode(true);
|
|
74632
|
+
process.stdin.on('keypress', (_pressedText, key) => {
|
|
74633
|
+
if (key.ctrl && key.name === 'c') {
|
|
74634
|
+
process.exit();
|
|
74635
|
+
}
|
|
74636
|
+
const controlFeedback = applyCoderRunControlKey(key.name);
|
|
74637
|
+
if (controlFeedback === undefined) {
|
|
74638
|
+
return;
|
|
74639
|
+
}
|
|
74640
|
+
// Note: Using console.log here which adds a new line.
|
|
74641
|
+
// This is intentional to prevent the message from being overwritten.
|
|
74642
|
+
console.log(formatCoderRunControlFeedback(controlFeedback));
|
|
74643
|
+
});
|
|
74644
|
+
}
|
|
74645
|
+
|
|
74444
74646
|
/**
|
|
74445
74647
|
* Checks working tree clean.
|
|
74446
74648
|
*/
|
|
@@ -74459,12 +74661,14 @@
|
|
|
74459
74661
|
async function ensureWorkingTreeClean() {
|
|
74460
74662
|
const isClean = await isWorkingTreeClean(process.cwd());
|
|
74461
74663
|
if (!isClean) {
|
|
74462
|
-
throw new
|
|
74664
|
+
throw new NotAllowed(_spaceTrim.spaceTrim(`
|
|
74463
74665
|
Git working tree is not clean.
|
|
74464
74666
|
|
|
74465
74667
|
Please commit or stash your changes before running this script
|
|
74466
|
-
OR
|
|
74467
|
-
|
|
74668
|
+
OR decide what should happen with them:
|
|
74669
|
+
|
|
74670
|
+
- \`--git-changes ignore\` leaves the changes where they are and starts the next prompt anyway
|
|
74671
|
+
- \`--git-changes continue\` resumes the single prompt which was left in the middle of its implementation
|
|
74468
74672
|
|
|
74469
74673
|
Aborting
|
|
74470
74674
|
`));
|
|
@@ -76031,6 +76235,51 @@
|
|
|
76031
76235
|
return `(failed after ${attemptCount} attempts) `;
|
|
76032
76236
|
}
|
|
76033
76237
|
|
|
76238
|
+
/**
|
|
76239
|
+
* Separator which introduces the harness that started a prompt another harness has taken over.
|
|
76240
|
+
*
|
|
76241
|
+
* It is used both for writing and for reading the attribution, so the status line of a continued prompt
|
|
76242
|
+
* stays parseable when yet another harness continues it later.
|
|
76243
|
+
*/
|
|
76244
|
+
const STARTED_BY_SEPARATOR = ', started by ';
|
|
76245
|
+
/**
|
|
76246
|
+
* Matches the attribution of one status line, which reaches from `by ` up to the step summary.
|
|
76247
|
+
*/
|
|
76248
|
+
const PROMPT_RUNNER_ATTRIBUTION_PATTERN = /(?:^|\s)by (?<attribution>.*?)(?: - |$)/u;
|
|
76249
|
+
/**
|
|
76250
|
+
* Formats who worked on one prompt for its status line.
|
|
76251
|
+
*
|
|
76252
|
+
* Produces `` by Claude Code `claude-opus-5` `` for a prompt implemented by a single harness and
|
|
76253
|
+
* `` by Claude Code `claude-opus-5`, started by OpenAI Codex `gpt-5.6-luna` `` for a prompt which one
|
|
76254
|
+
* harness started and another one continued through `--git-changes continue`.
|
|
76255
|
+
*/
|
|
76256
|
+
function formatPromptRunnerAttribution(options) {
|
|
76257
|
+
const { currentRunnerSignature, startedByRunnerSignature } = options;
|
|
76258
|
+
if (startedByRunnerSignature === undefined || startedByRunnerSignature === currentRunnerSignature) {
|
|
76259
|
+
return `by ${currentRunnerSignature}`;
|
|
76260
|
+
}
|
|
76261
|
+
return `by ${currentRunnerSignature}${STARTED_BY_SEPARATOR}${startedByRunnerSignature}`;
|
|
76262
|
+
}
|
|
76263
|
+
/**
|
|
76264
|
+
* Reads which harness started the work recorded on one prompt status line.
|
|
76265
|
+
*
|
|
76266
|
+
* A line written by a single harness names that harness, and a line of a prompt which was already taken
|
|
76267
|
+
* over keeps naming the harness which started it, so the original author survives any number of takeovers.
|
|
76268
|
+
*/
|
|
76269
|
+
function parsePromptStartedByRunnerSignature(statusLine) {
|
|
76270
|
+
var _a, _b, _c;
|
|
76271
|
+
const attribution = (_c = (_b = (_a = statusLine.match(PROMPT_RUNNER_ATTRIBUTION_PATTERN)) === null || _a === void 0 ? void 0 : _a.groups) === null || _b === void 0 ? void 0 : _b.attribution) === null || _c === void 0 ? void 0 : _c.trim();
|
|
76272
|
+
if (!attribution) {
|
|
76273
|
+
return undefined;
|
|
76274
|
+
}
|
|
76275
|
+
const startedBySeparatorIndex = attribution.indexOf(STARTED_BY_SEPARATOR);
|
|
76276
|
+
if (startedBySeparatorIndex === -1) {
|
|
76277
|
+
return attribution;
|
|
76278
|
+
}
|
|
76279
|
+
return attribution.slice(startedBySeparatorIndex + STARTED_BY_SEPARATOR.length).trim() || undefined;
|
|
76280
|
+
}
|
|
76281
|
+
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
76282
|
+
|
|
76034
76283
|
/**
|
|
76035
76284
|
* Builds the shared body of a prompt status line, used by both the in-progress `[^]` and the done `[x]` status.
|
|
76036
76285
|
*
|
|
@@ -76038,14 +76287,18 @@
|
|
|
76038
76287
|
* ``by OpenAI Codex `gpt-5.6-luna` thinking `max` (ChatGPT account) - Implementation ~$0.2036 10 minutes``.
|
|
76039
76288
|
*/
|
|
76040
76289
|
function buildPromptStatusDetails(options) {
|
|
76041
|
-
const { steps, inProgressStepKind, runnerName, modelName, attemptCount, loginMethod, thinkingLevel } = options;
|
|
76290
|
+
const { steps, inProgressStepKind, runnerName, modelName, startedByRunnerSignature, attemptCount, loginMethod, thinkingLevel, } = options;
|
|
76042
76291
|
const runnerSignature = formatRunnerSignature(runnerName, modelName, thinkingLevel);
|
|
76043
76292
|
const attemptMetadata = formatPromptAttemptMetadata('done', attemptCount);
|
|
76044
76293
|
const loginMethodLabel = formatCodexLoginMethod(loginMethod);
|
|
76045
76294
|
const loginMethodSuffix = loginMethodLabel ? ` (${loginMethodLabel})` : '';
|
|
76295
|
+
const attribution = formatPromptRunnerAttribution({
|
|
76296
|
+
currentRunnerSignature: `${runnerSignature}${loginMethodSuffix}`,
|
|
76297
|
+
startedByRunnerSignature,
|
|
76298
|
+
});
|
|
76046
76299
|
const stepsSummary = formatCoderRunSteps(steps, inProgressStepKind);
|
|
76047
76300
|
const stepsSuffix = stepsSummary === '' ? '' : ` - ${stepsSummary}`;
|
|
76048
|
-
return `${attemptMetadata}
|
|
76301
|
+
return `${attemptMetadata}${attribution}${stepsSuffix}`;
|
|
76049
76302
|
}
|
|
76050
76303
|
|
|
76051
76304
|
/**
|
|
@@ -76088,29 +76341,26 @@
|
|
|
76088
76341
|
/**
|
|
76089
76342
|
* Marks a prompt section as done and records the per-step usage pricing and runner details.
|
|
76090
76343
|
*/
|
|
76091
|
-
function markPromptDone(
|
|
76092
|
-
const
|
|
76093
|
-
|
|
76094
|
-
runnerName,
|
|
76095
|
-
modelName,
|
|
76096
|
-
attemptCount,
|
|
76097
|
-
loginMethod,
|
|
76098
|
-
thinkingLevel,
|
|
76099
|
-
});
|
|
76100
|
-
writePromptStatusLine(file, section, `[x] ${statusDetails}`);
|
|
76344
|
+
function markPromptDone(options) {
|
|
76345
|
+
const { file, section, ...statusDetailsOptions } = options;
|
|
76346
|
+
writePromptStatusLine(file, section, `[x] ${buildPromptStatusDetails(statusDetailsOptions)}`);
|
|
76101
76347
|
}
|
|
76102
76348
|
|
|
76103
76349
|
/**
|
|
76104
76350
|
* Marks a prompt section as failed and records runner details.
|
|
76105
76351
|
*/
|
|
76106
|
-
function markPromptFailed(
|
|
76107
|
-
const
|
|
76352
|
+
function markPromptFailed(options) {
|
|
76353
|
+
const { file, section, runnerName, modelName, startedByRunnerSignature, promptExecutionStartedDate, attemptCount } = options;
|
|
76354
|
+
const attribution = formatPromptRunnerAttribution({
|
|
76355
|
+
currentRunnerSignature: formatRunnerSignature(runnerName, modelName),
|
|
76356
|
+
startedByRunnerSignature,
|
|
76357
|
+
});
|
|
76108
76358
|
const attemptMetadata = formatPromptAttemptMetadata('failed', attemptCount);
|
|
76109
76359
|
const duration = moment__default["default"]().diff(promptExecutionStartedDate);
|
|
76110
76360
|
const durationString = moment__default["default"].duration(duration).humanize();
|
|
76111
76361
|
const failureDetails = attemptMetadata === ''
|
|
76112
|
-
? `failed after ${durationString}
|
|
76113
|
-
: `${attemptMetadata}${durationString}
|
|
76362
|
+
? `failed after ${durationString} ${attribution}`
|
|
76363
|
+
: `${attemptMetadata}${durationString} ${attribution}`;
|
|
76114
76364
|
writePromptStatusLine(file, section, `[!] ${failureDetails}`);
|
|
76115
76365
|
}
|
|
76116
76366
|
|
|
@@ -76202,6 +76452,12 @@
|
|
|
76202
76452
|
const codexPrompt = appendCoderContext(promptWithAgent, resolvedCoderContext);
|
|
76203
76453
|
// Note: Temporary scripts and runtime logs stay in the original project so they outlive an isolated worktree
|
|
76204
76454
|
const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
|
|
76455
|
+
// Note: Read before the first `[^]` rewrite of this round, which would overwrite the harness recorded so far
|
|
76456
|
+
const startedByRunnerSignature = resolveStartedByRunnerSignature({
|
|
76457
|
+
nextPrompt,
|
|
76458
|
+
runnerMetadata,
|
|
76459
|
+
thinkingLevel: options.thinkingLevel,
|
|
76460
|
+
});
|
|
76205
76461
|
setPromptRoundRunningState({ isRichUiEnabled, promptLabel, scriptPath, uiHandle });
|
|
76206
76462
|
await waitForRequestedPause({
|
|
76207
76463
|
checkpointLabel: 'preparing the current prompt execution',
|
|
@@ -76234,6 +76490,7 @@
|
|
|
76234
76490
|
onStepStarted: (progress) => recordPromptRoundInProgress({
|
|
76235
76491
|
nextPrompt,
|
|
76236
76492
|
runnerMetadata,
|
|
76493
|
+
startedByRunnerSignature,
|
|
76237
76494
|
thinkingLevel: options.thinkingLevel,
|
|
76238
76495
|
attemptCount,
|
|
76239
76496
|
progress,
|
|
@@ -76244,6 +76501,7 @@
|
|
|
76244
76501
|
options,
|
|
76245
76502
|
nextPrompt,
|
|
76246
76503
|
runnerMetadata,
|
|
76504
|
+
startedByRunnerSignature,
|
|
76247
76505
|
promptExecutionStartedDate,
|
|
76248
76506
|
result,
|
|
76249
76507
|
commitMessage,
|
|
@@ -76277,6 +76535,7 @@
|
|
|
76277
76535
|
await finalizeFailedPromptRound({
|
|
76278
76536
|
nextPrompt,
|
|
76279
76537
|
runnerMetadata,
|
|
76538
|
+
startedByRunnerSignature,
|
|
76280
76539
|
promptExecutionStartedDate,
|
|
76281
76540
|
attemptCount,
|
|
76282
76541
|
error: lastError,
|
|
@@ -76289,6 +76548,23 @@
|
|
|
76289
76548
|
throw lastError;
|
|
76290
76549
|
}, { preserveArtifactsOnSuccess: options.preserveLogs });
|
|
76291
76550
|
}
|
|
76551
|
+
/**
|
|
76552
|
+
* Reads which harness left the prompt of this round in the middle of its implementation.
|
|
76553
|
+
*
|
|
76554
|
+
* Only a prompt resumed through `--git-changes continue` still carries the in-progress `[^]` status when its
|
|
76555
|
+
* round starts, so every other round reports no earlier harness. A harness which continues its own work is
|
|
76556
|
+
* reported as no earlier harness either, because the status line would just name it twice.
|
|
76557
|
+
*/
|
|
76558
|
+
function resolveStartedByRunnerSignature(options) {
|
|
76559
|
+
const { nextPrompt, runnerMetadata, thinkingLevel } = options;
|
|
76560
|
+
if (nextPrompt.section.status !== 'in-progress') {
|
|
76561
|
+
return undefined;
|
|
76562
|
+
}
|
|
76563
|
+
const { line } = resolvePromptStatusLine(nextPrompt.file, nextPrompt.section);
|
|
76564
|
+
const startedByRunnerSignature = parsePromptStartedByRunnerSignature(line);
|
|
76565
|
+
const currentRunnerSignature = formatRunnerSignature(runnerMetadata.runnerName, runnerMetadata.modelName, thinkingLevel);
|
|
76566
|
+
return startedByRunnerSignature === currentRunnerSignature ? undefined : startedByRunnerSignature;
|
|
76567
|
+
}
|
|
76292
76568
|
/**
|
|
76293
76569
|
* Records into the prompt file that the prompt is being implemented right now.
|
|
76294
76570
|
*
|
|
@@ -76297,7 +76573,7 @@
|
|
|
76297
76573
|
* crashes leaves the `[^]` status behind as the signal that this task was left in the middle.
|
|
76298
76574
|
*/
|
|
76299
76575
|
async function recordPromptRoundInProgress(options) {
|
|
76300
|
-
const { nextPrompt, runnerMetadata, thinkingLevel, attemptCount, progress } = options;
|
|
76576
|
+
const { nextPrompt, runnerMetadata, startedByRunnerSignature, thinkingLevel, attemptCount, progress } = options;
|
|
76301
76577
|
markPromptInProgress({
|
|
76302
76578
|
file: nextPrompt.file,
|
|
76303
76579
|
section: nextPrompt.section,
|
|
@@ -76305,6 +76581,7 @@
|
|
|
76305
76581
|
inProgressStepKind: progress.startedStepKind,
|
|
76306
76582
|
runnerName: runnerMetadata.runnerName,
|
|
76307
76583
|
modelName: runnerMetadata.modelName,
|
|
76584
|
+
startedByRunnerSignature,
|
|
76308
76585
|
attemptCount,
|
|
76309
76586
|
loginMethod: progress.loginMethod,
|
|
76310
76587
|
thinkingLevel,
|
|
@@ -76362,14 +76639,24 @@
|
|
|
76362
76639
|
* Finalizes a successful prompt round, including prompt bookkeeping and commit flow.
|
|
76363
76640
|
*/
|
|
76364
76641
|
async function finalizeSuccessfulPromptRound(options) {
|
|
76365
|
-
const { options: runOptions, nextPrompt, runnerMetadata, promptExecutionStartedDate, result, commitMessage, logPath, roundCommitScope, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76642
|
+
const { options: runOptions, nextPrompt, runnerMetadata, startedByRunnerSignature, promptExecutionStartedDate, result, commitMessage, logPath, roundCommitScope, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76366
76643
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
|
|
76367
76644
|
await waitForRequestedPause({
|
|
76368
76645
|
checkpointLabel: 'recording the successful prompt result',
|
|
76369
76646
|
phase: 'running',
|
|
76370
76647
|
statusMessage: 'Recording prompt result',
|
|
76371
76648
|
});
|
|
76372
|
-
markPromptDone(
|
|
76649
|
+
markPromptDone({
|
|
76650
|
+
file: nextPrompt.file,
|
|
76651
|
+
section: nextPrompt.section,
|
|
76652
|
+
steps: result.steps,
|
|
76653
|
+
runnerName: runnerMetadata.runnerName,
|
|
76654
|
+
modelName: runnerMetadata.modelName,
|
|
76655
|
+
startedByRunnerSignature,
|
|
76656
|
+
attemptCount: result.attemptCount,
|
|
76657
|
+
loginMethod: result.loginMethod,
|
|
76658
|
+
thinkingLevel: runOptions.thinkingLevel,
|
|
76659
|
+
});
|
|
76373
76660
|
// Note: The prompt status is always written into the original project, an isolated round transports
|
|
76374
76661
|
// its own changes back through the merge instead
|
|
76375
76662
|
await writePromptFile(nextPrompt.file);
|
|
@@ -76420,7 +76707,7 @@
|
|
|
76420
76707
|
* Finalizes a failed prompt round, persisting prompt failure metadata before rethrowing.
|
|
76421
76708
|
*/
|
|
76422
76709
|
async function finalizeFailedPromptRound(options) {
|
|
76423
|
-
const { nextPrompt, runnerMetadata, promptExecutionStartedDate, attemptCount, error, options: runOptions, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76710
|
+
const { nextPrompt, runnerMetadata, startedByRunnerSignature, promptExecutionStartedDate, attemptCount, error, options: runOptions, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76424
76711
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
|
|
76425
76712
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
|
|
76426
76713
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
|
|
@@ -76429,7 +76716,15 @@
|
|
|
76429
76716
|
phase: 'error',
|
|
76430
76717
|
statusMessage: 'Recording prompt failure',
|
|
76431
76718
|
});
|
|
76432
|
-
markPromptFailed(
|
|
76719
|
+
markPromptFailed({
|
|
76720
|
+
file: nextPrompt.file,
|
|
76721
|
+
section: nextPrompt.section,
|
|
76722
|
+
runnerName: runnerMetadata.runnerName,
|
|
76723
|
+
modelName: runnerMetadata.modelName,
|
|
76724
|
+
startedByRunnerSignature,
|
|
76725
|
+
promptExecutionStartedDate,
|
|
76726
|
+
attemptCount,
|
|
76727
|
+
});
|
|
76433
76728
|
await writePromptFile(nextPrompt.file);
|
|
76434
76729
|
await writePromptErrorLog({
|
|
76435
76730
|
file: nextPrompt.file,
|
|
@@ -77182,6 +77477,50 @@
|
|
|
77182
77477
|
}
|
|
77183
77478
|
}
|
|
77184
77479
|
|
|
77480
|
+
/**
|
|
77481
|
+
* Builds a label that identifies the prompt section by index.
|
|
77482
|
+
*/
|
|
77483
|
+
function buildPromptLabel(file, section) {
|
|
77484
|
+
return `${path.relative(process.cwd(), file.path).replace(/\\/g, '/')}#${section.index + 1}`;
|
|
77485
|
+
}
|
|
77486
|
+
|
|
77487
|
+
/**
|
|
77488
|
+
* Resolves the one prompt which `--git-changes continue` resumes.
|
|
77489
|
+
*
|
|
77490
|
+
* A coder which was killed or crashed leaves exactly one prompt behind in the in-progress `[^]` status,
|
|
77491
|
+
* so anything else means the working tree changes cannot be attributed to a single interrupted prompt.
|
|
77492
|
+
*/
|
|
77493
|
+
function resolveInterruptedPrompt(files) {
|
|
77494
|
+
const interruptedPrompts = listPromptsByStatus(files, 'in-progress');
|
|
77495
|
+
if (interruptedPrompts.length === 0) {
|
|
77496
|
+
throw new NotFoundError(_spaceTrim.spaceTrim(`
|
|
77497
|
+
Flag \`--git-changes continue\` found no interrupted prompt to continue.
|
|
77498
|
+
|
|
77499
|
+
Continuing resumes the prompt an earlier coder left in the middle of its implementation, which is the
|
|
77500
|
+
one marked with the in-progress \`[^]\` status. No prompt carries that status right now.
|
|
77501
|
+
|
|
77502
|
+
Use \`--git-changes ignore\` to start the next \`[ ]\` prompt with the current working tree changes
|
|
77503
|
+
left in place, or \`--git-changes fail\` to commit or stash them first.
|
|
77504
|
+
`));
|
|
77505
|
+
}
|
|
77506
|
+
if (interruptedPrompts.length > 1) {
|
|
77507
|
+
throw new NotAllowed(_spaceTrim.spaceTrim((block) => `
|
|
77508
|
+
Flag \`--git-changes continue\` found ${interruptedPrompts.length} interrupted prompts, but it can
|
|
77509
|
+
continue only one.
|
|
77510
|
+
|
|
77511
|
+
The working tree changes cannot be attributed to a single prompt while more than one of them carries
|
|
77512
|
+
the in-progress \`[^]\` status. Resolve the extra ones by marking them \`[x]\`, \`[!]\` or \`[ ]\`
|
|
77513
|
+
and run the coder again.
|
|
77514
|
+
|
|
77515
|
+
Interrupted prompts:
|
|
77516
|
+
${block(interruptedPrompts
|
|
77517
|
+
.map(({ file, section }) => `- \`${buildPromptLabel(file, section)}\``)
|
|
77518
|
+
.join('\n'))}
|
|
77519
|
+
`));
|
|
77520
|
+
}
|
|
77521
|
+
return interruptedPrompts[0];
|
|
77522
|
+
}
|
|
77523
|
+
|
|
77185
77524
|
/**
|
|
77186
77525
|
* Prints a summary of the prompt that will run next.
|
|
77187
77526
|
*/
|
|
@@ -77248,7 +77587,7 @@
|
|
|
77248
77587
|
* visuals are rendered through the shared canvas avatar pipeline into terminal ASCII art, using a
|
|
77249
77588
|
* transparent horizontal canvas instead of the website's framed 1:1 surface.
|
|
77250
77589
|
*
|
|
77251
|
-
* The visual is decorative, so any failure returns `null` and the caller
|
|
77590
|
+
* The visual is decorative, so any failure returns `null` and the caller renders no header visual at all.
|
|
77252
77591
|
*
|
|
77253
77592
|
* @param agentSource Source of the `--agent` book file.
|
|
77254
77593
|
* @returns ANSI-colored ASCII-art renderer or `null` when the visual cannot be rendered.
|
|
@@ -77410,6 +77749,8 @@
|
|
|
77410
77749
|
let previousRoundEndTime;
|
|
77411
77750
|
let completedRunCount = 0;
|
|
77412
77751
|
let hasRunTestBefore = false;
|
|
77752
|
+
// Note: Only the very first round resumes the interrupted prompt, every later round starts from a clean tree
|
|
77753
|
+
let isContinuingInterruptedPrompt = options.gitChanges === 'continue';
|
|
77413
77754
|
while (just(true)) {
|
|
77414
77755
|
if (options.autoPull && !options.dryRun) {
|
|
77415
77756
|
await waitForRequestedPause({
|
|
@@ -77434,6 +77775,7 @@
|
|
|
77434
77775
|
progressDisplay,
|
|
77435
77776
|
uiHandle,
|
|
77436
77777
|
promptRunnerIdentity,
|
|
77778
|
+
isContinuingInterruptedPrompt,
|
|
77437
77779
|
});
|
|
77438
77780
|
}
|
|
77439
77781
|
if (!hasRunTestBefore) {
|
|
@@ -77448,6 +77790,7 @@
|
|
|
77448
77790
|
uiHandle,
|
|
77449
77791
|
waitForRequestedPause,
|
|
77450
77792
|
hasWaitedForStart,
|
|
77793
|
+
isContinuingInterruptedPrompt,
|
|
77451
77794
|
});
|
|
77452
77795
|
hasRunTestBefore = true;
|
|
77453
77796
|
}
|
|
@@ -77462,6 +77805,7 @@
|
|
|
77462
77805
|
progressDisplay,
|
|
77463
77806
|
uiHandle,
|
|
77464
77807
|
promptRunnerIdentity,
|
|
77808
|
+
isContinuingInterruptedPrompt,
|
|
77465
77809
|
});
|
|
77466
77810
|
hasShownUpcomingTasks || (hasShownUpcomingTasks = showUpcomingTasksOnce({
|
|
77467
77811
|
hasShownUpcomingTasks,
|
|
@@ -77482,7 +77826,12 @@
|
|
|
77482
77826
|
}
|
|
77483
77827
|
if (options.keepAlive) {
|
|
77484
77828
|
announceKeepAliveStatus(promptQueueSnapshot, isRichUiEnabled, uiHandle);
|
|
77485
|
-
|
|
77829
|
+
// Note: The keep-alive poll runs in the `waiting` phase, where `S Skip current waiting`
|
|
77830
|
+
// is offered, so pressing `S` looks for new prompts right away
|
|
77831
|
+
await waitForSkippableWorldTimeDeadline({
|
|
77832
|
+
deadlineTimeMs: Date.now() + KEEP_ALIVE_POLL_INTERVAL_MS,
|
|
77833
|
+
pollIntervalMs: KEEP_ALIVE_POLL_INTERVAL_MS,
|
|
77834
|
+
});
|
|
77486
77835
|
continue;
|
|
77487
77836
|
}
|
|
77488
77837
|
finishWhenNoPromptIsAvailable(promptQueueSnapshot, isRichUiEnabled, uiHandle);
|
|
@@ -77518,7 +77867,7 @@
|
|
|
77518
77867
|
progressDisplay,
|
|
77519
77868
|
uiHandle,
|
|
77520
77869
|
});
|
|
77521
|
-
if (
|
|
77870
|
+
if (isCleanWorkingTreeRequired(options.gitChanges, isContinuingInterruptedPrompt)) {
|
|
77522
77871
|
await waitForRequestedPause({
|
|
77523
77872
|
checkpointLabel: 'checking the git working tree',
|
|
77524
77873
|
phase: 'loading',
|
|
@@ -77542,6 +77891,7 @@
|
|
|
77542
77891
|
uiHandle,
|
|
77543
77892
|
waitForRequestedPause,
|
|
77544
77893
|
});
|
|
77894
|
+
isContinuingInterruptedPrompt = false;
|
|
77545
77895
|
previousRoundStartTime = currentRoundStartTime;
|
|
77546
77896
|
previousRoundEndTime = Date.now();
|
|
77547
77897
|
completedRunCount += 1;
|
|
@@ -77585,9 +77935,9 @@
|
|
|
77585
77935
|
Flag \`--allow-destructive-auto-migrate\` requires \`--auto-migrate\`.
|
|
77586
77936
|
`));
|
|
77587
77937
|
}
|
|
77588
|
-
if (options.noCommit && !options.waitForUser &&
|
|
77938
|
+
if (options.noCommit && !options.waitForUser && options.gitChanges !== 'ignore') {
|
|
77589
77939
|
throw new NotAllowed(_spaceTrim.spaceTrim(`
|
|
77590
|
-
Flag \`--no-commit\` requires \`--
|
|
77940
|
+
Flag \`--no-commit\` requires \`--git-changes ignore\` when running in auto mode (the default; pass \`--no-auto\` for interactive confirmation).
|
|
77591
77941
|
|
|
77592
77942
|
Without commits, the next prompt round would fail the clean working tree check.
|
|
77593
77943
|
`));
|
|
@@ -77606,6 +77956,13 @@
|
|
|
77606
77956
|
An isolated task is implemented in a temporary worktree and reaches the original branch only through a merge, which requires the round to end with a commit.
|
|
77607
77957
|
`));
|
|
77608
77958
|
}
|
|
77959
|
+
if (options.isIsolated && options.gitChanges === 'continue') {
|
|
77960
|
+
throw new NotAllowed(_spaceTrim.spaceTrim(`
|
|
77961
|
+
Flag \`--isolate\` cannot be combined with \`--git-changes continue\`.
|
|
77962
|
+
|
|
77963
|
+
An isolated task is implemented in a fresh temporary worktree checked out from the last commit, so the uncommitted changes of the interrupted prompt would be left behind instead of being continued.
|
|
77964
|
+
`));
|
|
77965
|
+
}
|
|
77609
77966
|
if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit <= 0)) {
|
|
77610
77967
|
throw new NotAllowed(_spaceTrim.spaceTrim(`
|
|
77611
77968
|
Flag \`--limit\` expects a positive integer.
|
|
@@ -77614,6 +77971,18 @@
|
|
|
77614
77971
|
`));
|
|
77615
77972
|
}
|
|
77616
77973
|
}
|
|
77974
|
+
/**
|
|
77975
|
+
* Decides whether the working tree has to be verified clean before the next prompt starts.
|
|
77976
|
+
*
|
|
77977
|
+
* `--git-changes continue` waives the check for the single round which resumes the interrupted prompt,
|
|
77978
|
+
* because that round is started exactly for the uncommitted changes the interrupted prompt left behind.
|
|
77979
|
+
*/
|
|
77980
|
+
function isCleanWorkingTreeRequired(gitChanges, isContinuingInterruptedPrompt) {
|
|
77981
|
+
if (gitChanges === 'ignore') {
|
|
77982
|
+
return false;
|
|
77983
|
+
}
|
|
77984
|
+
return !isContinuingInterruptedPrompt;
|
|
77985
|
+
}
|
|
77617
77986
|
/**
|
|
77618
77987
|
* Pulls the latest repository state before loading prompts when the feature is enabled.
|
|
77619
77988
|
*/
|
|
@@ -77672,7 +78041,7 @@
|
|
|
77672
78041
|
* Runs the optional pre-coding verification and, when requested, its one repair prompt.
|
|
77673
78042
|
*/
|
|
77674
78043
|
async function runTestBeforeIfNeeded(options) {
|
|
77675
|
-
const { options: runOptions, runner, runnerMetadata, resolvedCoderContext, resolvedAgentSystemMessage, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, hasWaitedForStart, } = options;
|
|
78044
|
+
const { options: runOptions, runner, runnerMetadata, resolvedCoderContext, resolvedAgentSystemMessage, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, hasWaitedForStart, isContinuingInterruptedPrompt, } = options;
|
|
77676
78045
|
if (runOptions.testBefore === 'no') {
|
|
77677
78046
|
return hasWaitedForStart;
|
|
77678
78047
|
}
|
|
@@ -77683,7 +78052,7 @@
|
|
|
77683
78052
|
Pass one with ${'`--test <test-command>`'} or use the default ${'`npm test`'} command by providing the mode through the CLI.
|
|
77684
78053
|
`));
|
|
77685
78054
|
}
|
|
77686
|
-
if (
|
|
78055
|
+
if (isCleanWorkingTreeRequired(runOptions.gitChanges, isContinuingInterruptedPrompt)) {
|
|
77687
78056
|
await waitForRequestedPause({
|
|
77688
78057
|
checkpointLabel: 'checking the git working tree before testing',
|
|
77689
78058
|
phase: 'loading',
|
|
@@ -77852,9 +78221,9 @@
|
|
|
77852
78221
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Running prompts with ${runnerName}`);
|
|
77853
78222
|
}
|
|
77854
78223
|
/**
|
|
77855
|
-
* Prepares the `--agent` book avatar ASCII-art renderer and shows it
|
|
78224
|
+
* Prepares the `--agent` book avatar ASCII-art renderer and shows it above the dashboard boxes.
|
|
77856
78225
|
*
|
|
77857
|
-
*
|
|
78226
|
+
* Leaves the header empty when no agent is selected, the UI is disabled, or the visual cannot be rendered.
|
|
77858
78227
|
*/
|
|
77859
78228
|
async function initializeRunUiAgentVisual(uiHandle, agentSource) {
|
|
77860
78229
|
if (!uiHandle || !agentSource) {
|
|
@@ -77869,7 +78238,7 @@
|
|
|
77869
78238
|
* Loads prompt files, updates progress displays, and selects the next runnable prompt.
|
|
77870
78239
|
*/
|
|
77871
78240
|
async function loadPromptQueueSnapshot(options) {
|
|
77872
|
-
const { options: runOptions, isRichUiEnabled, progressDisplay, uiHandle, promptRunnerIdentity } = options;
|
|
78241
|
+
const { options: runOptions, isRichUiEnabled, progressDisplay, uiHandle, promptRunnerIdentity, isContinuingInterruptedPrompt, } = options;
|
|
77873
78242
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentScriptPath(undefined);
|
|
77874
78243
|
const promptFiles = await loadPromptFiles(PROMPTS_DIR$1);
|
|
77875
78244
|
const stats = summarizePrompts(promptFiles, runOptions.priorityFilter);
|
|
@@ -77881,7 +78250,11 @@
|
|
|
77881
78250
|
return {
|
|
77882
78251
|
promptFiles,
|
|
77883
78252
|
stats,
|
|
77884
|
-
|
|
78253
|
+
// Note: A resumed prompt is picked by its `[^]` status alone, the priority and runner filters select
|
|
78254
|
+
// which prompt is started next, not which unfinished work is continued
|
|
78255
|
+
nextPrompt: isContinuingInterruptedPrompt
|
|
78256
|
+
? resolveInterruptedPrompt(promptFiles)
|
|
78257
|
+
: findNextTodoPrompt(promptFiles, runOptions.priorityFilter, promptRunnerIdentity),
|
|
77885
78258
|
};
|
|
77886
78259
|
}
|
|
77887
78260
|
/**
|