@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/esm/index.es.js
CHANGED
|
@@ -48,7 +48,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
48
48
|
* @generated
|
|
49
49
|
* @see https://github.com/webgptorg/promptbook
|
|
50
50
|
*/
|
|
51
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.114.0-
|
|
51
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.114.0-20';
|
|
52
52
|
/**
|
|
53
53
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
54
54
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -1750,6 +1750,32 @@ const PTBK_MODEL_ENV = 'PTBK_MODEL';
|
|
|
1750
1750
|
*/
|
|
1751
1751
|
const PTBK_THINKING_LEVEL_ENV = 'PTBK_THINKING_LEVEL';
|
|
1752
1752
|
|
|
1753
|
+
/**
|
|
1754
|
+
* Supported values of the `--git-changes` option, which decides what happens with a dirty working tree.
|
|
1755
|
+
*
|
|
1756
|
+
* - `fail` — refuse to start while the working tree has uncommitted changes
|
|
1757
|
+
* - `ignore` — start anyway and leave the uncommitted changes where they are
|
|
1758
|
+
* - `continue` — resume the single prompt which was left in the middle of its implementation
|
|
1759
|
+
*
|
|
1760
|
+
* @private internal shared utility of `ptbk coder run`
|
|
1761
|
+
*/
|
|
1762
|
+
const GIT_CHANGES_MODE_VALUES = ['fail', 'ignore', 'continue'];
|
|
1763
|
+
/**
|
|
1764
|
+
* Mode applied when `--git-changes` is not used at all.
|
|
1765
|
+
*
|
|
1766
|
+
* @private internal shared utility of `ptbk coder run`
|
|
1767
|
+
*/
|
|
1768
|
+
const DEFAULT_GIT_CHANGES_MODE = 'fail';
|
|
1769
|
+
/**
|
|
1770
|
+
* Checks whether a value is a supported `--git-changes` mode.
|
|
1771
|
+
*
|
|
1772
|
+
* @private internal shared utility of `ptbk coder run`
|
|
1773
|
+
*/
|
|
1774
|
+
function isGitChangesMode(value) {
|
|
1775
|
+
return GIT_CHANGES_MODE_VALUES.includes(value);
|
|
1776
|
+
}
|
|
1777
|
+
// Note: [🟡] Code for CLI command [run](src/cli/cli-commands/coder/run.ts) should never be published outside of `@promptbook/cli`
|
|
1778
|
+
|
|
1753
1779
|
/**
|
|
1754
1780
|
* Supported thinking-level CLI values for coding-agent runners with configurable reasoning effort.
|
|
1755
1781
|
*
|
|
@@ -1809,6 +1835,17 @@ const PROMPT_RUNNER_MODEL_OPTION_DESCRIPTION = spaceTrim$1(`
|
|
|
1809
1835
|
OpenAI examples: gpt-5.2-codex, default
|
|
1810
1836
|
Gemini examples: gemini-3-flash-preview, default
|
|
1811
1837
|
`);
|
|
1838
|
+
/**
|
|
1839
|
+
* Commander description for the `--git-changes` option.
|
|
1840
|
+
*
|
|
1841
|
+
* @private internal utility of `promptbookCli`
|
|
1842
|
+
*/
|
|
1843
|
+
const GIT_CHANGES_OPTION_DESCRIPTION = spaceTrim$1(`
|
|
1844
|
+
Decide what happens when the working tree has uncommitted changes before a prompt starts:
|
|
1845
|
+
- fail: refuse to start and ask for a commit or a stash (the default)
|
|
1846
|
+
- ignore: start anyway and leave the uncommitted changes where they are
|
|
1847
|
+
- continue: resume the single prompt which was left in the middle of its implementation
|
|
1848
|
+
`);
|
|
1812
1849
|
/**
|
|
1813
1850
|
* Registers runner selection flags on a command.
|
|
1814
1851
|
*
|
|
@@ -1838,7 +1875,9 @@ function addPromptRunnerRuntimeOptions(command) {
|
|
|
1838
1875
|
function addPromptRunnerExecutionOptions(command) {
|
|
1839
1876
|
addPromptRunnerRuntimeOptions(command);
|
|
1840
1877
|
command.option('--no-commit', 'Leave successful changes in the working directory instead of creating git commits');
|
|
1841
|
-
command.
|
|
1878
|
+
command.addOption(new Option('--git-changes <mode>', GIT_CHANGES_OPTION_DESCRIPTION)
|
|
1879
|
+
.choices([...GIT_CHANGES_MODE_VALUES])
|
|
1880
|
+
.default(DEFAULT_GIT_CHANGES_MODE));
|
|
1842
1881
|
command.option('--no-normalize-line-endings', 'Disable automatic LF normalization for files changed in each coding round');
|
|
1843
1882
|
command.option('--auto-push', 'Automatically git push after each commit', false);
|
|
1844
1883
|
command.option('--auto-pull', 'Automatically git pull before the first and each subsequent prompt', false);
|
|
@@ -1849,11 +1888,12 @@ function addPromptRunnerExecutionOptions(command) {
|
|
|
1849
1888
|
* @private internal utility of `promptbookCli`
|
|
1850
1889
|
*/
|
|
1851
1890
|
function normalizePromptRunnerCliOptions(cliOptions, options) {
|
|
1891
|
+
var _a;
|
|
1852
1892
|
const selectionOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, options);
|
|
1853
1893
|
return {
|
|
1854
1894
|
...selectionOptions,
|
|
1855
1895
|
noCommit: !cliOptions.commit,
|
|
1856
|
-
|
|
1896
|
+
gitChanges: (_a = cliOptions.gitChanges) !== null && _a !== void 0 ? _a : DEFAULT_GIT_CHANGES_MODE,
|
|
1857
1897
|
normalizeLineEndings: cliOptions.normalizeLineEndings,
|
|
1858
1898
|
autoPush: cliOptions.autoPush,
|
|
1859
1899
|
autoPull: cliOptions.autoPull,
|
|
@@ -22752,7 +22792,7 @@ async function ensureWorkingTreeCleanForAgentQueue(projectPath) {
|
|
|
22752
22792
|
Git working tree has changes outside of \`${normalizeGitPath(AGENT_QUEUED_MESSAGES_DIRECTORY_PATH)}/\`.
|
|
22753
22793
|
|
|
22754
22794
|
Please commit or stash unrelated changes before running \`ptbk agent-folder\`
|
|
22755
|
-
OR run with \`--
|
|
22795
|
+
OR run with \`--git-changes ignore\`.
|
|
22756
22796
|
|
|
22757
22797
|
Unrelated changed files:
|
|
22758
22798
|
${unrelatedChangedFiles.map((relativePath) => `- \`${relativePath}\``).join('\n')}
|
|
@@ -22808,7 +22848,7 @@ async function pullLatestChangesForAgentQueueIfEnabled(options) {
|
|
|
22808
22848
|
* Runs the clean working tree guard unless the user explicitly disabled it.
|
|
22809
22849
|
*/
|
|
22810
22850
|
async function ensureCleanQueueIfNeeded(projectPath, options) {
|
|
22811
|
-
if (options.
|
|
22851
|
+
if (options.gitChanges !== 'fail') {
|
|
22812
22852
|
return;
|
|
22813
22853
|
}
|
|
22814
22854
|
await ensureWorkingTreeCleanForAgentQueue(projectPath);
|
|
@@ -23330,6 +23370,58 @@ function buildAgentMessageScriptPath(projectPath, messageFile) {
|
|
|
23330
23370
|
*/
|
|
23331
23371
|
const WAITING_FOR_MESSAGE_LABEL = 'Waiting for the message';
|
|
23332
23372
|
|
|
23373
|
+
/**
|
|
23374
|
+
* Colors of the key badges, so one control key looks the same in its pill and in its answer.
|
|
23375
|
+
*
|
|
23376
|
+
* @private internal constant of `ptbk coder` terminal controls
|
|
23377
|
+
*/
|
|
23378
|
+
const CODER_RUN_CONTROL_KEY_BADGE_COLORIZERS = {
|
|
23379
|
+
P: (badgeText) => colors.bgYellow.black(badgeText),
|
|
23380
|
+
S: (badgeText) => colors.bgCyan.black(badgeText),
|
|
23381
|
+
X: (badgeText) => colors.bgBlue.white(badgeText),
|
|
23382
|
+
};
|
|
23383
|
+
/**
|
|
23384
|
+
* Builds the colored badge of one control key, for example ` S `.
|
|
23385
|
+
*
|
|
23386
|
+
* @private internal utility of `ptbk coder` terminal controls
|
|
23387
|
+
*/
|
|
23388
|
+
function buildCoderRunControlKeyBadge(controlKey) {
|
|
23389
|
+
return CODER_RUN_CONTROL_KEY_BADGE_COLORIZERS[controlKey](` ${controlKey} `);
|
|
23390
|
+
}
|
|
23391
|
+
|
|
23392
|
+
/**
|
|
23393
|
+
* Marker printed before every control answer so it stands out from the runner output.
|
|
23394
|
+
*
|
|
23395
|
+
* @private internal constant of `ptbk coder` terminal controls
|
|
23396
|
+
*/
|
|
23397
|
+
const CODER_RUN_CONTROL_FEEDBACK_MARKER = '»';
|
|
23398
|
+
/**
|
|
23399
|
+
* Colors of the control answers by their meaning.
|
|
23400
|
+
*
|
|
23401
|
+
* @private internal constant of `ptbk coder` terminal controls
|
|
23402
|
+
*/
|
|
23403
|
+
const CODER_RUN_CONTROL_FEEDBACK_COLORIZERS = {
|
|
23404
|
+
success: (message) => colors.green(message),
|
|
23405
|
+
info: (message) => colors.cyan(message),
|
|
23406
|
+
warning: (message) => colors.yellow(message),
|
|
23407
|
+
};
|
|
23408
|
+
/**
|
|
23409
|
+
* Formats one control answer as a single colored terminal line.
|
|
23410
|
+
*
|
|
23411
|
+
* The same line is printed by the plain console listener and rendered inside the `Controls` box of the
|
|
23412
|
+
* rich terminal UI, so both modes answer a pressed control key identically.
|
|
23413
|
+
*
|
|
23414
|
+
* @param feedback Answer of the runner to the pressed control key.
|
|
23415
|
+
* @param repeatCount How many times this exact answer was given in a row, rendered from the second time on.
|
|
23416
|
+
*
|
|
23417
|
+
* @private internal utility of `ptbk coder` terminal controls
|
|
23418
|
+
*/
|
|
23419
|
+
function formatCoderRunControlFeedback(feedback, repeatCount = 1) {
|
|
23420
|
+
const colorizeMessage = CODER_RUN_CONTROL_FEEDBACK_COLORIZERS[feedback.tone];
|
|
23421
|
+
const repeatSuffix = repeatCount > 1 ? colors.gray(` (×${repeatCount})`) : '';
|
|
23422
|
+
return `${colors.gray(CODER_RUN_CONTROL_FEEDBACK_MARKER)} ${buildCoderRunControlKeyBadge(feedback.controlKey)} ${colorizeMessage(feedback.message)}${repeatSuffix}`;
|
|
23423
|
+
}
|
|
23424
|
+
|
|
23333
23425
|
/**
|
|
23334
23426
|
* Centers an ANSI-colored line within the available frame width.
|
|
23335
23427
|
*
|
|
@@ -23504,7 +23596,8 @@ function buildPausePresentation(phase, pauseState, pauseTargetLabel, statusMessa
|
|
|
23504
23596
|
return {
|
|
23505
23597
|
badge: buildRunningPhaseBadge(phase),
|
|
23506
23598
|
stateMessage: statusMessage,
|
|
23507
|
-
|
|
23599
|
+
// Note: The two paused variants above deliberately recolor the `P` badge, everything else uses the shared one
|
|
23600
|
+
pauseControl: buildCoderRunControlKeyBadge('P') + colors.white(' Pause'),
|
|
23508
23601
|
};
|
|
23509
23602
|
}
|
|
23510
23603
|
/**
|
|
@@ -23545,20 +23638,34 @@ function buildCoderRunControlPills(options) {
|
|
|
23545
23638
|
],
|
|
23546
23639
|
});
|
|
23547
23640
|
}
|
|
23641
|
+
/**
|
|
23642
|
+
* Builds the body of the `Controls` box.
|
|
23643
|
+
*
|
|
23644
|
+
* The pills are always shown, and right after a control key was pressed the answer of the runner to
|
|
23645
|
+
* that key is shown underneath, so no key press ever leaves the frame unchanged.
|
|
23646
|
+
*/
|
|
23647
|
+
function buildControlsBoxLines(options) {
|
|
23648
|
+
const { controlPills, controlFeedback } = options;
|
|
23649
|
+
const controlPillsLine = controlPills.join(' ');
|
|
23650
|
+
if (controlFeedback === undefined) {
|
|
23651
|
+
return [controlPillsLine];
|
|
23652
|
+
}
|
|
23653
|
+
return [controlPillsLine, formatCoderRunControlFeedback(controlFeedback, controlFeedback.repeatCount)];
|
|
23654
|
+
}
|
|
23548
23655
|
/**
|
|
23549
23656
|
* Builds the `S` wait-skip control label.
|
|
23550
23657
|
*/
|
|
23551
23658
|
function buildSkipCurrentWaitControl() {
|
|
23552
|
-
return
|
|
23659
|
+
return buildCoderRunControlKeyBadge('S') + colors.white(' Skip current waiting');
|
|
23553
23660
|
}
|
|
23554
23661
|
/**
|
|
23555
23662
|
* Builds the dynamic `X` control label.
|
|
23556
23663
|
*/
|
|
23557
23664
|
function buildEndAfterCurrentPromptControl(isEndAfterCurrentPromptRequested, sessionTotal) {
|
|
23558
23665
|
if (isEndAfterCurrentPromptRequested) {
|
|
23559
|
-
return
|
|
23666
|
+
return buildCoderRunControlKeyBadge('X') + colors.white(` Do all ${formatControlPromptCount(sessionTotal)}`);
|
|
23560
23667
|
}
|
|
23561
|
-
return
|
|
23668
|
+
return buildCoderRunControlKeyBadge('X') + colors.white(' End with this prompt');
|
|
23562
23669
|
}
|
|
23563
23670
|
/**
|
|
23564
23671
|
* Formats one prompt count for the `X` control.
|
|
@@ -23776,10 +23883,13 @@ function buildAgentRunUiFrame(options) {
|
|
|
23776
23883
|
: [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
|
|
23777
23884
|
const userMessageBoxes = buildUserMessageBoxes(options, totalWidth);
|
|
23778
23885
|
const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
|
|
23779
|
-
const
|
|
23780
|
-
|
|
23781
|
-
|
|
23782
|
-
|
|
23886
|
+
const controlsBoxLines = buildControlsBoxLines({
|
|
23887
|
+
controlPills: buildControlPills({
|
|
23888
|
+
pauseControl: pausePresentation.pauseControl,
|
|
23889
|
+
pendingEnterLabel: options.pendingEnterLabel,
|
|
23890
|
+
}),
|
|
23891
|
+
controlFeedback: options.controlFeedback,
|
|
23892
|
+
});
|
|
23783
23893
|
const frame = [
|
|
23784
23894
|
...buildAgentRunInitialsVisual(options.config.localAgentName || 'Local Agent', totalWidth),
|
|
23785
23895
|
'',
|
|
@@ -23792,7 +23902,7 @@ function buildAgentRunUiFrame(options) {
|
|
|
23792
23902
|
if (options.errors.length > 0) {
|
|
23793
23903
|
frame.push(...renderBox('Errors', options.errors.map((errorLine) => `${colors.red('✗')} ${errorLine}`), totalWidth, colors.red.bold));
|
|
23794
23904
|
}
|
|
23795
|
-
frame.push(...renderBox('Controls',
|
|
23905
|
+
frame.push(...renderBox('Controls', controlsBoxLines, totalWidth, colors.white.bold));
|
|
23796
23906
|
return frame;
|
|
23797
23907
|
}
|
|
23798
23908
|
/**
|
|
@@ -25114,6 +25224,254 @@ async function $runGoScriptWithOutput(options) {
|
|
|
25114
25224
|
});
|
|
25115
25225
|
}
|
|
25116
25226
|
|
|
25227
|
+
/**
|
|
25228
|
+
* Default label used before the next pause checkpoint is known.
|
|
25229
|
+
*/
|
|
25230
|
+
const DEFAULT_PAUSE_TARGET_LABEL = 'the next task';
|
|
25231
|
+
/**
|
|
25232
|
+
* Current pause state.
|
|
25233
|
+
*/
|
|
25234
|
+
let pauseState = 'RUNNING';
|
|
25235
|
+
/**
|
|
25236
|
+
* Label of the next checkpoint where the requested pause will take effect.
|
|
25237
|
+
*/
|
|
25238
|
+
let pauseTargetLabel = DEFAULT_PAUSE_TARGET_LABEL;
|
|
25239
|
+
/**
|
|
25240
|
+
* Token of the currently active timed wait, if the runner is inside one.
|
|
25241
|
+
*/
|
|
25242
|
+
let activeSkippableWaitToken;
|
|
25243
|
+
/**
|
|
25244
|
+
* Whether the user requested the active timed wait to end immediately.
|
|
25245
|
+
*/
|
|
25246
|
+
let isSkipCurrentWaitRequested = false;
|
|
25247
|
+
/**
|
|
25248
|
+
* Whether the runner should stop after the current prompt round reaches its normal boundary.
|
|
25249
|
+
*/
|
|
25250
|
+
let isEndAfterCurrentPromptRequested$1 = false;
|
|
25251
|
+
/**
|
|
25252
|
+
* Promise resolvers waiting for the active timed wait to be skipped.
|
|
25253
|
+
*/
|
|
25254
|
+
const SKIP_CURRENT_WAIT_RESOLVERS = new Map();
|
|
25255
|
+
/**
|
|
25256
|
+
* Stores one new pause state in the shared runner controller.
|
|
25257
|
+
*/
|
|
25258
|
+
function setPauseState(nextPauseState) {
|
|
25259
|
+
pauseState = nextPauseState;
|
|
25260
|
+
}
|
|
25261
|
+
/**
|
|
25262
|
+
* Stores one new pause target label in the shared runner controller.
|
|
25263
|
+
*/
|
|
25264
|
+
function setPauseTargetLabel(nextPauseTargetLabel) {
|
|
25265
|
+
pauseTargetLabel = nextPauseTargetLabel.trim() || DEFAULT_PAUSE_TARGET_LABEL;
|
|
25266
|
+
}
|
|
25267
|
+
/**
|
|
25268
|
+
* Stores the dynamic end-after-current-prompt state.
|
|
25269
|
+
*/
|
|
25270
|
+
function setEndAfterCurrentPromptRequested(isRequested) {
|
|
25271
|
+
isEndAfterCurrentPromptRequested$1 = isRequested;
|
|
25272
|
+
}
|
|
25273
|
+
/**
|
|
25274
|
+
* Applies the same three-state toggle used by the `P` hotkey.
|
|
25275
|
+
*/
|
|
25276
|
+
function togglePauseState() {
|
|
25277
|
+
if (pauseState === 'RUNNING') {
|
|
25278
|
+
setPauseState('PAUSING');
|
|
25279
|
+
return 'REQUESTED_PAUSE';
|
|
25280
|
+
}
|
|
25281
|
+
if (pauseState === 'PAUSING') {
|
|
25282
|
+
setPauseState('RUNNING');
|
|
25283
|
+
resetPauseTargetLabel();
|
|
25284
|
+
return 'CANCELLED_PAUSE';
|
|
25285
|
+
}
|
|
25286
|
+
setPauseState('RUNNING');
|
|
25287
|
+
resetPauseTargetLabel();
|
|
25288
|
+
return 'RESUMED';
|
|
25289
|
+
}
|
|
25290
|
+
/**
|
|
25291
|
+
* Applies the two-state toggle used by the `X` hotkey.
|
|
25292
|
+
*/
|
|
25293
|
+
function toggleEndAfterCurrentPromptState() {
|
|
25294
|
+
if (!isEndAfterCurrentPromptRequested$1) {
|
|
25295
|
+
setEndAfterCurrentPromptRequested(true);
|
|
25296
|
+
return 'REQUESTED_END';
|
|
25297
|
+
}
|
|
25298
|
+
setEndAfterCurrentPromptRequested(false);
|
|
25299
|
+
return 'CANCELLED_END';
|
|
25300
|
+
}
|
|
25301
|
+
/**
|
|
25302
|
+
* Returns whether the dynamic end-after-current-prompt control is active.
|
|
25303
|
+
*/
|
|
25304
|
+
function getEndAfterCurrentPromptState() {
|
|
25305
|
+
return isEndAfterCurrentPromptRequested$1;
|
|
25306
|
+
}
|
|
25307
|
+
/**
|
|
25308
|
+
* Starts one timed wait which can be skipped by the `S` hotkey.
|
|
25309
|
+
*/
|
|
25310
|
+
function beginSkippableWait() {
|
|
25311
|
+
const waitToken = Symbol('coder-run-skippable-wait');
|
|
25312
|
+
activeSkippableWaitToken = waitToken;
|
|
25313
|
+
isSkipCurrentWaitRequested = false;
|
|
25314
|
+
SKIP_CURRENT_WAIT_RESOLVERS.clear();
|
|
25315
|
+
return waitToken;
|
|
25316
|
+
}
|
|
25317
|
+
/**
|
|
25318
|
+
* Finishes one timed wait and clears any skip request that belonged to it.
|
|
25319
|
+
*/
|
|
25320
|
+
function finishSkippableWait(waitToken) {
|
|
25321
|
+
if (activeSkippableWaitToken !== waitToken) {
|
|
25322
|
+
return;
|
|
25323
|
+
}
|
|
25324
|
+
activeSkippableWaitToken = undefined;
|
|
25325
|
+
isSkipCurrentWaitRequested = false;
|
|
25326
|
+
resolveSkipCurrentWaitResolvers(waitToken);
|
|
25327
|
+
SKIP_CURRENT_WAIT_RESOLVERS.delete(waitToken);
|
|
25328
|
+
}
|
|
25329
|
+
/**
|
|
25330
|
+
* Requests that the currently active timed wait ends immediately.
|
|
25331
|
+
*/
|
|
25332
|
+
function requestSkipCurrentWait() {
|
|
25333
|
+
if (activeSkippableWaitToken === undefined) {
|
|
25334
|
+
return 'NO_ACTIVE_WAIT';
|
|
25335
|
+
}
|
|
25336
|
+
isSkipCurrentWaitRequested = true;
|
|
25337
|
+
resolveSkipCurrentWaitResolvers(activeSkippableWaitToken);
|
|
25338
|
+
return 'REQUESTED_SKIP';
|
|
25339
|
+
}
|
|
25340
|
+
/**
|
|
25341
|
+
* Returns whether one timed wait should end early.
|
|
25342
|
+
*/
|
|
25343
|
+
function shouldSkipCurrentWait(waitToken) {
|
|
25344
|
+
return activeSkippableWaitToken === waitToken && isSkipCurrentWaitRequested;
|
|
25345
|
+
}
|
|
25346
|
+
/**
|
|
25347
|
+
* Waits for either a timeout or an `S` hotkey skip request.
|
|
25348
|
+
*/
|
|
25349
|
+
async function waitForSkippableMilliseconds(waitToken, durationMs) {
|
|
25350
|
+
if (durationMs <= 0 || shouldSkipCurrentWait(waitToken)) {
|
|
25351
|
+
return;
|
|
25352
|
+
}
|
|
25353
|
+
await new Promise((resolve) => {
|
|
25354
|
+
let timeout = setTimeout(resolveWait, durationMs);
|
|
25355
|
+
const resolver = () => resolveWait();
|
|
25356
|
+
const resolvers = getSkipCurrentWaitResolvers(waitToken);
|
|
25357
|
+
resolvers.add(resolver);
|
|
25358
|
+
function resolveWait() {
|
|
25359
|
+
if (timeout !== undefined) {
|
|
25360
|
+
clearTimeout(timeout);
|
|
25361
|
+
timeout = undefined;
|
|
25362
|
+
}
|
|
25363
|
+
resolvers.delete(resolver);
|
|
25364
|
+
if (resolvers.size === 0) {
|
|
25365
|
+
SKIP_CURRENT_WAIT_RESOLVERS.delete(waitToken);
|
|
25366
|
+
}
|
|
25367
|
+
resolve();
|
|
25368
|
+
}
|
|
25369
|
+
});
|
|
25370
|
+
}
|
|
25371
|
+
/**
|
|
25372
|
+
* Restores all shared terminal controls to their default state.
|
|
25373
|
+
*/
|
|
25374
|
+
function resetCoderRunControls() {
|
|
25375
|
+
setPauseState('RUNNING');
|
|
25376
|
+
resetPauseTargetLabel();
|
|
25377
|
+
setEndAfterCurrentPromptRequested(false);
|
|
25378
|
+
if (activeSkippableWaitToken !== undefined) {
|
|
25379
|
+
resolveSkipCurrentWaitResolvers(activeSkippableWaitToken);
|
|
25380
|
+
}
|
|
25381
|
+
activeSkippableWaitToken = undefined;
|
|
25382
|
+
isSkipCurrentWaitRequested = false;
|
|
25383
|
+
SKIP_CURRENT_WAIT_RESOLVERS.clear();
|
|
25384
|
+
}
|
|
25385
|
+
/**
|
|
25386
|
+
* If the execution is paused, it will wait until it is resumed.
|
|
25387
|
+
*
|
|
25388
|
+
* @param options.silent - When `true`, suppresses console output (used when the terminal UI handles display).
|
|
25389
|
+
* @param options.onPaused - Callback invoked when entering the PAUSED state.
|
|
25390
|
+
* @param options.onResumed - Callback invoked when leaving the PAUSED state.
|
|
25391
|
+
*/
|
|
25392
|
+
async function checkPause(options) {
|
|
25393
|
+
var _a, _b;
|
|
25394
|
+
if (pauseState === 'PAUSING') {
|
|
25395
|
+
setPauseState('PAUSED');
|
|
25396
|
+
if (!(options === null || options === void 0 ? void 0 : options.silent)) {
|
|
25397
|
+
console.log(colors.bgWhite.black(`Paused before ${getPauseTargetLabel()}`) +
|
|
25398
|
+
colors.gray(' (Press "p" to resume)'));
|
|
25399
|
+
}
|
|
25400
|
+
(_a = options === null || options === void 0 ? void 0 : options.onPaused) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
25401
|
+
while (getPauseState() === 'PAUSED') {
|
|
25402
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
25403
|
+
}
|
|
25404
|
+
(_b = options === null || options === void 0 ? void 0 : options.onResumed) === null || _b === void 0 ? void 0 : _b.call(options);
|
|
25405
|
+
if (!(options === null || options === void 0 ? void 0 : options.silent)) {
|
|
25406
|
+
console.log(colors.green('Resuming...'));
|
|
25407
|
+
}
|
|
25408
|
+
}
|
|
25409
|
+
}
|
|
25410
|
+
/**
|
|
25411
|
+
* Returns the current pause state for external consumers such as the terminal UI.
|
|
25412
|
+
*/
|
|
25413
|
+
function getPauseState() {
|
|
25414
|
+
return pauseState;
|
|
25415
|
+
}
|
|
25416
|
+
/**
|
|
25417
|
+
* Returns the label of the next checkpoint where pausing will take effect.
|
|
25418
|
+
*/
|
|
25419
|
+
function getPauseTargetLabel() {
|
|
25420
|
+
return pauseTargetLabel;
|
|
25421
|
+
}
|
|
25422
|
+
/**
|
|
25423
|
+
* Updates the label of the next pause checkpoint.
|
|
25424
|
+
*/
|
|
25425
|
+
function announcePauseTargetLabel(nextPauseTargetLabel) {
|
|
25426
|
+
setPauseTargetLabel(nextPauseTargetLabel);
|
|
25427
|
+
}
|
|
25428
|
+
/**
|
|
25429
|
+
* Restores the default generic pause target label.
|
|
25430
|
+
*/
|
|
25431
|
+
function resetPauseTargetLabel() {
|
|
25432
|
+
setPauseTargetLabel(DEFAULT_PAUSE_TARGET_LABEL);
|
|
25433
|
+
}
|
|
25434
|
+
/**
|
|
25435
|
+
* Requests a pause from an external controller (e.g. the Ink UI).
|
|
25436
|
+
*/
|
|
25437
|
+
function requestPause() {
|
|
25438
|
+
if (pauseState === 'RUNNING') {
|
|
25439
|
+
setPauseState('PAUSING');
|
|
25440
|
+
}
|
|
25441
|
+
}
|
|
25442
|
+
/**
|
|
25443
|
+
* Resumes execution from an external controller after a pause.
|
|
25444
|
+
*/
|
|
25445
|
+
function requestResume() {
|
|
25446
|
+
setPauseState('RUNNING');
|
|
25447
|
+
resetPauseTargetLabel();
|
|
25448
|
+
}
|
|
25449
|
+
/**
|
|
25450
|
+
* Gets or creates the resolver set for one timed wait.
|
|
25451
|
+
*/
|
|
25452
|
+
function getSkipCurrentWaitResolvers(waitToken) {
|
|
25453
|
+
const existingResolvers = SKIP_CURRENT_WAIT_RESOLVERS.get(waitToken);
|
|
25454
|
+
if (existingResolvers !== undefined) {
|
|
25455
|
+
return existingResolvers;
|
|
25456
|
+
}
|
|
25457
|
+
const resolvers = new Set();
|
|
25458
|
+
SKIP_CURRENT_WAIT_RESOLVERS.set(waitToken, resolvers);
|
|
25459
|
+
return resolvers;
|
|
25460
|
+
}
|
|
25461
|
+
/**
|
|
25462
|
+
* Resolves all pending skip waiters for one timed wait.
|
|
25463
|
+
*/
|
|
25464
|
+
function resolveSkipCurrentWaitResolvers(waitToken) {
|
|
25465
|
+
const resolvers = SKIP_CURRENT_WAIT_RESOLVERS.get(waitToken);
|
|
25466
|
+
if (resolvers === undefined) {
|
|
25467
|
+
return;
|
|
25468
|
+
}
|
|
25469
|
+
for (const resolver of resolvers) {
|
|
25470
|
+
resolver();
|
|
25471
|
+
}
|
|
25472
|
+
resolvers.clear();
|
|
25473
|
+
}
|
|
25474
|
+
|
|
25117
25475
|
/**
|
|
25118
25476
|
* Minimum timer duration used to avoid a zero-millisecond polling loop.
|
|
25119
25477
|
*/
|
|
@@ -25163,6 +25521,32 @@ async function waitForRegularMilliseconds(durationMs) {
|
|
|
25163
25521
|
await new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
25164
25522
|
}
|
|
25165
25523
|
|
|
25524
|
+
/**
|
|
25525
|
+
* Waits until one wall-clock deadline has passed, or until the user skips the wait with the `S` control.
|
|
25526
|
+
*
|
|
25527
|
+
* This is the single way `ptbk coder` waits for a deadline the user is allowed to cut short, so every
|
|
25528
|
+
* wait which shows the `S Skip current waiting` control really reacts to it: the pacing waits between
|
|
25529
|
+
* prompts, the cool-down after an error, the harness session-limit waits and the server keep-alive poll.
|
|
25530
|
+
*
|
|
25531
|
+
* @private internal utility of `ptbk coder` wait handling
|
|
25532
|
+
*/
|
|
25533
|
+
async function waitForSkippableWorldTimeDeadline(options) {
|
|
25534
|
+
const { deadlineTimeMs, pollIntervalMs, onTick } = options;
|
|
25535
|
+
const waitToken = beginSkippableWait();
|
|
25536
|
+
try {
|
|
25537
|
+
await waitUntilWorldTimeDeadline({
|
|
25538
|
+
deadlineTimeMs,
|
|
25539
|
+
pollIntervalMs,
|
|
25540
|
+
onTick,
|
|
25541
|
+
shouldStopWaiting: () => shouldSkipCurrentWait(waitToken),
|
|
25542
|
+
waitForMilliseconds: (waitDurationMs) => waitForSkippableMilliseconds(waitToken, waitDurationMs),
|
|
25543
|
+
});
|
|
25544
|
+
}
|
|
25545
|
+
finally {
|
|
25546
|
+
finishSkippableWait(waitToken);
|
|
25547
|
+
}
|
|
25548
|
+
}
|
|
25549
|
+
|
|
25166
25550
|
/**
|
|
25167
25551
|
* Base delimiter used for passing large prompts through stdin.
|
|
25168
25552
|
*/
|
|
@@ -25570,6 +25954,10 @@ class ClaudeCodeRunner {
|
|
|
25570
25954
|
}
|
|
25571
25955
|
/**
|
|
25572
25956
|
* Waits until the Claude Code session can be resumed, keeping terminal status clear.
|
|
25957
|
+
*
|
|
25958
|
+
* The wait runs in the `waiting` phase, where the terminal UI offers `S Skip current waiting`, so it is
|
|
25959
|
+
* a skippable wait: pressing `S` resumes the session with `--resume` immediately instead of sitting out
|
|
25960
|
+
* the reset window.
|
|
25573
25961
|
*/
|
|
25574
25962
|
async function waitForClaudeCodeSessionLimitReset(sessionLimit, resurrectionCount, options) {
|
|
25575
25963
|
var _a, _b;
|
|
@@ -25580,7 +25968,7 @@ async function waitForClaudeCodeSessionLimitReset(sessionLimit, resurrectionCoun
|
|
|
25580
25968
|
if ((_a = options.shouldPrintLiveOutput) !== null && _a !== void 0 ? _a : true) {
|
|
25581
25969
|
console.warn(colors.yellow(`[claude-code] Session limit detected for ${sessionLimit.sessionId}. Resurrection #${resurrectionCount} will resume with --resume after ${formatDurationMs(delayMs)}. ${resetSummary}`));
|
|
25582
25970
|
}
|
|
25583
|
-
await
|
|
25971
|
+
await waitForSkippableWorldTimeDeadline({
|
|
25584
25972
|
deadlineTimeMs: resetDeadlineTimeMs,
|
|
25585
25973
|
pollIntervalMs: CLAUDE_CODE_SESSION_RESURRECTION_POLL_MS,
|
|
25586
25974
|
onTick: async (remainingDelayMs) => {
|
|
@@ -27019,292 +27407,100 @@ function exitForMissingModel(agentName, availableModels, exampleUsages) {
|
|
|
27019
27407
|
}
|
|
27020
27408
|
|
|
27021
27409
|
/**
|
|
27022
|
-
*
|
|
27023
|
-
|
|
27024
|
-
|
|
27025
|
-
|
|
27026
|
-
*
|
|
27027
|
-
|
|
27028
|
-
|
|
27029
|
-
|
|
27030
|
-
*
|
|
27031
|
-
|
|
27032
|
-
let pauseTargetLabel = DEFAULT_PAUSE_TARGET_LABEL;
|
|
27033
|
-
/**
|
|
27034
|
-
* Token of the currently active timed wait, if the runner is inside one.
|
|
27035
|
-
*/
|
|
27036
|
-
let activeSkippableWaitToken;
|
|
27037
|
-
/**
|
|
27038
|
-
* Whether the user requested the active timed wait to end immediately.
|
|
27039
|
-
*/
|
|
27040
|
-
let isSkipCurrentWaitRequested = false;
|
|
27041
|
-
/**
|
|
27042
|
-
* Whether the runner should stop after the current prompt round reaches its normal boundary.
|
|
27043
|
-
*/
|
|
27044
|
-
let isEndAfterCurrentPromptRequested$1 = false;
|
|
27045
|
-
/**
|
|
27046
|
-
* Promise resolvers waiting for the active timed wait to be skipped.
|
|
27047
|
-
*/
|
|
27048
|
-
const SKIP_CURRENT_WAIT_RESOLVERS = new Map();
|
|
27049
|
-
/**
|
|
27050
|
-
* Stores one new pause state in the shared runner controller.
|
|
27051
|
-
*/
|
|
27052
|
-
function setPauseState(nextPauseState) {
|
|
27053
|
-
pauseState = nextPauseState;
|
|
27054
|
-
}
|
|
27055
|
-
/**
|
|
27056
|
-
* Stores one new pause target label in the shared runner controller.
|
|
27057
|
-
*/
|
|
27058
|
-
function setPauseTargetLabel(nextPauseTargetLabel) {
|
|
27059
|
-
pauseTargetLabel = nextPauseTargetLabel.trim() || DEFAULT_PAUSE_TARGET_LABEL;
|
|
27060
|
-
}
|
|
27061
|
-
/**
|
|
27062
|
-
* Stores the dynamic end-after-current-prompt state.
|
|
27063
|
-
*/
|
|
27064
|
-
function setEndAfterCurrentPromptRequested(isRequested) {
|
|
27065
|
-
isEndAfterCurrentPromptRequested$1 = isRequested;
|
|
27066
|
-
}
|
|
27067
|
-
/**
|
|
27068
|
-
* Applies the same three-state toggle used by the `P` hotkey.
|
|
27069
|
-
*/
|
|
27070
|
-
function togglePauseState() {
|
|
27071
|
-
if (pauseState === 'RUNNING') {
|
|
27072
|
-
setPauseState('PAUSING');
|
|
27073
|
-
return 'REQUESTED_PAUSE';
|
|
27074
|
-
}
|
|
27075
|
-
if (pauseState === 'PAUSING') {
|
|
27076
|
-
setPauseState('RUNNING');
|
|
27077
|
-
resetPauseTargetLabel();
|
|
27078
|
-
return 'CANCELLED_PAUSE';
|
|
27079
|
-
}
|
|
27080
|
-
setPauseState('RUNNING');
|
|
27081
|
-
resetPauseTargetLabel();
|
|
27082
|
-
return 'RESUMED';
|
|
27083
|
-
}
|
|
27084
|
-
/**
|
|
27085
|
-
* Applies the two-state toggle used by the `X` hotkey.
|
|
27086
|
-
*/
|
|
27087
|
-
function toggleEndAfterCurrentPromptState() {
|
|
27088
|
-
if (!isEndAfterCurrentPromptRequested$1) {
|
|
27089
|
-
setEndAfterCurrentPromptRequested(true);
|
|
27090
|
-
return 'REQUESTED_END';
|
|
27091
|
-
}
|
|
27092
|
-
setEndAfterCurrentPromptRequested(false);
|
|
27093
|
-
return 'CANCELLED_END';
|
|
27094
|
-
}
|
|
27095
|
-
/**
|
|
27096
|
-
* Returns whether the dynamic end-after-current-prompt control is active.
|
|
27097
|
-
*/
|
|
27098
|
-
function getEndAfterCurrentPromptState() {
|
|
27099
|
-
return isEndAfterCurrentPromptRequested$1;
|
|
27100
|
-
}
|
|
27101
|
-
/**
|
|
27102
|
-
* Starts one timed wait which can be skipped by the `S` hotkey.
|
|
27103
|
-
*/
|
|
27104
|
-
function beginSkippableWait() {
|
|
27105
|
-
const waitToken = Symbol('coder-run-skippable-wait');
|
|
27106
|
-
activeSkippableWaitToken = waitToken;
|
|
27107
|
-
isSkipCurrentWaitRequested = false;
|
|
27108
|
-
SKIP_CURRENT_WAIT_RESOLVERS.clear();
|
|
27109
|
-
return waitToken;
|
|
27110
|
-
}
|
|
27111
|
-
/**
|
|
27112
|
-
* Finishes one timed wait and clears any skip request that belonged to it.
|
|
27113
|
-
*/
|
|
27114
|
-
function finishSkippableWait(waitToken) {
|
|
27115
|
-
if (activeSkippableWaitToken !== waitToken) {
|
|
27116
|
-
return;
|
|
27117
|
-
}
|
|
27118
|
-
activeSkippableWaitToken = undefined;
|
|
27119
|
-
isSkipCurrentWaitRequested = false;
|
|
27120
|
-
resolveSkipCurrentWaitResolvers(waitToken);
|
|
27121
|
-
SKIP_CURRENT_WAIT_RESOLVERS.delete(waitToken);
|
|
27122
|
-
}
|
|
27123
|
-
/**
|
|
27124
|
-
* Requests that the currently active timed wait ends immediately.
|
|
27125
|
-
*/
|
|
27126
|
-
function requestSkipCurrentWait() {
|
|
27127
|
-
if (activeSkippableWaitToken === undefined) {
|
|
27128
|
-
return 'NO_ACTIVE_WAIT';
|
|
27129
|
-
}
|
|
27130
|
-
isSkipCurrentWaitRequested = true;
|
|
27131
|
-
resolveSkipCurrentWaitResolvers(activeSkippableWaitToken);
|
|
27132
|
-
return 'REQUESTED_SKIP';
|
|
27133
|
-
}
|
|
27134
|
-
/**
|
|
27135
|
-
* Returns whether one timed wait should end early.
|
|
27136
|
-
*/
|
|
27137
|
-
function shouldSkipCurrentWait(waitToken) {
|
|
27138
|
-
return activeSkippableWaitToken === waitToken && isSkipCurrentWaitRequested;
|
|
27139
|
-
}
|
|
27140
|
-
/**
|
|
27141
|
-
* Waits for either a timeout or an `S` hotkey skip request.
|
|
27410
|
+
* Applies the terminal control bound to one pressed key and describes what that key press did.
|
|
27411
|
+
*
|
|
27412
|
+
* This is the single place where the `P`, `S` and `X` keys are turned into runner actions, so the
|
|
27413
|
+
* plain console listener and the rich terminal UI always perform the same action and always answer
|
|
27414
|
+
* the user, even when the pressed control changed nothing.
|
|
27415
|
+
*
|
|
27416
|
+
* @param keyName Name of the pressed key as reported by the `keypress` event.
|
|
27417
|
+
* @returns The answer shown to the user, or `undefined` when the key is not a control key.
|
|
27418
|
+
*
|
|
27419
|
+
* @private internal utility of `ptbk coder` terminal controls
|
|
27142
27420
|
*/
|
|
27143
|
-
|
|
27144
|
-
|
|
27145
|
-
|
|
27421
|
+
function applyCoderRunControlKey(keyName) {
|
|
27422
|
+
switch (keyName) {
|
|
27423
|
+
case 'p':
|
|
27424
|
+
return describePauseToggle(togglePauseState());
|
|
27425
|
+
case 's':
|
|
27426
|
+
return describeSkipCurrentWait(requestSkipCurrentWait());
|
|
27427
|
+
case 'x':
|
|
27428
|
+
return describeEndAfterCurrentPrompt(toggleEndAfterCurrentPromptState());
|
|
27429
|
+
default:
|
|
27430
|
+
return undefined;
|
|
27146
27431
|
}
|
|
27147
|
-
await new Promise((resolve) => {
|
|
27148
|
-
let timeout = setTimeout(resolveWait, durationMs);
|
|
27149
|
-
const resolver = () => resolveWait();
|
|
27150
|
-
const resolvers = getSkipCurrentWaitResolvers(waitToken);
|
|
27151
|
-
resolvers.add(resolver);
|
|
27152
|
-
function resolveWait() {
|
|
27153
|
-
if (timeout !== undefined) {
|
|
27154
|
-
clearTimeout(timeout);
|
|
27155
|
-
timeout = undefined;
|
|
27156
|
-
}
|
|
27157
|
-
resolvers.delete(resolver);
|
|
27158
|
-
if (resolvers.size === 0) {
|
|
27159
|
-
SKIP_CURRENT_WAIT_RESOLVERS.delete(waitToken);
|
|
27160
|
-
}
|
|
27161
|
-
resolve();
|
|
27162
|
-
}
|
|
27163
|
-
});
|
|
27164
27432
|
}
|
|
27165
27433
|
/**
|
|
27166
|
-
*
|
|
27434
|
+
* Describes what pressing `P` did.
|
|
27435
|
+
*
|
|
27436
|
+
* @private helper of `applyCoderRunControlKey`
|
|
27167
27437
|
*/
|
|
27168
|
-
function
|
|
27169
|
-
|
|
27170
|
-
|
|
27171
|
-
|
|
27172
|
-
|
|
27173
|
-
|
|
27438
|
+
function describePauseToggle(toggleResult) {
|
|
27439
|
+
if (toggleResult === 'REQUESTED_PAUSE') {
|
|
27440
|
+
return {
|
|
27441
|
+
controlKey: 'P',
|
|
27442
|
+
message: `Pause requested, pausing before ${getPauseTargetLabel()}`,
|
|
27443
|
+
tone: 'warning',
|
|
27444
|
+
};
|
|
27174
27445
|
}
|
|
27175
|
-
|
|
27176
|
-
|
|
27177
|
-
|
|
27178
|
-
|
|
27179
|
-
|
|
27180
|
-
|
|
27181
|
-
*/
|
|
27182
|
-
function listenForCoderRunControls() {
|
|
27183
|
-
if (!process.stdin.isTTY) {
|
|
27184
|
-
return;
|
|
27446
|
+
if (toggleResult === 'CANCELLED_PAUSE') {
|
|
27447
|
+
return {
|
|
27448
|
+
controlKey: 'P',
|
|
27449
|
+
message: 'Pause cancelled, the run continues',
|
|
27450
|
+
tone: 'success',
|
|
27451
|
+
};
|
|
27185
27452
|
}
|
|
27186
|
-
|
|
27187
|
-
|
|
27188
|
-
|
|
27189
|
-
|
|
27190
|
-
|
|
27191
|
-
}
|
|
27192
|
-
if (key.name === 'p') {
|
|
27193
|
-
const toggleResult = togglePauseState();
|
|
27194
|
-
if (toggleResult === 'REQUESTED_PAUSE') {
|
|
27195
|
-
// Note: Using console.log here which adds a new line.
|
|
27196
|
-
// This is intentional to prevent the message from being overwritten.
|
|
27197
|
-
console.log(colors.bgWhite('Pausing...'));
|
|
27198
|
-
}
|
|
27199
|
-
else if (toggleResult === 'CANCELLED_PAUSE') {
|
|
27200
|
-
console.log(colors.green('Pause cancelled. Resuming...'));
|
|
27201
|
-
}
|
|
27202
|
-
}
|
|
27203
|
-
if (key.name === 's') {
|
|
27204
|
-
if (requestSkipCurrentWait() === 'REQUESTED_SKIP') {
|
|
27205
|
-
console.log(colors.green('Skipping current wait...'));
|
|
27206
|
-
}
|
|
27207
|
-
}
|
|
27208
|
-
if (key.name === 'x') {
|
|
27209
|
-
const toggleResult = toggleEndAfterCurrentPromptState();
|
|
27210
|
-
if (toggleResult === 'REQUESTED_END') {
|
|
27211
|
-
console.log(colors.yellow('Will end after the current prompt finishes.'));
|
|
27212
|
-
}
|
|
27213
|
-
else {
|
|
27214
|
-
console.log(colors.green('End request cancelled. Continuing all prompts.'));
|
|
27215
|
-
}
|
|
27216
|
-
}
|
|
27217
|
-
});
|
|
27453
|
+
return {
|
|
27454
|
+
controlKey: 'P',
|
|
27455
|
+
message: 'Resuming the run',
|
|
27456
|
+
tone: 'success',
|
|
27457
|
+
};
|
|
27218
27458
|
}
|
|
27219
27459
|
/**
|
|
27220
|
-
*
|
|
27460
|
+
* Describes what pressing `S` did.
|
|
27221
27461
|
*
|
|
27222
|
-
* @
|
|
27223
|
-
* @param options.onPaused - Callback invoked when entering the PAUSED state.
|
|
27224
|
-
* @param options.onResumed - Callback invoked when leaving the PAUSED state.
|
|
27225
|
-
*/
|
|
27226
|
-
async function checkPause(options) {
|
|
27227
|
-
var _a, _b;
|
|
27228
|
-
if (pauseState === 'PAUSING') {
|
|
27229
|
-
setPauseState('PAUSED');
|
|
27230
|
-
if (!(options === null || options === void 0 ? void 0 : options.silent)) {
|
|
27231
|
-
console.log(colors.bgWhite.black(`Paused before ${getPauseTargetLabel()}`) +
|
|
27232
|
-
colors.gray(' (Press "p" to resume)'));
|
|
27233
|
-
}
|
|
27234
|
-
(_a = options === null || options === void 0 ? void 0 : options.onPaused) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
27235
|
-
while (getPauseState() === 'PAUSED') {
|
|
27236
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
27237
|
-
}
|
|
27238
|
-
(_b = options === null || options === void 0 ? void 0 : options.onResumed) === null || _b === void 0 ? void 0 : _b.call(options);
|
|
27239
|
-
if (!(options === null || options === void 0 ? void 0 : options.silent)) {
|
|
27240
|
-
console.log(colors.green('Resuming...'));
|
|
27241
|
-
}
|
|
27242
|
-
}
|
|
27243
|
-
}
|
|
27244
|
-
/**
|
|
27245
|
-
* Returns the current pause state for external consumers such as the terminal UI.
|
|
27462
|
+
* @private helper of `applyCoderRunControlKey`
|
|
27246
27463
|
*/
|
|
27247
|
-
function
|
|
27248
|
-
|
|
27249
|
-
|
|
27250
|
-
|
|
27251
|
-
|
|
27252
|
-
|
|
27253
|
-
|
|
27254
|
-
return pauseTargetLabel;
|
|
27255
|
-
}
|
|
27256
|
-
/**
|
|
27257
|
-
* Updates the label of the next pause checkpoint.
|
|
27258
|
-
*/
|
|
27259
|
-
function announcePauseTargetLabel(nextPauseTargetLabel) {
|
|
27260
|
-
setPauseTargetLabel(nextPauseTargetLabel);
|
|
27261
|
-
}
|
|
27262
|
-
/**
|
|
27263
|
-
* Restores the default generic pause target label.
|
|
27264
|
-
*/
|
|
27265
|
-
function resetPauseTargetLabel() {
|
|
27266
|
-
setPauseTargetLabel(DEFAULT_PAUSE_TARGET_LABEL);
|
|
27267
|
-
}
|
|
27268
|
-
/**
|
|
27269
|
-
* Requests a pause from an external controller (e.g. the Ink UI).
|
|
27270
|
-
*/
|
|
27271
|
-
function requestPause() {
|
|
27272
|
-
if (pauseState === 'RUNNING') {
|
|
27273
|
-
setPauseState('PAUSING');
|
|
27464
|
+
function describeSkipCurrentWait(skipResult) {
|
|
27465
|
+
if (skipResult === 'REQUESTED_SKIP') {
|
|
27466
|
+
return {
|
|
27467
|
+
controlKey: 'S',
|
|
27468
|
+
message: 'Skipping the current waiting, continuing right now',
|
|
27469
|
+
tone: 'success',
|
|
27470
|
+
};
|
|
27274
27471
|
}
|
|
27472
|
+
return {
|
|
27473
|
+
controlKey: 'S',
|
|
27474
|
+
message: 'Nothing to skip, the coder is not waiting right now',
|
|
27475
|
+
tone: 'info',
|
|
27476
|
+
};
|
|
27275
27477
|
}
|
|
27276
27478
|
/**
|
|
27277
|
-
*
|
|
27278
|
-
|
|
27279
|
-
|
|
27280
|
-
setPauseState('RUNNING');
|
|
27281
|
-
resetPauseTargetLabel();
|
|
27282
|
-
}
|
|
27283
|
-
/**
|
|
27284
|
-
* Gets or creates the resolver set for one timed wait.
|
|
27479
|
+
* Describes what pressing `X` did.
|
|
27480
|
+
*
|
|
27481
|
+
* @private helper of `applyCoderRunControlKey`
|
|
27285
27482
|
*/
|
|
27286
|
-
function
|
|
27287
|
-
|
|
27288
|
-
|
|
27289
|
-
|
|
27483
|
+
function describeEndAfterCurrentPrompt(toggleResult) {
|
|
27484
|
+
if (toggleResult === 'REQUESTED_END') {
|
|
27485
|
+
return {
|
|
27486
|
+
controlKey: 'X',
|
|
27487
|
+
message: 'Ending after the current prompt finishes',
|
|
27488
|
+
tone: 'warning',
|
|
27489
|
+
};
|
|
27290
27490
|
}
|
|
27291
|
-
|
|
27292
|
-
|
|
27293
|
-
|
|
27491
|
+
return {
|
|
27492
|
+
controlKey: 'X',
|
|
27493
|
+
message: 'End cancelled, the whole queue will be processed',
|
|
27494
|
+
tone: 'success',
|
|
27495
|
+
};
|
|
27294
27496
|
}
|
|
27497
|
+
|
|
27295
27498
|
/**
|
|
27296
|
-
*
|
|
27499
|
+
* How long one control answer stays visible in the rich terminal UI.
|
|
27500
|
+
*
|
|
27501
|
+
* @private internal constant of `ptbk coder` terminal controls
|
|
27297
27502
|
*/
|
|
27298
|
-
|
|
27299
|
-
const resolvers = SKIP_CURRENT_WAIT_RESOLVERS.get(waitToken);
|
|
27300
|
-
if (resolvers === undefined) {
|
|
27301
|
-
return;
|
|
27302
|
-
}
|
|
27303
|
-
for (const resolver of resolvers) {
|
|
27304
|
-
resolver();
|
|
27305
|
-
}
|
|
27306
|
-
resolvers.clear();
|
|
27307
|
-
}
|
|
27503
|
+
const CODER_RUN_CONTROL_FEEDBACK_DURATION_MS = 5000;
|
|
27308
27504
|
|
|
27309
27505
|
/**
|
|
27310
27506
|
* Human-readable label for a priority scope without any bounds.
|
|
@@ -27429,85 +27625,696 @@ function assertPriorityBoundary(optionName, priority) {
|
|
|
27429
27625
|
// Note: [🟡] Code for priority filtering [priorityFilter](scripts/run-codex-prompts/prompts/priorityFilter.ts) should never be published outside of `@promptbook/cli`
|
|
27430
27626
|
|
|
27431
27627
|
/**
|
|
27432
|
-
*
|
|
27628
|
+
* Minimum width used for the rich coder-run frame.
|
|
27433
27629
|
*/
|
|
27434
|
-
const
|
|
27435
|
-
colors.magenta.bold(' .-""""-.'),
|
|
27436
|
-
colors.magenta.bold(" .' .-. '."),
|
|
27437
|
-
`${colors.magenta.bold(' / (')}${colors.yellow.bold('o o')}${colors.magenta.bold(') \\')}`,
|
|
27438
|
-
colors.magenta.bold(' | ^ |'),
|
|
27439
|
-
colors.magenta.bold(' | \\___/ |'),
|
|
27440
|
-
colors.magenta.bold(' \\___________/'),
|
|
27441
|
-
];
|
|
27630
|
+
const MIN_FRAME_WIDTH = 56;
|
|
27442
27631
|
/**
|
|
27443
|
-
*
|
|
27632
|
+
* Maximum width used for the rich coder-run frame.
|
|
27444
27633
|
*/
|
|
27445
|
-
const
|
|
27634
|
+
const MAX_FRAME_WIDTH = 96;
|
|
27446
27635
|
/**
|
|
27447
|
-
*
|
|
27636
|
+
* Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
|
|
27448
27637
|
*/
|
|
27449
|
-
|
|
27450
|
-
|
|
27451
|
-
|
|
27452
|
-
|
|
27453
|
-
|
|
27454
|
-
|
|
27455
|
-
|
|
27456
|
-
|
|
27457
|
-
|
|
27458
|
-
|
|
27459
|
-
|
|
27460
|
-
|
|
27461
|
-
|
|
27462
|
-
|
|
27463
|
-
|
|
27464
|
-
|
|
27465
|
-
|
|
27466
|
-
|
|
27467
|
-
|
|
27468
|
-
|
|
27469
|
-
|
|
27470
|
-
|
|
27471
|
-
|
|
27472
|
-
|
|
27473
|
-
|
|
27474
|
-
|
|
27475
|
-
|
|
27476
|
-
|
|
27477
|
-
|
|
27478
|
-
|
|
27479
|
-
|
|
27480
|
-
|
|
27481
|
-
|
|
27482
|
-
|
|
27638
|
+
function buildCoderRunUiFrame(options) {
|
|
27639
|
+
const totalWidth = Math.max(MIN_FRAME_WIDTH, Math.min(options.terminalWidth, MAX_FRAME_WIDTH));
|
|
27640
|
+
const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
|
|
27641
|
+
const promptStatusPrefix = isPromptActive ? `${colors.yellow(`${options.spinner} `)}` : '';
|
|
27642
|
+
const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.pauseTargetLabel, options.statusMessage);
|
|
27643
|
+
const sessionLines = buildSessionLines(options, totalWidth, pausePresentation);
|
|
27644
|
+
const currentTaskLines = options.currentPromptLabel
|
|
27645
|
+
? [
|
|
27646
|
+
`${promptStatusPrefix}${colors.bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
|
|
27647
|
+
`Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
|
|
27648
|
+
...options.detailLines.map((detailLine) => `• ${detailLine}`),
|
|
27649
|
+
]
|
|
27650
|
+
: [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
|
|
27651
|
+
const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
|
|
27652
|
+
const controlsBoxLines = buildControlsBoxLines({
|
|
27653
|
+
controlPills: buildCoderRunControlPills({
|
|
27654
|
+
phase: options.phase,
|
|
27655
|
+
pauseControl: pausePresentation.pauseControl,
|
|
27656
|
+
pendingEnterLabel: options.pendingEnterLabel,
|
|
27657
|
+
isEndAfterCurrentPromptRequested: options.isEndAfterCurrentPromptRequested,
|
|
27658
|
+
sessionTotal: options.progress.sessionTotal,
|
|
27659
|
+
}),
|
|
27660
|
+
controlFeedback: options.controlFeedback,
|
|
27661
|
+
});
|
|
27662
|
+
const frame = [
|
|
27663
|
+
...buildFrameHeaderVisual(options, totalWidth),
|
|
27664
|
+
'',
|
|
27665
|
+
...renderBox('Session', sessionLines, totalWidth, colors.yellow.bold),
|
|
27666
|
+
...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors.magenta.bold),
|
|
27667
|
+
...renderBox('Live output', visibleOutputLines, totalWidth, colors.green.bold),
|
|
27668
|
+
];
|
|
27669
|
+
if (options.errors.length > 0) {
|
|
27670
|
+
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
27671
|
+
frame.push(...renderBox('Errors', buildErrorDisplayLines(options.errors, bodyWidth), totalWidth, colors.red.bold));
|
|
27672
|
+
}
|
|
27673
|
+
frame.push(...renderBox('Controls', controlsBoxLines, totalWidth, colors.white.bold));
|
|
27674
|
+
return frame;
|
|
27675
|
+
}
|
|
27483
27676
|
/**
|
|
27484
|
-
* Builds the
|
|
27677
|
+
* Builds the header visual above the dashboard boxes.
|
|
27678
|
+
*
|
|
27679
|
+
* Shows the ASCII-art `--agent` avatar visual as soon as one is available. Until the avatar is
|
|
27680
|
+
* loaded - and when no avatar can be rendered at all - the header stays empty, because the
|
|
27681
|
+
* placeholder ASCII `PTBK.IO` wordmark only added terminal noise while nothing was rendered yet.
|
|
27682
|
+
*/
|
|
27683
|
+
function buildFrameHeaderVisual(options, totalWidth) {
|
|
27684
|
+
if (options.agentVisualLines !== undefined && options.agentVisualLines.length > 0) {
|
|
27685
|
+
return options.agentVisualLines.map((agentVisualLine) => centerAnsiText(agentVisualLine, totalWidth));
|
|
27686
|
+
}
|
|
27687
|
+
if (options.agentVisual !== undefined) {
|
|
27688
|
+
const agentVisualLines = options.agentVisual.renderFrame({
|
|
27689
|
+
animationTimeMs: options.animationTimeMs,
|
|
27690
|
+
});
|
|
27691
|
+
if (agentVisualLines.length > 0) {
|
|
27692
|
+
return agentVisualLines.map((agentVisualLine) => centerAnsiText(agentVisualLine, totalWidth));
|
|
27693
|
+
}
|
|
27694
|
+
}
|
|
27695
|
+
return [];
|
|
27696
|
+
}
|
|
27697
|
+
/**
|
|
27698
|
+
* Builds the structured session lines that combine state, runner, queue, and timing metadata.
|
|
27699
|
+
*/
|
|
27700
|
+
function buildSessionLines(options, totalWidth, pausePresentation) {
|
|
27701
|
+
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
27702
|
+
return buildSessionRows(options, bodyWidth, pausePresentation).map((sessionRow) => buildLabeledSessionLine(sessionRow.label, sessionRow.value, bodyWidth));
|
|
27703
|
+
}
|
|
27704
|
+
/**
|
|
27705
|
+
* Builds the session rows so the renderer can keep one consistent structure without duplicating labels.
|
|
27706
|
+
*/
|
|
27707
|
+
function buildSessionRows(options, bodyWidth, pausePresentation) {
|
|
27708
|
+
const runnerParts = [options.config.agentName || 'No agent selected'];
|
|
27709
|
+
if (options.config.modelName) {
|
|
27710
|
+
runnerParts.push(options.config.modelName);
|
|
27711
|
+
}
|
|
27712
|
+
if (options.config.thinkingLevel) {
|
|
27713
|
+
runnerParts.push(`thinking ${options.config.thinkingLevel}`);
|
|
27714
|
+
}
|
|
27715
|
+
const configurationRows = [
|
|
27716
|
+
...buildOptionalSessionRow('Context', options.config.context),
|
|
27717
|
+
...buildOptionalSessionRow('Server', options.config.serverUrl
|
|
27718
|
+
? buildTerminalUrlLink(options.config.serverUrl, bodyWidth - SESSION_LABEL_WIDTH - 1)
|
|
27719
|
+
: undefined),
|
|
27720
|
+
...buildOptionalSessionRow('Test', options.config.testCommand),
|
|
27721
|
+
];
|
|
27722
|
+
return [
|
|
27723
|
+
{
|
|
27724
|
+
label: 'State',
|
|
27725
|
+
value: `${pausePresentation.badge} ${pausePresentation.stateMessage}`,
|
|
27726
|
+
},
|
|
27727
|
+
{
|
|
27728
|
+
label: 'Runner',
|
|
27729
|
+
value: runnerParts.join(' · '),
|
|
27730
|
+
},
|
|
27731
|
+
...configurationRows,
|
|
27732
|
+
...buildScriptPathSessionRows(options.currentScriptPaths || [], bodyWidth),
|
|
27733
|
+
{
|
|
27734
|
+
label: 'This run',
|
|
27735
|
+
value: buildThisRunSummary(options.progress),
|
|
27736
|
+
},
|
|
27737
|
+
{
|
|
27738
|
+
label: 'Backlog',
|
|
27739
|
+
value: buildBacklogSummary(options.progress),
|
|
27740
|
+
},
|
|
27741
|
+
{
|
|
27742
|
+
label: 'Scope',
|
|
27743
|
+
value: buildScopeSummary(options.progress, options.config),
|
|
27744
|
+
},
|
|
27745
|
+
{
|
|
27746
|
+
label: 'Timing',
|
|
27747
|
+
value: buildTimingSummary(options.progress),
|
|
27748
|
+
},
|
|
27749
|
+
{
|
|
27750
|
+
label: 'Progress',
|
|
27751
|
+
value: buildProgressBar$1(options.progress.percentage, bodyWidth - SESSION_LABEL_WIDTH - 1, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
|
|
27752
|
+
},
|
|
27753
|
+
];
|
|
27754
|
+
}
|
|
27755
|
+
/**
|
|
27756
|
+
* Builds zero or one structured session row for optional metadata.
|
|
27757
|
+
*/
|
|
27758
|
+
function buildOptionalSessionRow(label, value) {
|
|
27759
|
+
if (!value) {
|
|
27760
|
+
return [];
|
|
27761
|
+
}
|
|
27762
|
+
return [{ label, value }];
|
|
27763
|
+
}
|
|
27764
|
+
/**
|
|
27765
|
+
* Builds the active-session summary shown in the session box.
|
|
27766
|
+
*/
|
|
27767
|
+
function buildThisRunSummary(progress) {
|
|
27768
|
+
if (progress.sessionTotal === 0) {
|
|
27769
|
+
return 'No runnable prompts in current scope';
|
|
27770
|
+
}
|
|
27771
|
+
return `Task ${progress.currentPromptIndex}/${progress.sessionTotal} · ${progress.sessionDone} done · ${progress.sessionRemaining} left`;
|
|
27772
|
+
}
|
|
27773
|
+
/**
|
|
27774
|
+
* Builds the backlog/filter summary shown in the session box.
|
|
27775
|
+
*/
|
|
27776
|
+
function buildBacklogSummary(progress) {
|
|
27777
|
+
const parts = [`Repo ${progress.totalPrompts} total`];
|
|
27778
|
+
if (progress.skippedPrompts > 0) {
|
|
27779
|
+
parts.push(`${formatPromptCount$1(progress.skippedPrompts)} outside priority scope`);
|
|
27780
|
+
}
|
|
27781
|
+
return parts.join(' · ');
|
|
27782
|
+
}
|
|
27783
|
+
/**
|
|
27784
|
+
* Builds the priority/write-order summary shown in the session box.
|
|
27785
|
+
*/
|
|
27786
|
+
function buildScopeSummary(progress, config) {
|
|
27787
|
+
var _a;
|
|
27788
|
+
const parts = [formatPriorityFilter((_a = config.priorityFilter) !== null && _a !== void 0 ? _a : {})];
|
|
27789
|
+
if (config.limit !== undefined) {
|
|
27790
|
+
parts.push(`Limit ${formatPromptRunCount(config.limit)}`);
|
|
27791
|
+
}
|
|
27792
|
+
if (progress.toBeWrittenPrompts > 0) {
|
|
27793
|
+
parts.push(`Write ${formatPromptCount$1(progress.toBeWrittenPrompts)} first`);
|
|
27794
|
+
}
|
|
27795
|
+
return parts.join(' · ');
|
|
27796
|
+
}
|
|
27797
|
+
/**
|
|
27798
|
+
* Builds the elapsed/estimate summary shown in the session box.
|
|
27799
|
+
*/
|
|
27800
|
+
function buildTimingSummary(progress) {
|
|
27801
|
+
return `Elapsed ${progress.elapsedText} · Total ${progress.estimatedTotalText} · ETA ${progress.estimatedLabel}`;
|
|
27802
|
+
}
|
|
27803
|
+
/**
|
|
27804
|
+
* Formats a prompt count with singular/plural wording.
|
|
27805
|
+
*/
|
|
27806
|
+
function formatPromptCount$1(count) {
|
|
27807
|
+
return `${count} prompt${count === 1 ? '' : 's'}`;
|
|
27808
|
+
}
|
|
27809
|
+
/**
|
|
27810
|
+
* Formats a configured successful prompt-run limit with singular/plural wording.
|
|
27811
|
+
*/
|
|
27812
|
+
function formatPromptRunCount(count) {
|
|
27813
|
+
return `${count} prompt run${count === 1 ? '' : 's'}`;
|
|
27814
|
+
}
|
|
27815
|
+
|
|
27816
|
+
/**
|
|
27817
|
+
* ANSI sequence that clears the current terminal row.
|
|
27818
|
+
*
|
|
27819
|
+
* @private internal constant of coder run UI
|
|
27820
|
+
*/
|
|
27821
|
+
const CLEAR_CURRENT_LINE = '\x1b[2K';
|
|
27822
|
+
/**
|
|
27823
|
+
* ANSI control character that moves the cursor to the start of its current row.
|
|
27824
|
+
*
|
|
27825
|
+
* @private internal constant of coder run UI
|
|
27826
|
+
*/
|
|
27827
|
+
const MOVE_CURSOR_TO_LINE_START = '\r';
|
|
27828
|
+
/**
|
|
27829
|
+
* Builds one atomic ANSI update that changes the displayed coder-run terminal frame.
|
|
27830
|
+
*
|
|
27831
|
+
* A change in frame height requires a full rewrite to reserve or clear rows correctly. Frames with
|
|
27832
|
+
* the same height only rewrite contiguous groups of changed rows, returning to the frame bottom
|
|
27833
|
+
* between groups so each group can be assembled into the same output payload safely.
|
|
27834
|
+
*
|
|
27835
|
+
* @returns One terminal output payload, or `undefined` when the frame is already up to date.
|
|
27485
27836
|
*
|
|
27486
27837
|
* @private internal utility of coder run UI
|
|
27487
27838
|
*/
|
|
27488
|
-
function
|
|
27489
|
-
const
|
|
27490
|
-
|
|
27491
|
-
|
|
27492
|
-
|
|
27493
|
-
|
|
27494
|
-
|
|
27495
|
-
|
|
27496
|
-
|
|
27497
|
-
|
|
27498
|
-
|
|
27499
|
-
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
27505
|
-
|
|
27506
|
-
|
|
27507
|
-
|
|
27508
|
-
|
|
27509
|
-
.
|
|
27510
|
-
|
|
27839
|
+
function buildCoderRunUiTerminalFrameUpdate(options) {
|
|
27840
|
+
const { previousFrameLines, nextFrameLines } = options;
|
|
27841
|
+
if (previousFrameLines.length === 0 && nextFrameLines.length === 0) {
|
|
27842
|
+
return undefined;
|
|
27843
|
+
}
|
|
27844
|
+
if (previousFrameLines.length === 0 || previousFrameLines.length !== nextFrameLines.length) {
|
|
27845
|
+
return buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
|
|
27846
|
+
}
|
|
27847
|
+
return buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
|
|
27848
|
+
}
|
|
27849
|
+
/**
|
|
27850
|
+
* Builds an ANSI update which completely rewrites the reserved terminal frame area.
|
|
27851
|
+
*
|
|
27852
|
+
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
27853
|
+
*/
|
|
27854
|
+
function buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
|
|
27855
|
+
var _a;
|
|
27856
|
+
const previousFrameLineCount = previousFrameLines.length;
|
|
27857
|
+
const lineCountToRewrite = Math.max(previousFrameLineCount, nextFrameLines.length);
|
|
27858
|
+
const outputParts = [];
|
|
27859
|
+
if (previousFrameLineCount > 1) {
|
|
27860
|
+
outputParts.push(moveCursorUp(previousFrameLineCount - 1));
|
|
27861
|
+
}
|
|
27862
|
+
for (let lineIndex = 0; lineIndex < lineCountToRewrite; lineIndex++) {
|
|
27863
|
+
outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, (_a = nextFrameLines[lineIndex]) !== null && _a !== void 0 ? _a : '');
|
|
27864
|
+
if (lineIndex < lineCountToRewrite - 1) {
|
|
27865
|
+
outputParts.push('\n');
|
|
27866
|
+
}
|
|
27867
|
+
}
|
|
27868
|
+
const clearedTrailingLineCount = lineCountToRewrite - nextFrameLines.length;
|
|
27869
|
+
if (clearedTrailingLineCount > 0) {
|
|
27870
|
+
outputParts.push(moveCursorUp(clearedTrailingLineCount));
|
|
27871
|
+
}
|
|
27872
|
+
outputParts.push(MOVE_CURSOR_TO_LINE_START);
|
|
27873
|
+
return outputParts.join('');
|
|
27874
|
+
}
|
|
27875
|
+
/**
|
|
27876
|
+
* Builds an ANSI update that rewrites only contiguous groups of rows which changed.
|
|
27877
|
+
*
|
|
27878
|
+
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
27879
|
+
*/
|
|
27880
|
+
function buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
|
|
27881
|
+
const outputParts = [];
|
|
27882
|
+
let lineIndex = 0;
|
|
27883
|
+
while (lineIndex < nextFrameLines.length) {
|
|
27884
|
+
if (previousFrameLines[lineIndex] === nextFrameLines[lineIndex]) {
|
|
27885
|
+
lineIndex++;
|
|
27886
|
+
continue;
|
|
27887
|
+
}
|
|
27888
|
+
const changedRangeStartLineIndex = lineIndex;
|
|
27889
|
+
while (lineIndex + 1 < nextFrameLines.length &&
|
|
27890
|
+
previousFrameLines[lineIndex + 1] !== nextFrameLines[lineIndex + 1]) {
|
|
27891
|
+
lineIndex++;
|
|
27892
|
+
}
|
|
27893
|
+
appendChangedCoderRunUiTerminalFrameRange({
|
|
27894
|
+
outputParts,
|
|
27895
|
+
frameLineCount: nextFrameLines.length,
|
|
27896
|
+
changedRangeStartLineIndex,
|
|
27897
|
+
changedRangeEndLineIndex: lineIndex,
|
|
27898
|
+
nextFrameLines,
|
|
27899
|
+
});
|
|
27900
|
+
lineIndex++;
|
|
27901
|
+
}
|
|
27902
|
+
return outputParts.length === 0 ? undefined : outputParts.join('');
|
|
27903
|
+
}
|
|
27904
|
+
/**
|
|
27905
|
+
* Appends the cursor movements and row rewrites for one contiguous changed frame range.
|
|
27906
|
+
*
|
|
27907
|
+
* @private helper of `buildChangedCoderRunUiTerminalFrameUpdate`
|
|
27908
|
+
*/
|
|
27909
|
+
function appendChangedCoderRunUiTerminalFrameRange(options) {
|
|
27910
|
+
const { outputParts, frameLineCount, changedRangeStartLineIndex, changedRangeEndLineIndex, nextFrameLines } = options;
|
|
27911
|
+
const linesUpFromFrameBottom = frameLineCount - 1 - changedRangeStartLineIndex;
|
|
27912
|
+
if (linesUpFromFrameBottom > 0) {
|
|
27913
|
+
outputParts.push(moveCursorUp(linesUpFromFrameBottom));
|
|
27914
|
+
}
|
|
27915
|
+
for (let lineIndex = changedRangeStartLineIndex; lineIndex <= changedRangeEndLineIndex; lineIndex++) {
|
|
27916
|
+
outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, nextFrameLines[lineIndex]);
|
|
27917
|
+
if (lineIndex < changedRangeEndLineIndex) {
|
|
27918
|
+
outputParts.push('\n');
|
|
27919
|
+
}
|
|
27920
|
+
}
|
|
27921
|
+
const linesDownToFrameBottom = frameLineCount - 1 - changedRangeEndLineIndex;
|
|
27922
|
+
if (linesDownToFrameBottom > 0) {
|
|
27923
|
+
outputParts.push(moveCursorDown(linesDownToFrameBottom));
|
|
27924
|
+
}
|
|
27925
|
+
outputParts.push(MOVE_CURSOR_TO_LINE_START);
|
|
27926
|
+
}
|
|
27927
|
+
/**
|
|
27928
|
+
* Builds an ANSI sequence which moves the terminal cursor up by a positive number of rows.
|
|
27929
|
+
*
|
|
27930
|
+
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
27931
|
+
*/
|
|
27932
|
+
function moveCursorUp(lineCount) {
|
|
27933
|
+
return `\x1b[${lineCount}A`;
|
|
27934
|
+
}
|
|
27935
|
+
/**
|
|
27936
|
+
* Builds an ANSI sequence which moves the terminal cursor down by a positive number of rows.
|
|
27937
|
+
*
|
|
27938
|
+
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
27939
|
+
*/
|
|
27940
|
+
function moveCursorDown(lineCount) {
|
|
27941
|
+
return `\x1b[${lineCount}B`;
|
|
27942
|
+
}
|
|
27943
|
+
|
|
27944
|
+
/**
|
|
27945
|
+
* Calendar formats used when displaying the estimated completion time.
|
|
27946
|
+
*
|
|
27947
|
+
* Uses 24-hour format (`HH:mm`) so estimates are unambiguous (for example `17:30` instead of `5:30`).
|
|
27948
|
+
*/
|
|
27949
|
+
const ESTIMATED_DONE_CALENDAR_FORMATS = {
|
|
27950
|
+
sameDay: '[Today] HH:mm',
|
|
27951
|
+
nextDay: '[Tomorrow] HH:mm',
|
|
27952
|
+
nextWeek: 'dddd HH:mm',
|
|
27953
|
+
lastDay: '[Yesterday] HH:mm',
|
|
27954
|
+
lastWeek: 'dddd HH:mm',
|
|
27955
|
+
sameElse: 'MMM D HH:mm',
|
|
27956
|
+
};
|
|
27957
|
+
/**
|
|
27958
|
+
* Formats a duration into a compact string such as "3h 12m" or "45s".
|
|
27959
|
+
*/
|
|
27960
|
+
function formatDurationBrief(duration) {
|
|
27961
|
+
const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
|
|
27962
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
27963
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
27964
|
+
const seconds = totalSeconds % 60;
|
|
27965
|
+
const parts = [];
|
|
27966
|
+
if (hours > 0) {
|
|
27967
|
+
parts.push(`${hours}h`);
|
|
27968
|
+
}
|
|
27969
|
+
if (minutes > 0) {
|
|
27970
|
+
parts.push(`${minutes}m`);
|
|
27971
|
+
}
|
|
27972
|
+
if (!parts.length && seconds > 0) {
|
|
27973
|
+
parts.push(`${seconds}s`);
|
|
27974
|
+
}
|
|
27975
|
+
if (!parts.length) {
|
|
27976
|
+
parts.push('0s');
|
|
27977
|
+
}
|
|
27978
|
+
return parts.join(' ');
|
|
27979
|
+
}
|
|
27980
|
+
|
|
27981
|
+
/**
|
|
27982
|
+
* Builds a session-scoped progress snapshot from prompt stats and elapsed active time.
|
|
27983
|
+
*
|
|
27984
|
+
* When the current session has not finished any prompt yet but a `cachedAveragePromptDurationMs` is
|
|
27985
|
+
* provided (loaded from the per-config estimate cache stored in the temp folder), the snapshot uses
|
|
27986
|
+
* that historical average to project the remaining work instead of waiting for the first completion.
|
|
27987
|
+
*/
|
|
27988
|
+
function buildCoderRunProgressSnapshot(stats, elapsedDuration, initialDone, cachedAveragePromptDurationMs, runLimit) {
|
|
27989
|
+
const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
|
|
27990
|
+
const completedSessionPrompts = Math.max(0, stats.done - initialDone);
|
|
27991
|
+
const availableSessionTotal = completedSessionPrompts + stats.forAgent;
|
|
27992
|
+
const sessionTotal = resolveSessionTotal(availableSessionTotal, runLimit);
|
|
27993
|
+
const sessionDone = Math.min(completedSessionPrompts, sessionTotal);
|
|
27994
|
+
const sessionRemaining = Math.max(0, sessionTotal - sessionDone);
|
|
27995
|
+
const currentPromptIndex = sessionTotal > 0 ? Math.min(sessionDone + 1, sessionTotal) : 0;
|
|
27996
|
+
const percentage = sessionTotal > 0 ? Math.round((sessionDone / sessionTotal) * 100) : 0;
|
|
27997
|
+
const elapsedText = formatDurationBrief(elapsedDuration);
|
|
27998
|
+
const elapsedMs = elapsedDuration.asMilliseconds();
|
|
27999
|
+
let estimatedTotalText = 'estimating...';
|
|
28000
|
+
let estimatedLabel = 'after first completion';
|
|
28001
|
+
let isEstimatedTotalKnown = false;
|
|
28002
|
+
let estimatedTotalMs;
|
|
28003
|
+
if (sessionTotal > 0 && sessionDone > 0) {
|
|
28004
|
+
estimatedTotalMs = (elapsedMs * sessionTotal) / sessionDone;
|
|
28005
|
+
}
|
|
28006
|
+
else if (sessionTotal > 0 &&
|
|
28007
|
+
cachedAveragePromptDurationMs !== undefined &&
|
|
28008
|
+
cachedAveragePromptDurationMs > 0) {
|
|
28009
|
+
const projectedRemainingMs = sessionRemaining * cachedAveragePromptDurationMs;
|
|
28010
|
+
estimatedTotalMs = elapsedMs + projectedRemainingMs;
|
|
28011
|
+
}
|
|
28012
|
+
if (estimatedTotalMs !== undefined) {
|
|
28013
|
+
const estimatedRemainingMs = Math.max(0, estimatedTotalMs - elapsedMs);
|
|
28014
|
+
const estimatedTotalDuration = moment.duration(estimatedTotalMs);
|
|
28015
|
+
const estimatedCompletion = moment().add(estimatedRemainingMs, 'milliseconds');
|
|
28016
|
+
estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
|
|
28017
|
+
estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
|
|
28018
|
+
isEstimatedTotalKnown = true;
|
|
28019
|
+
}
|
|
28020
|
+
return {
|
|
28021
|
+
totalPrompts,
|
|
28022
|
+
sessionDone,
|
|
28023
|
+
sessionRemaining,
|
|
28024
|
+
sessionTotal,
|
|
28025
|
+
currentPromptIndex,
|
|
28026
|
+
skippedPrompts: stats.outsidePriorityRange,
|
|
28027
|
+
toBeWrittenPrompts: stats.toBeWritten,
|
|
28028
|
+
percentage,
|
|
28029
|
+
elapsedText,
|
|
28030
|
+
estimatedTotalText,
|
|
28031
|
+
estimatedLabel,
|
|
28032
|
+
isEstimatedTotalKnown,
|
|
28033
|
+
};
|
|
28034
|
+
}
|
|
28035
|
+
/**
|
|
28036
|
+
* Resolves the number of prompt runs represented by the current session progress.
|
|
28037
|
+
*/
|
|
28038
|
+
function resolveSessionTotal(availableSessionTotal, runLimit) {
|
|
28039
|
+
if (runLimit === undefined) {
|
|
28040
|
+
return availableSessionTotal;
|
|
28041
|
+
}
|
|
28042
|
+
return Math.min(availableSessionTotal, runLimit);
|
|
28043
|
+
}
|
|
28044
|
+
|
|
28045
|
+
/**
|
|
28046
|
+
* Tracks active coder-run time while excluding pauses and user-confirmation waits.
|
|
28047
|
+
*/
|
|
28048
|
+
class CoderRunTimer {
|
|
28049
|
+
/**
|
|
28050
|
+
* Creates a timer anchored at the provided start time.
|
|
28051
|
+
*/
|
|
28052
|
+
constructor(startTime, isPausedInitially = false) {
|
|
28053
|
+
this.startTime = startTime;
|
|
28054
|
+
/**
|
|
28055
|
+
* Total milliseconds spent in paused state across the run.
|
|
28056
|
+
*/
|
|
28057
|
+
this.pausedMs = 0;
|
|
28058
|
+
if (isPausedInitially) {
|
|
28059
|
+
this.pausedSince = startTime.clone();
|
|
28060
|
+
}
|
|
28061
|
+
}
|
|
28062
|
+
/**
|
|
28063
|
+
* Pauses active-time tracking until `resume()` is called.
|
|
28064
|
+
*/
|
|
28065
|
+
pause() {
|
|
28066
|
+
if (this.pausedSince === undefined) {
|
|
28067
|
+
this.pausedSince = moment();
|
|
28068
|
+
}
|
|
28069
|
+
}
|
|
28070
|
+
/**
|
|
28071
|
+
* Resumes active-time tracking after a pause.
|
|
28072
|
+
*/
|
|
28073
|
+
resume() {
|
|
28074
|
+
if (this.pausedSince !== undefined) {
|
|
28075
|
+
this.pausedMs += moment().diff(this.pausedSince);
|
|
28076
|
+
this.pausedSince = undefined;
|
|
28077
|
+
}
|
|
28078
|
+
}
|
|
28079
|
+
/**
|
|
28080
|
+
* Returns the currently accumulated active duration.
|
|
28081
|
+
*/
|
|
28082
|
+
getElapsedDuration() {
|
|
28083
|
+
const wallMs = moment().diff(this.startTime);
|
|
28084
|
+
const currentPauseMs = this.pausedSince !== undefined ? moment().diff(this.pausedSince) : 0;
|
|
28085
|
+
return moment.duration(Math.max(0, wallMs - this.pausedMs - currentPauseMs));
|
|
28086
|
+
}
|
|
28087
|
+
}
|
|
28088
|
+
|
|
28089
|
+
/**
|
|
28090
|
+
* Maximum number of agent output lines kept in the scrolling output area.
|
|
28091
|
+
*
|
|
28092
|
+
* @private internal constant of coder run UI
|
|
28093
|
+
*/
|
|
28094
|
+
const MAX_AGENT_OUTPUT_LINES = 12;
|
|
28095
|
+
/**
|
|
28096
|
+
* Reactive state manager for the coder run terminal UI.
|
|
28097
|
+
*
|
|
28098
|
+
* Holds all data the Ink components need and emits `'change'` events
|
|
28099
|
+
* whenever any property is updated so the UI can re-render.
|
|
28100
|
+
*
|
|
28101
|
+
* @private internal utility of coder run UI
|
|
28102
|
+
*/
|
|
28103
|
+
class CoderRunUiState extends EventEmitter {
|
|
28104
|
+
constructor(startTime) {
|
|
28105
|
+
super();
|
|
28106
|
+
this.config = { agentName: '' };
|
|
28107
|
+
this.currentPromptLabel = '';
|
|
28108
|
+
this.currentScriptPaths = [];
|
|
28109
|
+
this.currentAttempt = 1;
|
|
28110
|
+
this.maxAttempts = 3;
|
|
28111
|
+
this.detailLines = [];
|
|
28112
|
+
this.messagePreviewLines = [];
|
|
28113
|
+
this.messagePreviewSections = [];
|
|
28114
|
+
this.agentStatusLines = [];
|
|
28115
|
+
this.agentStatusTableRows = [];
|
|
28116
|
+
this.agentOutputLines = [];
|
|
28117
|
+
this.phase = 'initializing';
|
|
28118
|
+
this.statusMessage = 'Initializing...';
|
|
28119
|
+
this.errors = [];
|
|
28120
|
+
this.stats = { done: 0, forAgent: 0, outsidePriorityRange: 0, toBeWritten: 0 };
|
|
28121
|
+
// Match the plain CLI progress header and count active run time from startup.
|
|
28122
|
+
// Explicit waits/pauses are excluded later through `pauseTimer()` / `resumeTimer()`.
|
|
28123
|
+
this.timer = new CoderRunTimer(startTime);
|
|
28124
|
+
}
|
|
28125
|
+
/**
|
|
28126
|
+
* Pauses the elapsed timer (e.g. while waiting for user input or paused state).
|
|
28127
|
+
*/
|
|
28128
|
+
pauseTimer() {
|
|
28129
|
+
this.timer.pause();
|
|
28130
|
+
this.emitChange();
|
|
28131
|
+
}
|
|
28132
|
+
/**
|
|
28133
|
+
* Resumes the elapsed timer after a pause.
|
|
28134
|
+
*/
|
|
28135
|
+
resumeTimer() {
|
|
28136
|
+
this.timer.resume();
|
|
28137
|
+
this.emitChange();
|
|
28138
|
+
}
|
|
28139
|
+
/**
|
|
28140
|
+
* Replaces the configuration shown in the UI header.
|
|
28141
|
+
*/
|
|
28142
|
+
setConfig(config) {
|
|
28143
|
+
this.config = config;
|
|
28144
|
+
this.emitChange();
|
|
28145
|
+
}
|
|
28146
|
+
/**
|
|
28147
|
+
* Replaces the ASCII-art agent visual renderer shown above the dashboard boxes.
|
|
28148
|
+
*/
|
|
28149
|
+
setAgentVisual(agentVisual) {
|
|
28150
|
+
this.agentVisual = agentVisual;
|
|
28151
|
+
this.emitChange();
|
|
28152
|
+
}
|
|
28153
|
+
/**
|
|
28154
|
+
* Feeds new prompt statistics from the main loop so the progress bar updates.
|
|
28155
|
+
*/
|
|
28156
|
+
updateProgress(stats) {
|
|
28157
|
+
if (this.initialDone === undefined && (stats.done > 0 || stats.forAgent > 0 || stats.toBeWritten > 0)) {
|
|
28158
|
+
this.initialDone = stats.done;
|
|
28159
|
+
}
|
|
28160
|
+
this.stats = stats;
|
|
28161
|
+
this.emitChange();
|
|
28162
|
+
}
|
|
28163
|
+
/**
|
|
28164
|
+
* Sets the cached average prompt duration (in milliseconds) loaded from the temp-folder estimate cache.
|
|
28165
|
+
*
|
|
28166
|
+
* Used to project completion estimates before the first prompt of the current session finishes.
|
|
28167
|
+
*/
|
|
28168
|
+
setCachedAveragePromptDurationMs(cachedAveragePromptDurationMs) {
|
|
28169
|
+
this.cachedAveragePromptDurationMs = cachedAveragePromptDurationMs;
|
|
28170
|
+
this.emitChange();
|
|
28171
|
+
}
|
|
28172
|
+
/**
|
|
28173
|
+
* Computes a progress snapshot on demand so elapsed time ticks with periodic re-renders.
|
|
28174
|
+
*/
|
|
28175
|
+
getProgress() {
|
|
28176
|
+
var _a;
|
|
28177
|
+
return buildCoderRunProgressSnapshot(this.stats, this.timer.getElapsedDuration(), (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done, this.cachedAveragePromptDurationMs, this.config.limit);
|
|
28178
|
+
}
|
|
28179
|
+
/**
|
|
28180
|
+
* Sets the label of the prompt currently being processed and resets per-prompt state.
|
|
28181
|
+
*/
|
|
28182
|
+
setCurrentPrompt(label) {
|
|
28183
|
+
this.currentPromptLabel = label;
|
|
28184
|
+
this.currentScriptPaths = [];
|
|
28185
|
+
this.detailLines = [];
|
|
28186
|
+
this.messagePreviewLines = [];
|
|
28187
|
+
this.messagePreviewSections = [];
|
|
28188
|
+
this.pendingEnterLabel = undefined;
|
|
28189
|
+
this.agentOutputLines = [];
|
|
28190
|
+
this.currentAttempt = 1;
|
|
28191
|
+
this.emitChange();
|
|
28192
|
+
}
|
|
28193
|
+
/**
|
|
28194
|
+
* Sets the temporary shell script currently used by the active prompt.
|
|
28195
|
+
*/
|
|
28196
|
+
setCurrentScriptPath(scriptPath) {
|
|
28197
|
+
this.currentScriptPaths = scriptPath ? [scriptPath] : [];
|
|
28198
|
+
this.emitChange();
|
|
28199
|
+
}
|
|
28200
|
+
/**
|
|
28201
|
+
* Sets multiple temporary shell scripts used by a shared multi-agent session.
|
|
28202
|
+
*/
|
|
28203
|
+
setCurrentScriptPaths(scriptPaths) {
|
|
28204
|
+
this.currentScriptPaths = [...scriptPaths];
|
|
28205
|
+
this.emitChange();
|
|
28206
|
+
}
|
|
28207
|
+
/**
|
|
28208
|
+
* Updates the current retry attempt number.
|
|
28209
|
+
*/
|
|
28210
|
+
setAttempt(attempt) {
|
|
28211
|
+
this.currentAttempt = attempt;
|
|
28212
|
+
this.emitChange();
|
|
28213
|
+
}
|
|
28214
|
+
/**
|
|
28215
|
+
* Appends raw agent output text, keeping only the last `MAX_AGENT_OUTPUT_LINES`.
|
|
28216
|
+
*/
|
|
28217
|
+
addAgentOutput(text) {
|
|
28218
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim() !== '');
|
|
28219
|
+
if (lines.length === 0) {
|
|
28220
|
+
return;
|
|
28221
|
+
}
|
|
28222
|
+
this.agentOutputLines.push(...lines);
|
|
28223
|
+
if (this.agentOutputLines.length > MAX_AGENT_OUTPUT_LINES) {
|
|
28224
|
+
this.agentOutputLines = this.agentOutputLines.slice(-MAX_AGENT_OUTPUT_LINES);
|
|
28225
|
+
}
|
|
28226
|
+
this.emitChange();
|
|
28227
|
+
}
|
|
28228
|
+
/**
|
|
28229
|
+
* Transitions the execution phase shown in the UI.
|
|
28230
|
+
*/
|
|
28231
|
+
setPhase(phase) {
|
|
28232
|
+
this.phase = phase;
|
|
28233
|
+
this.emitChange();
|
|
28234
|
+
}
|
|
28235
|
+
/**
|
|
28236
|
+
* Updates the status message line beneath the current prompt label.
|
|
28237
|
+
*/
|
|
28238
|
+
setStatusMessage(message) {
|
|
28239
|
+
this.statusMessage = message;
|
|
28240
|
+
this.emitChange();
|
|
28241
|
+
}
|
|
28242
|
+
/**
|
|
28243
|
+
* Replaces the contextual detail lines shown beneath the current prompt status.
|
|
28244
|
+
*/
|
|
28245
|
+
setDetailLines(detailLines) {
|
|
28246
|
+
this.detailLines = detailLines.filter((detailLine) => detailLine.trim() !== '');
|
|
28247
|
+
this.emitChange();
|
|
28248
|
+
}
|
|
28249
|
+
/**
|
|
28250
|
+
* Replaces the exact user-message preview lines shown in agent-specific panels.
|
|
28251
|
+
*/
|
|
28252
|
+
setMessagePreviewLines(messagePreviewLines) {
|
|
28253
|
+
this.messagePreviewLines = [...messagePreviewLines];
|
|
28254
|
+
this.emitChange();
|
|
28255
|
+
}
|
|
28256
|
+
/**
|
|
28257
|
+
* Replaces the structured user-message panels shown in agent-specific dashboards.
|
|
28258
|
+
*/
|
|
28259
|
+
setMessagePreviewSections(messagePreviewSections) {
|
|
28260
|
+
this.messagePreviewSections = [...messagePreviewSections];
|
|
28261
|
+
this.emitChange();
|
|
28262
|
+
}
|
|
28263
|
+
/**
|
|
28264
|
+
* Replaces agent status rows shown by agent-specific terminal frames.
|
|
28265
|
+
*/
|
|
28266
|
+
setAgentStatusLines(agentStatusLines) {
|
|
28267
|
+
this.agentStatusLines = [...agentStatusLines];
|
|
28268
|
+
this.emitChange();
|
|
28269
|
+
}
|
|
28270
|
+
/**
|
|
28271
|
+
* Replaces structured agent status rows shown by agent-specific terminal frames.
|
|
28272
|
+
*/
|
|
28273
|
+
setAgentStatusTableRows(agentStatusTableRows) {
|
|
28274
|
+
this.agentStatusTableRows = [...agentStatusTableRows];
|
|
28275
|
+
this.emitChange();
|
|
28276
|
+
}
|
|
28277
|
+
/**
|
|
28278
|
+
* Sets or clears the Enter-key action label shown in the controls panel.
|
|
28279
|
+
*/
|
|
28280
|
+
setPendingEnterLabel(pendingEnterLabel) {
|
|
28281
|
+
this.pendingEnterLabel = pendingEnterLabel;
|
|
28282
|
+
this.emitChange();
|
|
28283
|
+
}
|
|
28284
|
+
/**
|
|
28285
|
+
* Shows the answer of the runner to one pressed control key, or clears the currently shown one.
|
|
28286
|
+
*
|
|
28287
|
+
* Pressing one control twice in a row can produce the very same answer, for example when there is
|
|
28288
|
+
* nothing to skip. The repeat count is therefore tracked here, so that even such a press changes the
|
|
28289
|
+
* rendered frame and the user always sees that the key arrived.
|
|
28290
|
+
*/
|
|
28291
|
+
setControlFeedback(controlFeedback) {
|
|
28292
|
+
this.controlFeedback =
|
|
28293
|
+
controlFeedback === undefined
|
|
28294
|
+
? undefined
|
|
28295
|
+
: { ...controlFeedback, repeatCount: this.countControlFeedbackRepeat(controlFeedback) };
|
|
28296
|
+
this.emitChange();
|
|
28297
|
+
}
|
|
28298
|
+
/**
|
|
28299
|
+
* Appends an error message to the error list shown in the UI.
|
|
28300
|
+
*/
|
|
28301
|
+
addError(errorMessage) {
|
|
28302
|
+
this.errors.push(errorMessage);
|
|
28303
|
+
this.emitChange();
|
|
28304
|
+
}
|
|
28305
|
+
/**
|
|
28306
|
+
* Counts how many times the shown answer repeats the previously shown one.
|
|
28307
|
+
*/
|
|
28308
|
+
countControlFeedbackRepeat(controlFeedback) {
|
|
28309
|
+
const previousControlFeedback = this.controlFeedback;
|
|
28310
|
+
const isSameControlFeedback = previousControlFeedback !== undefined &&
|
|
28311
|
+
previousControlFeedback.controlKey === controlFeedback.controlKey &&
|
|
28312
|
+
previousControlFeedback.message === controlFeedback.message;
|
|
28313
|
+
return isSameControlFeedback ? previousControlFeedback.repeatCount + 1 : 1;
|
|
28314
|
+
}
|
|
28315
|
+
emitChange() {
|
|
28316
|
+
this.emit('change');
|
|
28317
|
+
}
|
|
27511
28318
|
}
|
|
27512
28319
|
|
|
27513
28320
|
/**
|
|
@@ -28177,807 +28984,139 @@ const TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_HEIGHT = DEFAULT_AVATAR_SIZE;
|
|
|
28177
28984
|
* @param options Agent source, canvas factory, and optional terminal color settings.
|
|
28178
28985
|
* @returns Runtime terminal avatar visual renderer.
|
|
28179
28986
|
*
|
|
28180
|
-
* @private shared helper for terminal avatar rendering
|
|
28181
|
-
*/
|
|
28182
|
-
function createTerminalAgentAvatarVisual(options) {
|
|
28183
|
-
const renderInputs = createTerminalAgentAvatarVisualRenderInputs(options.agentSource, options.defaultAvatarVisualId || DEFAULT_AGENT_AVATAR_VISUAL_ID);
|
|
28184
|
-
return {
|
|
28185
|
-
isAnimated: renderInputs.resolvedAvatarRenderDefinition.avatarVisual.isAnimated,
|
|
28186
|
-
renderFrame({ animationTimeMs }) {
|
|
28187
|
-
return renderTerminalAgentAvatarVisualFrame({
|
|
28188
|
-
...renderInputs,
|
|
28189
|
-
animationTimeMs,
|
|
28190
|
-
colorDepth: options.colorDepth,
|
|
28191
|
-
createCanvas: options.createCanvas,
|
|
28192
|
-
});
|
|
28193
|
-
},
|
|
28194
|
-
};
|
|
28195
|
-
}
|
|
28196
|
-
/**
|
|
28197
|
-
* Creates stable avatar render inputs from one agent source.
|
|
28198
|
-
*
|
|
28199
|
-
* @private shared helper for terminal avatar rendering
|
|
28200
|
-
*/
|
|
28201
|
-
function createTerminalAgentAvatarVisualRenderInputs(agentSource, defaultAvatarVisualId) {
|
|
28202
|
-
const agentBasicInformation = parseAgentSource(agentSource);
|
|
28203
|
-
const avatarDefinition = createAvatarDefinitionFromAgentBasicInformation(agentBasicInformation);
|
|
28204
|
-
const avatarVisualId = resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId);
|
|
28205
|
-
const resolvedAvatarRenderDefinition = resolveAvatarRenderDefinition({
|
|
28206
|
-
avatarDefinition,
|
|
28207
|
-
visualId: avatarVisualId,
|
|
28208
|
-
surface: 'transparent',
|
|
28209
|
-
});
|
|
28210
|
-
return {
|
|
28211
|
-
avatarDefinition,
|
|
28212
|
-
avatarVisualId,
|
|
28213
|
-
resolvedAvatarRenderDefinition,
|
|
28214
|
-
};
|
|
28215
|
-
}
|
|
28216
|
-
/**
|
|
28217
|
-
* Resolves the terminal avatar visual id from already parsed agent information.
|
|
28218
|
-
*
|
|
28219
|
-
* @private shared helper for terminal avatar rendering
|
|
28220
|
-
*/
|
|
28221
|
-
function resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId) {
|
|
28222
|
-
return resolveAgentAvatarVisualId(agentBasicInformation, resolveAvatarVisualId(agentBasicInformation.meta.visual) || defaultAvatarVisualId);
|
|
28223
|
-
}
|
|
28224
|
-
/**
|
|
28225
|
-
* Renders one terminal avatar frame through the shared avatar-to-ASCII pipeline.
|
|
28226
|
-
*
|
|
28227
|
-
* Visuals which are made of characters themselves, for example `AsciiOctopus`, paint the terminal
|
|
28228
|
-
* character grid directly, because rasterizing them into half-block ASCII art averages every glyph
|
|
28229
|
-
* away and makes them indistinguishable from the other blob-shaped visuals.
|
|
28230
|
-
*
|
|
28231
|
-
* @private shared helper for terminal avatar rendering
|
|
28232
|
-
*/
|
|
28233
|
-
function renderTerminalAgentAvatarVisualFrame(options) {
|
|
28234
|
-
const terminalTextLines = renderAvatarVisualTerminalTextLines({
|
|
28235
|
-
avatarDefinition: options.avatarDefinition,
|
|
28236
|
-
visualId: options.avatarVisualId,
|
|
28237
|
-
surface: 'transparent',
|
|
28238
|
-
columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
|
|
28239
|
-
rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
|
|
28240
|
-
colorDepth: options.colorDepth,
|
|
28241
|
-
timeMs: options.animationTimeMs,
|
|
28242
|
-
resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
|
|
28243
|
-
});
|
|
28244
|
-
if (terminalTextLines !== null) {
|
|
28245
|
-
return terminalTextLines;
|
|
28246
|
-
}
|
|
28247
|
-
if (options.createCanvas === undefined) {
|
|
28248
|
-
// Note: A pixel-based visual cannot be rasterized without a canvas, so the caller keeps its default banner
|
|
28249
|
-
return [];
|
|
28250
|
-
}
|
|
28251
|
-
return renderAvatarVisualAsciiArt({
|
|
28252
|
-
avatarDefinition: options.avatarDefinition,
|
|
28253
|
-
visualId: options.avatarVisualId,
|
|
28254
|
-
surface: 'transparent',
|
|
28255
|
-
columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
|
|
28256
|
-
rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
|
|
28257
|
-
canvasWidth: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_WIDTH,
|
|
28258
|
-
canvasHeight: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_HEIGHT,
|
|
28259
|
-
colorDepth: options.colorDepth,
|
|
28260
|
-
timeMs: options.animationTimeMs,
|
|
28261
|
-
createCanvas: options.createCanvas,
|
|
28262
|
-
resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
|
|
28263
|
-
});
|
|
28264
|
-
}
|
|
28265
|
-
|
|
28266
|
-
/**
|
|
28267
|
-
* Refresh cadence used only while the rich coder UI needs animated updates.
|
|
28268
|
-
*
|
|
28269
|
-
* @private internal constant of coder run UI
|
|
28270
|
-
*/
|
|
28271
|
-
const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = TERMINAL_AGENT_AVATAR_VISUAL_REFRESH_INTERVAL_MS;
|
|
28272
|
-
/**
|
|
28273
|
-
* Phases that still benefit from automatic refreshes because the frame can change
|
|
28274
|
-
* over time even without new runner output.
|
|
28275
|
-
*
|
|
28276
|
-
* @private internal constant of coder run UI
|
|
28277
|
-
*/
|
|
28278
|
-
const AUTO_REFRESH_PHASES = ['initializing', 'loading', 'running', 'verifying'];
|
|
28279
|
-
/**
|
|
28280
|
-
* Phases where an animated `--agent` visual should keep rendering like it does on the web.
|
|
28281
|
-
*
|
|
28282
|
-
* @private internal constant of coder run UI
|
|
28283
|
-
*/
|
|
28284
|
-
const AGENT_VISUAL_AUTO_REFRESH_PHASES = [...AUTO_REFRESH_PHASES, 'waiting'];
|
|
28285
|
-
/**
|
|
28286
|
-
* Returns whether the rich coder UI should keep animating on its own.
|
|
28287
|
-
*
|
|
28288
|
-
* @private internal utility of coder run UI
|
|
28289
|
-
*/
|
|
28290
|
-
function isCoderRunUiAutoRefreshing(phase, pauseState, isAgentVisualAnimated = false) {
|
|
28291
|
-
// `PAUSING` still means the current task is winding down, so keep active
|
|
28292
|
-
// animations/timers running until the runner reaches the fully paused state.
|
|
28293
|
-
if (pauseState === 'PAUSED') {
|
|
28294
|
-
return false;
|
|
28295
|
-
}
|
|
28296
|
-
if (isAgentVisualAnimated) {
|
|
28297
|
-
return AGENT_VISUAL_AUTO_REFRESH_PHASES.includes(phase);
|
|
28298
|
-
}
|
|
28299
|
-
return AUTO_REFRESH_PHASES.includes(phase);
|
|
28300
|
-
}
|
|
28301
|
-
/**
|
|
28302
|
-
* Returns the automatic refresh interval for the current UI state.
|
|
28303
|
-
*
|
|
28304
|
-
* Waiting, paused, and completed states return `undefined` so the rich UI stays
|
|
28305
|
-
* perfectly still until actual state changes arrive.
|
|
28306
|
-
*
|
|
28307
|
-
* @private internal utility of coder run UI
|
|
28308
|
-
*/
|
|
28309
|
-
function getCoderRunUiAutoRefreshInterval(phase, pauseState, isAgentVisualAnimated = false) {
|
|
28310
|
-
return isCoderRunUiAutoRefreshing(phase, pauseState, isAgentVisualAnimated)
|
|
28311
|
-
? ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS
|
|
28312
|
-
: undefined;
|
|
28313
|
-
}
|
|
28314
|
-
|
|
28315
|
-
/**
|
|
28316
|
-
* Minimum width used for the rich coder-run frame.
|
|
28317
|
-
*/
|
|
28318
|
-
const MIN_FRAME_WIDTH = 56;
|
|
28319
|
-
/**
|
|
28320
|
-
* Maximum width used for the rich coder-run frame.
|
|
28321
|
-
*/
|
|
28322
|
-
const MAX_FRAME_WIDTH = 96;
|
|
28323
|
-
/**
|
|
28324
|
-
* Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
|
|
28325
|
-
*/
|
|
28326
|
-
function buildCoderRunUiFrame(options) {
|
|
28327
|
-
const totalWidth = Math.max(MIN_FRAME_WIDTH, Math.min(options.terminalWidth, MAX_FRAME_WIDTH));
|
|
28328
|
-
const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
|
|
28329
|
-
const promptStatusPrefix = isPromptActive ? `${colors.yellow(`${options.spinner} `)}` : '';
|
|
28330
|
-
const octopusAnimationFrame = isCoderRunUiAutoRefreshing(options.phase, options.pauseState)
|
|
28331
|
-
? options.animationFrame
|
|
28332
|
-
: 0;
|
|
28333
|
-
const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.pauseTargetLabel, options.statusMessage);
|
|
28334
|
-
const sessionLines = buildSessionLines(options, totalWidth, pausePresentation);
|
|
28335
|
-
const currentTaskLines = options.currentPromptLabel
|
|
28336
|
-
? [
|
|
28337
|
-
`${promptStatusPrefix}${colors.bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
|
|
28338
|
-
`Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
|
|
28339
|
-
...options.detailLines.map((detailLine) => `• ${detailLine}`),
|
|
28340
|
-
]
|
|
28341
|
-
: [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
|
|
28342
|
-
const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
|
|
28343
|
-
const controls = buildCoderRunControlPills({
|
|
28344
|
-
phase: options.phase,
|
|
28345
|
-
pauseControl: pausePresentation.pauseControl,
|
|
28346
|
-
pendingEnterLabel: options.pendingEnterLabel,
|
|
28347
|
-
isEndAfterCurrentPromptRequested: options.isEndAfterCurrentPromptRequested,
|
|
28348
|
-
sessionTotal: options.progress.sessionTotal,
|
|
28349
|
-
}).join(' ');
|
|
28350
|
-
const frame = [
|
|
28351
|
-
...buildFrameHeaderVisual(options, totalWidth, octopusAnimationFrame),
|
|
28352
|
-
'',
|
|
28353
|
-
...renderBox('Session', sessionLines, totalWidth, colors.yellow.bold),
|
|
28354
|
-
...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors.magenta.bold),
|
|
28355
|
-
...renderBox('Live output', visibleOutputLines, totalWidth, colors.green.bold),
|
|
28356
|
-
];
|
|
28357
|
-
if (options.errors.length > 0) {
|
|
28358
|
-
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
28359
|
-
frame.push(...renderBox('Errors', buildErrorDisplayLines(options.errors, bodyWidth), totalWidth, colors.red.bold));
|
|
28360
|
-
}
|
|
28361
|
-
frame.push(...renderBox('Controls', [controls], totalWidth, colors.white.bold));
|
|
28362
|
-
return frame;
|
|
28363
|
-
}
|
|
28364
|
-
/**
|
|
28365
|
-
* Builds the header visual above the dashboard boxes.
|
|
28366
|
-
*
|
|
28367
|
-
* Shows the ASCII-art `--agent` avatar visual when one is available and
|
|
28368
|
-
* falls back to the default brand banner otherwise.
|
|
28369
|
-
*/
|
|
28370
|
-
function buildFrameHeaderVisual(options, totalWidth, octopusAnimationFrame) {
|
|
28371
|
-
if (options.agentVisualLines !== undefined && options.agentVisualLines.length > 0) {
|
|
28372
|
-
return options.agentVisualLines.map((agentVisualLine) => centerAnsiText(agentVisualLine, totalWidth));
|
|
28373
|
-
}
|
|
28374
|
-
if (options.agentVisual !== undefined) {
|
|
28375
|
-
const agentVisualLines = options.agentVisual.renderFrame({
|
|
28376
|
-
animationTimeMs: options.animationTimeMs,
|
|
28377
|
-
});
|
|
28378
|
-
if (agentVisualLines.length > 0) {
|
|
28379
|
-
return agentVisualLines.map((agentVisualLine) => centerAnsiText(agentVisualLine, totalWidth));
|
|
28380
|
-
}
|
|
28381
|
-
}
|
|
28382
|
-
return buildCoderRunOctopusVisual({ totalWidth, animationFrame: octopusAnimationFrame });
|
|
28383
|
-
}
|
|
28384
|
-
/**
|
|
28385
|
-
* Builds the structured session lines that combine state, runner, queue, and timing metadata.
|
|
28386
|
-
*/
|
|
28387
|
-
function buildSessionLines(options, totalWidth, pausePresentation) {
|
|
28388
|
-
const bodyWidth = Math.max(10, totalWidth - 4);
|
|
28389
|
-
return buildSessionRows(options, bodyWidth, pausePresentation).map((sessionRow) => buildLabeledSessionLine(sessionRow.label, sessionRow.value, bodyWidth));
|
|
28390
|
-
}
|
|
28391
|
-
/**
|
|
28392
|
-
* Builds the session rows so the renderer can keep one consistent structure without duplicating labels.
|
|
28393
|
-
*/
|
|
28394
|
-
function buildSessionRows(options, bodyWidth, pausePresentation) {
|
|
28395
|
-
const runnerParts = [options.config.agentName || 'No agent selected'];
|
|
28396
|
-
if (options.config.modelName) {
|
|
28397
|
-
runnerParts.push(options.config.modelName);
|
|
28398
|
-
}
|
|
28399
|
-
if (options.config.thinkingLevel) {
|
|
28400
|
-
runnerParts.push(`thinking ${options.config.thinkingLevel}`);
|
|
28401
|
-
}
|
|
28402
|
-
const configurationRows = [
|
|
28403
|
-
...buildOptionalSessionRow('Context', options.config.context),
|
|
28404
|
-
...buildOptionalSessionRow('Server', options.config.serverUrl
|
|
28405
|
-
? buildTerminalUrlLink(options.config.serverUrl, bodyWidth - SESSION_LABEL_WIDTH - 1)
|
|
28406
|
-
: undefined),
|
|
28407
|
-
...buildOptionalSessionRow('Test', options.config.testCommand),
|
|
28408
|
-
];
|
|
28409
|
-
return [
|
|
28410
|
-
{
|
|
28411
|
-
label: 'State',
|
|
28412
|
-
value: `${pausePresentation.badge} ${pausePresentation.stateMessage}`,
|
|
28413
|
-
},
|
|
28414
|
-
{
|
|
28415
|
-
label: 'Runner',
|
|
28416
|
-
value: runnerParts.join(' · '),
|
|
28417
|
-
},
|
|
28418
|
-
...configurationRows,
|
|
28419
|
-
...buildScriptPathSessionRows(options.currentScriptPaths || [], bodyWidth),
|
|
28420
|
-
{
|
|
28421
|
-
label: 'This run',
|
|
28422
|
-
value: buildThisRunSummary(options.progress),
|
|
28423
|
-
},
|
|
28424
|
-
{
|
|
28425
|
-
label: 'Backlog',
|
|
28426
|
-
value: buildBacklogSummary(options.progress),
|
|
28427
|
-
},
|
|
28428
|
-
{
|
|
28429
|
-
label: 'Scope',
|
|
28430
|
-
value: buildScopeSummary(options.progress, options.config),
|
|
28431
|
-
},
|
|
28432
|
-
{
|
|
28433
|
-
label: 'Timing',
|
|
28434
|
-
value: buildTimingSummary(options.progress),
|
|
28435
|
-
},
|
|
28436
|
-
{
|
|
28437
|
-
label: 'Progress',
|
|
28438
|
-
value: buildProgressBar$1(options.progress.percentage, bodyWidth - SESSION_LABEL_WIDTH - 1, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
|
|
28439
|
-
},
|
|
28440
|
-
];
|
|
28441
|
-
}
|
|
28442
|
-
/**
|
|
28443
|
-
* Builds zero or one structured session row for optional metadata.
|
|
28444
|
-
*/
|
|
28445
|
-
function buildOptionalSessionRow(label, value) {
|
|
28446
|
-
if (!value) {
|
|
28447
|
-
return [];
|
|
28448
|
-
}
|
|
28449
|
-
return [{ label, value }];
|
|
28450
|
-
}
|
|
28451
|
-
/**
|
|
28452
|
-
* Builds the active-session summary shown in the session box.
|
|
28453
|
-
*/
|
|
28454
|
-
function buildThisRunSummary(progress) {
|
|
28455
|
-
if (progress.sessionTotal === 0) {
|
|
28456
|
-
return 'No runnable prompts in current scope';
|
|
28457
|
-
}
|
|
28458
|
-
return `Task ${progress.currentPromptIndex}/${progress.sessionTotal} · ${progress.sessionDone} done · ${progress.sessionRemaining} left`;
|
|
28459
|
-
}
|
|
28460
|
-
/**
|
|
28461
|
-
* Builds the backlog/filter summary shown in the session box.
|
|
28462
|
-
*/
|
|
28463
|
-
function buildBacklogSummary(progress) {
|
|
28464
|
-
const parts = [`Repo ${progress.totalPrompts} total`];
|
|
28465
|
-
if (progress.skippedPrompts > 0) {
|
|
28466
|
-
parts.push(`${formatPromptCount$1(progress.skippedPrompts)} outside priority scope`);
|
|
28467
|
-
}
|
|
28468
|
-
return parts.join(' · ');
|
|
28469
|
-
}
|
|
28470
|
-
/**
|
|
28471
|
-
* Builds the priority/write-order summary shown in the session box.
|
|
28472
|
-
*/
|
|
28473
|
-
function buildScopeSummary(progress, config) {
|
|
28474
|
-
var _a;
|
|
28475
|
-
const parts = [formatPriorityFilter((_a = config.priorityFilter) !== null && _a !== void 0 ? _a : {})];
|
|
28476
|
-
if (config.limit !== undefined) {
|
|
28477
|
-
parts.push(`Limit ${formatPromptRunCount(config.limit)}`);
|
|
28478
|
-
}
|
|
28479
|
-
if (progress.toBeWrittenPrompts > 0) {
|
|
28480
|
-
parts.push(`Write ${formatPromptCount$1(progress.toBeWrittenPrompts)} first`);
|
|
28481
|
-
}
|
|
28482
|
-
return parts.join(' · ');
|
|
28483
|
-
}
|
|
28484
|
-
/**
|
|
28485
|
-
* Builds the elapsed/estimate summary shown in the session box.
|
|
28486
|
-
*/
|
|
28487
|
-
function buildTimingSummary(progress) {
|
|
28488
|
-
return `Elapsed ${progress.elapsedText} · Total ${progress.estimatedTotalText} · ETA ${progress.estimatedLabel}`;
|
|
28489
|
-
}
|
|
28490
|
-
/**
|
|
28491
|
-
* Formats a prompt count with singular/plural wording.
|
|
28492
|
-
*/
|
|
28493
|
-
function formatPromptCount$1(count) {
|
|
28494
|
-
return `${count} prompt${count === 1 ? '' : 's'}`;
|
|
28495
|
-
}
|
|
28496
|
-
/**
|
|
28497
|
-
* Formats a configured successful prompt-run limit with singular/plural wording.
|
|
28498
|
-
*/
|
|
28499
|
-
function formatPromptRunCount(count) {
|
|
28500
|
-
return `${count} prompt run${count === 1 ? '' : 's'}`;
|
|
28501
|
-
}
|
|
28502
|
-
|
|
28503
|
-
/**
|
|
28504
|
-
* ANSI sequence that clears the current terminal row.
|
|
28505
|
-
*
|
|
28506
|
-
* @private internal constant of coder run UI
|
|
28507
|
-
*/
|
|
28508
|
-
const CLEAR_CURRENT_LINE = '\x1b[2K';
|
|
28509
|
-
/**
|
|
28510
|
-
* ANSI control character that moves the cursor to the start of its current row.
|
|
28511
|
-
*
|
|
28512
|
-
* @private internal constant of coder run UI
|
|
28513
|
-
*/
|
|
28514
|
-
const MOVE_CURSOR_TO_LINE_START = '\r';
|
|
28515
|
-
/**
|
|
28516
|
-
* Builds one atomic ANSI update that changes the displayed coder-run terminal frame.
|
|
28517
|
-
*
|
|
28518
|
-
* A change in frame height requires a full rewrite to reserve or clear rows correctly. Frames with
|
|
28519
|
-
* the same height only rewrite contiguous groups of changed rows, returning to the frame bottom
|
|
28520
|
-
* between groups so each group can be assembled into the same output payload safely.
|
|
28521
|
-
*
|
|
28522
|
-
* @returns One terminal output payload, or `undefined` when the frame is already up to date.
|
|
28523
|
-
*
|
|
28524
|
-
* @private internal utility of coder run UI
|
|
28525
|
-
*/
|
|
28526
|
-
function buildCoderRunUiTerminalFrameUpdate(options) {
|
|
28527
|
-
const { previousFrameLines, nextFrameLines } = options;
|
|
28528
|
-
if (previousFrameLines.length === 0 && nextFrameLines.length === 0) {
|
|
28529
|
-
return undefined;
|
|
28530
|
-
}
|
|
28531
|
-
if (previousFrameLines.length === 0 || previousFrameLines.length !== nextFrameLines.length) {
|
|
28532
|
-
return buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
|
|
28533
|
-
}
|
|
28534
|
-
return buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
|
|
28535
|
-
}
|
|
28536
|
-
/**
|
|
28537
|
-
* Builds an ANSI update which completely rewrites the reserved terminal frame area.
|
|
28538
|
-
*
|
|
28539
|
-
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
28540
|
-
*/
|
|
28541
|
-
function buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
|
|
28542
|
-
var _a;
|
|
28543
|
-
const previousFrameLineCount = previousFrameLines.length;
|
|
28544
|
-
const lineCountToRewrite = Math.max(previousFrameLineCount, nextFrameLines.length);
|
|
28545
|
-
const outputParts = [];
|
|
28546
|
-
if (previousFrameLineCount > 1) {
|
|
28547
|
-
outputParts.push(moveCursorUp(previousFrameLineCount - 1));
|
|
28548
|
-
}
|
|
28549
|
-
for (let lineIndex = 0; lineIndex < lineCountToRewrite; lineIndex++) {
|
|
28550
|
-
outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, (_a = nextFrameLines[lineIndex]) !== null && _a !== void 0 ? _a : '');
|
|
28551
|
-
if (lineIndex < lineCountToRewrite - 1) {
|
|
28552
|
-
outputParts.push('\n');
|
|
28553
|
-
}
|
|
28554
|
-
}
|
|
28555
|
-
const clearedTrailingLineCount = lineCountToRewrite - nextFrameLines.length;
|
|
28556
|
-
if (clearedTrailingLineCount > 0) {
|
|
28557
|
-
outputParts.push(moveCursorUp(clearedTrailingLineCount));
|
|
28558
|
-
}
|
|
28559
|
-
outputParts.push(MOVE_CURSOR_TO_LINE_START);
|
|
28560
|
-
return outputParts.join('');
|
|
28561
|
-
}
|
|
28562
|
-
/**
|
|
28563
|
-
* Builds an ANSI update that rewrites only contiguous groups of rows which changed.
|
|
28564
|
-
*
|
|
28565
|
-
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
28566
|
-
*/
|
|
28567
|
-
function buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
|
|
28568
|
-
const outputParts = [];
|
|
28569
|
-
let lineIndex = 0;
|
|
28570
|
-
while (lineIndex < nextFrameLines.length) {
|
|
28571
|
-
if (previousFrameLines[lineIndex] === nextFrameLines[lineIndex]) {
|
|
28572
|
-
lineIndex++;
|
|
28573
|
-
continue;
|
|
28574
|
-
}
|
|
28575
|
-
const changedRangeStartLineIndex = lineIndex;
|
|
28576
|
-
while (lineIndex + 1 < nextFrameLines.length &&
|
|
28577
|
-
previousFrameLines[lineIndex + 1] !== nextFrameLines[lineIndex + 1]) {
|
|
28578
|
-
lineIndex++;
|
|
28579
|
-
}
|
|
28580
|
-
appendChangedCoderRunUiTerminalFrameRange({
|
|
28581
|
-
outputParts,
|
|
28582
|
-
frameLineCount: nextFrameLines.length,
|
|
28583
|
-
changedRangeStartLineIndex,
|
|
28584
|
-
changedRangeEndLineIndex: lineIndex,
|
|
28585
|
-
nextFrameLines,
|
|
28586
|
-
});
|
|
28587
|
-
lineIndex++;
|
|
28588
|
-
}
|
|
28589
|
-
return outputParts.length === 0 ? undefined : outputParts.join('');
|
|
28590
|
-
}
|
|
28591
|
-
/**
|
|
28592
|
-
* Appends the cursor movements and row rewrites for one contiguous changed frame range.
|
|
28593
|
-
*
|
|
28594
|
-
* @private helper of `buildChangedCoderRunUiTerminalFrameUpdate`
|
|
28595
|
-
*/
|
|
28596
|
-
function appendChangedCoderRunUiTerminalFrameRange(options) {
|
|
28597
|
-
const { outputParts, frameLineCount, changedRangeStartLineIndex, changedRangeEndLineIndex, nextFrameLines } = options;
|
|
28598
|
-
const linesUpFromFrameBottom = frameLineCount - 1 - changedRangeStartLineIndex;
|
|
28599
|
-
if (linesUpFromFrameBottom > 0) {
|
|
28600
|
-
outputParts.push(moveCursorUp(linesUpFromFrameBottom));
|
|
28601
|
-
}
|
|
28602
|
-
for (let lineIndex = changedRangeStartLineIndex; lineIndex <= changedRangeEndLineIndex; lineIndex++) {
|
|
28603
|
-
outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, nextFrameLines[lineIndex]);
|
|
28604
|
-
if (lineIndex < changedRangeEndLineIndex) {
|
|
28605
|
-
outputParts.push('\n');
|
|
28606
|
-
}
|
|
28607
|
-
}
|
|
28608
|
-
const linesDownToFrameBottom = frameLineCount - 1 - changedRangeEndLineIndex;
|
|
28609
|
-
if (linesDownToFrameBottom > 0) {
|
|
28610
|
-
outputParts.push(moveCursorDown(linesDownToFrameBottom));
|
|
28611
|
-
}
|
|
28612
|
-
outputParts.push(MOVE_CURSOR_TO_LINE_START);
|
|
28613
|
-
}
|
|
28614
|
-
/**
|
|
28615
|
-
* Builds an ANSI sequence which moves the terminal cursor up by a positive number of rows.
|
|
28616
|
-
*
|
|
28617
|
-
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
28618
|
-
*/
|
|
28619
|
-
function moveCursorUp(lineCount) {
|
|
28620
|
-
return `\x1b[${lineCount}A`;
|
|
28621
|
-
}
|
|
28622
|
-
/**
|
|
28623
|
-
* Builds an ANSI sequence which moves the terminal cursor down by a positive number of rows.
|
|
28624
|
-
*
|
|
28625
|
-
* @private helper of `buildCoderRunUiTerminalFrameUpdate`
|
|
28626
|
-
*/
|
|
28627
|
-
function moveCursorDown(lineCount) {
|
|
28628
|
-
return `\x1b[${lineCount}B`;
|
|
28629
|
-
}
|
|
28630
|
-
|
|
28631
|
-
/**
|
|
28632
|
-
* Calendar formats used when displaying the estimated completion time.
|
|
28633
|
-
*
|
|
28634
|
-
* Uses 24-hour format (`HH:mm`) so estimates are unambiguous (for example `17:30` instead of `5:30`).
|
|
28635
|
-
*/
|
|
28636
|
-
const ESTIMATED_DONE_CALENDAR_FORMATS = {
|
|
28637
|
-
sameDay: '[Today] HH:mm',
|
|
28638
|
-
nextDay: '[Tomorrow] HH:mm',
|
|
28639
|
-
nextWeek: 'dddd HH:mm',
|
|
28640
|
-
lastDay: '[Yesterday] HH:mm',
|
|
28641
|
-
lastWeek: 'dddd HH:mm',
|
|
28642
|
-
sameElse: 'MMM D HH:mm',
|
|
28643
|
-
};
|
|
28644
|
-
/**
|
|
28645
|
-
* Formats a duration into a compact string such as "3h 12m" or "45s".
|
|
28646
|
-
*/
|
|
28647
|
-
function formatDurationBrief(duration) {
|
|
28648
|
-
const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
|
|
28649
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
28650
|
-
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
28651
|
-
const seconds = totalSeconds % 60;
|
|
28652
|
-
const parts = [];
|
|
28653
|
-
if (hours > 0) {
|
|
28654
|
-
parts.push(`${hours}h`);
|
|
28655
|
-
}
|
|
28656
|
-
if (minutes > 0) {
|
|
28657
|
-
parts.push(`${minutes}m`);
|
|
28658
|
-
}
|
|
28659
|
-
if (!parts.length && seconds > 0) {
|
|
28660
|
-
parts.push(`${seconds}s`);
|
|
28661
|
-
}
|
|
28662
|
-
if (!parts.length) {
|
|
28663
|
-
parts.push('0s');
|
|
28664
|
-
}
|
|
28665
|
-
return parts.join(' ');
|
|
28666
|
-
}
|
|
28667
|
-
|
|
28668
|
-
/**
|
|
28669
|
-
* Builds a session-scoped progress snapshot from prompt stats and elapsed active time.
|
|
28670
|
-
*
|
|
28671
|
-
* When the current session has not finished any prompt yet but a `cachedAveragePromptDurationMs` is
|
|
28672
|
-
* provided (loaded from the per-config estimate cache stored in the temp folder), the snapshot uses
|
|
28673
|
-
* that historical average to project the remaining work instead of waiting for the first completion.
|
|
28674
|
-
*/
|
|
28675
|
-
function buildCoderRunProgressSnapshot(stats, elapsedDuration, initialDone, cachedAveragePromptDurationMs, runLimit) {
|
|
28676
|
-
const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
|
|
28677
|
-
const completedSessionPrompts = Math.max(0, stats.done - initialDone);
|
|
28678
|
-
const availableSessionTotal = completedSessionPrompts + stats.forAgent;
|
|
28679
|
-
const sessionTotal = resolveSessionTotal(availableSessionTotal, runLimit);
|
|
28680
|
-
const sessionDone = Math.min(completedSessionPrompts, sessionTotal);
|
|
28681
|
-
const sessionRemaining = Math.max(0, sessionTotal - sessionDone);
|
|
28682
|
-
const currentPromptIndex = sessionTotal > 0 ? Math.min(sessionDone + 1, sessionTotal) : 0;
|
|
28683
|
-
const percentage = sessionTotal > 0 ? Math.round((sessionDone / sessionTotal) * 100) : 0;
|
|
28684
|
-
const elapsedText = formatDurationBrief(elapsedDuration);
|
|
28685
|
-
const elapsedMs = elapsedDuration.asMilliseconds();
|
|
28686
|
-
let estimatedTotalText = 'estimating...';
|
|
28687
|
-
let estimatedLabel = 'after first completion';
|
|
28688
|
-
let isEstimatedTotalKnown = false;
|
|
28689
|
-
let estimatedTotalMs;
|
|
28690
|
-
if (sessionTotal > 0 && sessionDone > 0) {
|
|
28691
|
-
estimatedTotalMs = (elapsedMs * sessionTotal) / sessionDone;
|
|
28692
|
-
}
|
|
28693
|
-
else if (sessionTotal > 0 &&
|
|
28694
|
-
cachedAveragePromptDurationMs !== undefined &&
|
|
28695
|
-
cachedAveragePromptDurationMs > 0) {
|
|
28696
|
-
const projectedRemainingMs = sessionRemaining * cachedAveragePromptDurationMs;
|
|
28697
|
-
estimatedTotalMs = elapsedMs + projectedRemainingMs;
|
|
28698
|
-
}
|
|
28699
|
-
if (estimatedTotalMs !== undefined) {
|
|
28700
|
-
const estimatedRemainingMs = Math.max(0, estimatedTotalMs - elapsedMs);
|
|
28701
|
-
const estimatedTotalDuration = moment.duration(estimatedTotalMs);
|
|
28702
|
-
const estimatedCompletion = moment().add(estimatedRemainingMs, 'milliseconds');
|
|
28703
|
-
estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
|
|
28704
|
-
estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
|
|
28705
|
-
isEstimatedTotalKnown = true;
|
|
28706
|
-
}
|
|
28987
|
+
* @private shared helper for terminal avatar rendering
|
|
28988
|
+
*/
|
|
28989
|
+
function createTerminalAgentAvatarVisual(options) {
|
|
28990
|
+
const renderInputs = createTerminalAgentAvatarVisualRenderInputs(options.agentSource, options.defaultAvatarVisualId || DEFAULT_AGENT_AVATAR_VISUAL_ID);
|
|
28707
28991
|
return {
|
|
28708
|
-
|
|
28709
|
-
|
|
28710
|
-
|
|
28711
|
-
|
|
28712
|
-
|
|
28713
|
-
|
|
28714
|
-
|
|
28715
|
-
|
|
28716
|
-
|
|
28717
|
-
estimatedTotalText,
|
|
28718
|
-
estimatedLabel,
|
|
28719
|
-
isEstimatedTotalKnown,
|
|
28992
|
+
isAnimated: renderInputs.resolvedAvatarRenderDefinition.avatarVisual.isAnimated,
|
|
28993
|
+
renderFrame({ animationTimeMs }) {
|
|
28994
|
+
return renderTerminalAgentAvatarVisualFrame({
|
|
28995
|
+
...renderInputs,
|
|
28996
|
+
animationTimeMs,
|
|
28997
|
+
colorDepth: options.colorDepth,
|
|
28998
|
+
createCanvas: options.createCanvas,
|
|
28999
|
+
});
|
|
29000
|
+
},
|
|
28720
29001
|
};
|
|
28721
29002
|
}
|
|
28722
29003
|
/**
|
|
28723
|
-
*
|
|
29004
|
+
* Creates stable avatar render inputs from one agent source.
|
|
29005
|
+
*
|
|
29006
|
+
* @private shared helper for terminal avatar rendering
|
|
28724
29007
|
*/
|
|
28725
|
-
function
|
|
28726
|
-
|
|
28727
|
-
|
|
28728
|
-
|
|
28729
|
-
|
|
29008
|
+
function createTerminalAgentAvatarVisualRenderInputs(agentSource, defaultAvatarVisualId) {
|
|
29009
|
+
const agentBasicInformation = parseAgentSource(agentSource);
|
|
29010
|
+
const avatarDefinition = createAvatarDefinitionFromAgentBasicInformation(agentBasicInformation);
|
|
29011
|
+
const avatarVisualId = resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId);
|
|
29012
|
+
const resolvedAvatarRenderDefinition = resolveAvatarRenderDefinition({
|
|
29013
|
+
avatarDefinition,
|
|
29014
|
+
visualId: avatarVisualId,
|
|
29015
|
+
surface: 'transparent',
|
|
29016
|
+
});
|
|
29017
|
+
return {
|
|
29018
|
+
avatarDefinition,
|
|
29019
|
+
avatarVisualId,
|
|
29020
|
+
resolvedAvatarRenderDefinition,
|
|
29021
|
+
};
|
|
28730
29022
|
}
|
|
28731
|
-
|
|
28732
29023
|
/**
|
|
28733
|
-
*
|
|
29024
|
+
* Resolves the terminal avatar visual id from already parsed agent information.
|
|
29025
|
+
*
|
|
29026
|
+
* @private shared helper for terminal avatar rendering
|
|
28734
29027
|
*/
|
|
28735
|
-
|
|
28736
|
-
|
|
28737
|
-
|
|
28738
|
-
|
|
28739
|
-
|
|
28740
|
-
|
|
28741
|
-
|
|
28742
|
-
|
|
28743
|
-
|
|
28744
|
-
|
|
28745
|
-
|
|
28746
|
-
|
|
28747
|
-
|
|
28748
|
-
|
|
28749
|
-
|
|
28750
|
-
|
|
28751
|
-
|
|
28752
|
-
|
|
28753
|
-
|
|
28754
|
-
|
|
28755
|
-
|
|
28756
|
-
|
|
28757
|
-
|
|
28758
|
-
|
|
28759
|
-
|
|
28760
|
-
resume() {
|
|
28761
|
-
if (this.pausedSince !== undefined) {
|
|
28762
|
-
this.pausedMs += moment().diff(this.pausedSince);
|
|
28763
|
-
this.pausedSince = undefined;
|
|
28764
|
-
}
|
|
29028
|
+
function resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicInformation, defaultAvatarVisualId) {
|
|
29029
|
+
return resolveAgentAvatarVisualId(agentBasicInformation, resolveAvatarVisualId(agentBasicInformation.meta.visual) || defaultAvatarVisualId);
|
|
29030
|
+
}
|
|
29031
|
+
/**
|
|
29032
|
+
* Renders one terminal avatar frame through the shared avatar-to-ASCII pipeline.
|
|
29033
|
+
*
|
|
29034
|
+
* Visuals which are made of characters themselves, for example `AsciiOctopus`, paint the terminal
|
|
29035
|
+
* character grid directly, because rasterizing them into half-block ASCII art averages every glyph
|
|
29036
|
+
* away and makes them indistinguishable from the other blob-shaped visuals.
|
|
29037
|
+
*
|
|
29038
|
+
* @private shared helper for terminal avatar rendering
|
|
29039
|
+
*/
|
|
29040
|
+
function renderTerminalAgentAvatarVisualFrame(options) {
|
|
29041
|
+
const terminalTextLines = renderAvatarVisualTerminalTextLines({
|
|
29042
|
+
avatarDefinition: options.avatarDefinition,
|
|
29043
|
+
visualId: options.avatarVisualId,
|
|
29044
|
+
surface: 'transparent',
|
|
29045
|
+
columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
|
|
29046
|
+
rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
|
|
29047
|
+
colorDepth: options.colorDepth,
|
|
29048
|
+
timeMs: options.animationTimeMs,
|
|
29049
|
+
resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
|
|
29050
|
+
});
|
|
29051
|
+
if (terminalTextLines !== null) {
|
|
29052
|
+
return terminalTextLines;
|
|
28765
29053
|
}
|
|
28766
|
-
|
|
28767
|
-
|
|
28768
|
-
|
|
28769
|
-
getElapsedDuration() {
|
|
28770
|
-
const wallMs = moment().diff(this.startTime);
|
|
28771
|
-
const currentPauseMs = this.pausedSince !== undefined ? moment().diff(this.pausedSince) : 0;
|
|
28772
|
-
return moment.duration(Math.max(0, wallMs - this.pausedMs - currentPauseMs));
|
|
29054
|
+
if (options.createCanvas === undefined) {
|
|
29055
|
+
// Note: A pixel-based visual cannot be rasterized without a canvas, so the caller renders no avatar
|
|
29056
|
+
return [];
|
|
28773
29057
|
}
|
|
29058
|
+
return renderAvatarVisualAsciiArt({
|
|
29059
|
+
avatarDefinition: options.avatarDefinition,
|
|
29060
|
+
visualId: options.avatarVisualId,
|
|
29061
|
+
surface: 'transparent',
|
|
29062
|
+
columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
|
|
29063
|
+
rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
|
|
29064
|
+
canvasWidth: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_WIDTH,
|
|
29065
|
+
canvasHeight: TERMINAL_AGENT_AVATAR_VISUAL_CANVAS_HEIGHT,
|
|
29066
|
+
colorDepth: options.colorDepth,
|
|
29067
|
+
timeMs: options.animationTimeMs,
|
|
29068
|
+
createCanvas: options.createCanvas,
|
|
29069
|
+
resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
|
|
29070
|
+
});
|
|
28774
29071
|
}
|
|
28775
29072
|
|
|
28776
29073
|
/**
|
|
28777
|
-
*
|
|
29074
|
+
* Refresh cadence used only while the rich coder UI needs animated updates.
|
|
28778
29075
|
*
|
|
28779
29076
|
* @private internal constant of coder run UI
|
|
28780
29077
|
*/
|
|
28781
|
-
const
|
|
29078
|
+
const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = TERMINAL_AGENT_AVATAR_VISUAL_REFRESH_INTERVAL_MS;
|
|
28782
29079
|
/**
|
|
28783
|
-
*
|
|
29080
|
+
* Phases that still benefit from automatic refreshes because the frame can change
|
|
29081
|
+
* over time even without new runner output.
|
|
28784
29082
|
*
|
|
28785
|
-
*
|
|
28786
|
-
|
|
29083
|
+
* @private internal constant of coder run UI
|
|
29084
|
+
*/
|
|
29085
|
+
const AUTO_REFRESH_PHASES = ['initializing', 'loading', 'running', 'verifying'];
|
|
29086
|
+
/**
|
|
29087
|
+
* Phases where an animated `--agent` visual should keep rendering like it does on the web.
|
|
29088
|
+
*
|
|
29089
|
+
* @private internal constant of coder run UI
|
|
29090
|
+
*/
|
|
29091
|
+
const AGENT_VISUAL_AUTO_REFRESH_PHASES = [...AUTO_REFRESH_PHASES, 'waiting'];
|
|
29092
|
+
/**
|
|
29093
|
+
* Returns whether the rich coder UI should keep animating on its own.
|
|
28787
29094
|
*
|
|
28788
29095
|
* @private internal utility of coder run UI
|
|
28789
29096
|
*/
|
|
28790
|
-
|
|
28791
|
-
|
|
28792
|
-
|
|
28793
|
-
|
|
28794
|
-
|
|
28795
|
-
this.currentScriptPaths = [];
|
|
28796
|
-
this.currentAttempt = 1;
|
|
28797
|
-
this.maxAttempts = 3;
|
|
28798
|
-
this.detailLines = [];
|
|
28799
|
-
this.messagePreviewLines = [];
|
|
28800
|
-
this.messagePreviewSections = [];
|
|
28801
|
-
this.agentStatusLines = [];
|
|
28802
|
-
this.agentStatusTableRows = [];
|
|
28803
|
-
this.agentOutputLines = [];
|
|
28804
|
-
this.phase = 'initializing';
|
|
28805
|
-
this.statusMessage = 'Initializing...';
|
|
28806
|
-
this.errors = [];
|
|
28807
|
-
this.stats = { done: 0, forAgent: 0, outsidePriorityRange: 0, toBeWritten: 0 };
|
|
28808
|
-
// Match the plain CLI progress header and count active run time from startup.
|
|
28809
|
-
// Explicit waits/pauses are excluded later through `pauseTimer()` / `resumeTimer()`.
|
|
28810
|
-
this.timer = new CoderRunTimer(startTime);
|
|
28811
|
-
}
|
|
28812
|
-
/**
|
|
28813
|
-
* Pauses the elapsed timer (e.g. while waiting for user input or paused state).
|
|
28814
|
-
*/
|
|
28815
|
-
pauseTimer() {
|
|
28816
|
-
this.timer.pause();
|
|
28817
|
-
this.emitChange();
|
|
28818
|
-
}
|
|
28819
|
-
/**
|
|
28820
|
-
* Resumes the elapsed timer after a pause.
|
|
28821
|
-
*/
|
|
28822
|
-
resumeTimer() {
|
|
28823
|
-
this.timer.resume();
|
|
28824
|
-
this.emitChange();
|
|
28825
|
-
}
|
|
28826
|
-
/**
|
|
28827
|
-
* Replaces the configuration shown in the UI header.
|
|
28828
|
-
*/
|
|
28829
|
-
setConfig(config) {
|
|
28830
|
-
this.config = config;
|
|
28831
|
-
this.emitChange();
|
|
28832
|
-
}
|
|
28833
|
-
/**
|
|
28834
|
-
* Replaces the ASCII-art agent visual renderer shown instead of the default brand banner.
|
|
28835
|
-
*/
|
|
28836
|
-
setAgentVisual(agentVisual) {
|
|
28837
|
-
this.agentVisual = agentVisual;
|
|
28838
|
-
this.emitChange();
|
|
28839
|
-
}
|
|
28840
|
-
/**
|
|
28841
|
-
* Feeds new prompt statistics from the main loop so the progress bar updates.
|
|
28842
|
-
*/
|
|
28843
|
-
updateProgress(stats) {
|
|
28844
|
-
if (this.initialDone === undefined && (stats.done > 0 || stats.forAgent > 0 || stats.toBeWritten > 0)) {
|
|
28845
|
-
this.initialDone = stats.done;
|
|
28846
|
-
}
|
|
28847
|
-
this.stats = stats;
|
|
28848
|
-
this.emitChange();
|
|
28849
|
-
}
|
|
28850
|
-
/**
|
|
28851
|
-
* Sets the cached average prompt duration (in milliseconds) loaded from the temp-folder estimate cache.
|
|
28852
|
-
*
|
|
28853
|
-
* Used to project completion estimates before the first prompt of the current session finishes.
|
|
28854
|
-
*/
|
|
28855
|
-
setCachedAveragePromptDurationMs(cachedAveragePromptDurationMs) {
|
|
28856
|
-
this.cachedAveragePromptDurationMs = cachedAveragePromptDurationMs;
|
|
28857
|
-
this.emitChange();
|
|
28858
|
-
}
|
|
28859
|
-
/**
|
|
28860
|
-
* Computes a progress snapshot on demand so elapsed time ticks with periodic re-renders.
|
|
28861
|
-
*/
|
|
28862
|
-
getProgress() {
|
|
28863
|
-
var _a;
|
|
28864
|
-
return buildCoderRunProgressSnapshot(this.stats, this.timer.getElapsedDuration(), (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done, this.cachedAveragePromptDurationMs, this.config.limit);
|
|
28865
|
-
}
|
|
28866
|
-
/**
|
|
28867
|
-
* Sets the label of the prompt currently being processed and resets per-prompt state.
|
|
28868
|
-
*/
|
|
28869
|
-
setCurrentPrompt(label) {
|
|
28870
|
-
this.currentPromptLabel = label;
|
|
28871
|
-
this.currentScriptPaths = [];
|
|
28872
|
-
this.detailLines = [];
|
|
28873
|
-
this.messagePreviewLines = [];
|
|
28874
|
-
this.messagePreviewSections = [];
|
|
28875
|
-
this.pendingEnterLabel = undefined;
|
|
28876
|
-
this.agentOutputLines = [];
|
|
28877
|
-
this.currentAttempt = 1;
|
|
28878
|
-
this.emitChange();
|
|
28879
|
-
}
|
|
28880
|
-
/**
|
|
28881
|
-
* Sets the temporary shell script currently used by the active prompt.
|
|
28882
|
-
*/
|
|
28883
|
-
setCurrentScriptPath(scriptPath) {
|
|
28884
|
-
this.currentScriptPaths = scriptPath ? [scriptPath] : [];
|
|
28885
|
-
this.emitChange();
|
|
28886
|
-
}
|
|
28887
|
-
/**
|
|
28888
|
-
* Sets multiple temporary shell scripts used by a shared multi-agent session.
|
|
28889
|
-
*/
|
|
28890
|
-
setCurrentScriptPaths(scriptPaths) {
|
|
28891
|
-
this.currentScriptPaths = [...scriptPaths];
|
|
28892
|
-
this.emitChange();
|
|
28893
|
-
}
|
|
28894
|
-
/**
|
|
28895
|
-
* Updates the current retry attempt number.
|
|
28896
|
-
*/
|
|
28897
|
-
setAttempt(attempt) {
|
|
28898
|
-
this.currentAttempt = attempt;
|
|
28899
|
-
this.emitChange();
|
|
28900
|
-
}
|
|
28901
|
-
/**
|
|
28902
|
-
* Appends raw agent output text, keeping only the last `MAX_AGENT_OUTPUT_LINES`.
|
|
28903
|
-
*/
|
|
28904
|
-
addAgentOutput(text) {
|
|
28905
|
-
const lines = text.split(/\r?\n/).filter((line) => line.trim() !== '');
|
|
28906
|
-
if (lines.length === 0) {
|
|
28907
|
-
return;
|
|
28908
|
-
}
|
|
28909
|
-
this.agentOutputLines.push(...lines);
|
|
28910
|
-
if (this.agentOutputLines.length > MAX_AGENT_OUTPUT_LINES) {
|
|
28911
|
-
this.agentOutputLines = this.agentOutputLines.slice(-MAX_AGENT_OUTPUT_LINES);
|
|
28912
|
-
}
|
|
28913
|
-
this.emitChange();
|
|
28914
|
-
}
|
|
28915
|
-
/**
|
|
28916
|
-
* Transitions the execution phase shown in the UI.
|
|
28917
|
-
*/
|
|
28918
|
-
setPhase(phase) {
|
|
28919
|
-
this.phase = phase;
|
|
28920
|
-
this.emitChange();
|
|
28921
|
-
}
|
|
28922
|
-
/**
|
|
28923
|
-
* Updates the status message line beneath the current prompt label.
|
|
28924
|
-
*/
|
|
28925
|
-
setStatusMessage(message) {
|
|
28926
|
-
this.statusMessage = message;
|
|
28927
|
-
this.emitChange();
|
|
28928
|
-
}
|
|
28929
|
-
/**
|
|
28930
|
-
* Replaces the contextual detail lines shown beneath the current prompt status.
|
|
28931
|
-
*/
|
|
28932
|
-
setDetailLines(detailLines) {
|
|
28933
|
-
this.detailLines = detailLines.filter((detailLine) => detailLine.trim() !== '');
|
|
28934
|
-
this.emitChange();
|
|
28935
|
-
}
|
|
28936
|
-
/**
|
|
28937
|
-
* Replaces the exact user-message preview lines shown in agent-specific panels.
|
|
28938
|
-
*/
|
|
28939
|
-
setMessagePreviewLines(messagePreviewLines) {
|
|
28940
|
-
this.messagePreviewLines = [...messagePreviewLines];
|
|
28941
|
-
this.emitChange();
|
|
28942
|
-
}
|
|
28943
|
-
/**
|
|
28944
|
-
* Replaces the structured user-message panels shown in agent-specific dashboards.
|
|
28945
|
-
*/
|
|
28946
|
-
setMessagePreviewSections(messagePreviewSections) {
|
|
28947
|
-
this.messagePreviewSections = [...messagePreviewSections];
|
|
28948
|
-
this.emitChange();
|
|
28949
|
-
}
|
|
28950
|
-
/**
|
|
28951
|
-
* Replaces agent status rows shown by agent-specific terminal frames.
|
|
28952
|
-
*/
|
|
28953
|
-
setAgentStatusLines(agentStatusLines) {
|
|
28954
|
-
this.agentStatusLines = [...agentStatusLines];
|
|
28955
|
-
this.emitChange();
|
|
28956
|
-
}
|
|
28957
|
-
/**
|
|
28958
|
-
* Replaces structured agent status rows shown by agent-specific terminal frames.
|
|
28959
|
-
*/
|
|
28960
|
-
setAgentStatusTableRows(agentStatusTableRows) {
|
|
28961
|
-
this.agentStatusTableRows = [...agentStatusTableRows];
|
|
28962
|
-
this.emitChange();
|
|
28963
|
-
}
|
|
28964
|
-
/**
|
|
28965
|
-
* Sets or clears the Enter-key action label shown in the controls panel.
|
|
28966
|
-
*/
|
|
28967
|
-
setPendingEnterLabel(pendingEnterLabel) {
|
|
28968
|
-
this.pendingEnterLabel = pendingEnterLabel;
|
|
28969
|
-
this.emitChange();
|
|
28970
|
-
}
|
|
28971
|
-
/**
|
|
28972
|
-
* Appends an error message to the error list shown in the UI.
|
|
28973
|
-
*/
|
|
28974
|
-
addError(errorMessage) {
|
|
28975
|
-
this.errors.push(errorMessage);
|
|
28976
|
-
this.emitChange();
|
|
29097
|
+
function isCoderRunUiAutoRefreshing(phase, pauseState, isAgentVisualAnimated = false) {
|
|
29098
|
+
// `PAUSING` still means the current task is winding down, so keep active
|
|
29099
|
+
// animations/timers running until the runner reaches the fully paused state.
|
|
29100
|
+
if (pauseState === 'PAUSED') {
|
|
29101
|
+
return false;
|
|
28977
29102
|
}
|
|
28978
|
-
|
|
28979
|
-
|
|
29103
|
+
if (isAgentVisualAnimated) {
|
|
29104
|
+
return AGENT_VISUAL_AUTO_REFRESH_PHASES.includes(phase);
|
|
28980
29105
|
}
|
|
29106
|
+
return AUTO_REFRESH_PHASES.includes(phase);
|
|
29107
|
+
}
|
|
29108
|
+
/**
|
|
29109
|
+
* Returns the automatic refresh interval for the current UI state.
|
|
29110
|
+
*
|
|
29111
|
+
* Waiting, paused, and completed states return `undefined` so the rich UI stays
|
|
29112
|
+
* perfectly still until actual state changes arrive.
|
|
29113
|
+
*
|
|
29114
|
+
* @private internal utility of coder run UI
|
|
29115
|
+
*/
|
|
29116
|
+
function getCoderRunUiAutoRefreshInterval(phase, pauseState, isAgentVisualAnimated = false) {
|
|
29117
|
+
return isCoderRunUiAutoRefreshing(phase, pauseState, isAgentVisualAnimated)
|
|
29118
|
+
? ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS
|
|
29119
|
+
: undefined;
|
|
28981
29120
|
}
|
|
28982
29121
|
|
|
28983
29122
|
/**
|
|
@@ -29075,6 +29214,7 @@ function renderCoderRunUi(startTime, options = {}) {
|
|
|
29075
29214
|
let isRendering = false;
|
|
29076
29215
|
let renderScheduled = false;
|
|
29077
29216
|
let autoRefreshTimeout;
|
|
29217
|
+
let controlFeedbackTimeout;
|
|
29078
29218
|
let isDisposed = false;
|
|
29079
29219
|
/**
|
|
29080
29220
|
* Schedules a render on the next tick if one isn't already pending.
|
|
@@ -29140,9 +29280,32 @@ function renderCoderRunUi(startTime, options = {}) {
|
|
|
29140
29280
|
pendingEnterLabel: state.pendingEnterLabel,
|
|
29141
29281
|
agentOutputLines: state.agentOutputLines,
|
|
29142
29282
|
errors: state.errors,
|
|
29283
|
+
controlFeedback: state.controlFeedback,
|
|
29143
29284
|
progress: state.getProgress(),
|
|
29144
29285
|
});
|
|
29145
29286
|
}
|
|
29287
|
+
/**
|
|
29288
|
+
* Answers one pressed control key in the frame and hides that answer again after a short moment.
|
|
29289
|
+
*/
|
|
29290
|
+
function showControlFeedback(controlFeedback) {
|
|
29291
|
+
clearControlFeedbackTimeout();
|
|
29292
|
+
// Note: Every state change re-renders the frame, so the answer appears with the very next frame
|
|
29293
|
+
state.setControlFeedback(controlFeedback);
|
|
29294
|
+
controlFeedbackTimeout = setTimeout(() => {
|
|
29295
|
+
controlFeedbackTimeout = undefined;
|
|
29296
|
+
state.setControlFeedback(undefined);
|
|
29297
|
+
}, CODER_RUN_CONTROL_FEEDBACK_DURATION_MS);
|
|
29298
|
+
}
|
|
29299
|
+
/**
|
|
29300
|
+
* Stops the timer which hides the currently shown control answer.
|
|
29301
|
+
*/
|
|
29302
|
+
function clearControlFeedbackTimeout() {
|
|
29303
|
+
if (controlFeedbackTimeout === undefined) {
|
|
29304
|
+
return;
|
|
29305
|
+
}
|
|
29306
|
+
clearTimeout(controlFeedbackTimeout);
|
|
29307
|
+
controlFeedbackTimeout = undefined;
|
|
29308
|
+
}
|
|
29146
29309
|
/**
|
|
29147
29310
|
* Clears previously rendered lines and writes a new frame only where needed.
|
|
29148
29311
|
*/
|
|
@@ -29175,19 +29338,10 @@ function renderCoderRunUi(startTime, options = {}) {
|
|
|
29175
29338
|
cleanup();
|
|
29176
29339
|
process.exit(0);
|
|
29177
29340
|
}
|
|
29178
|
-
|
|
29179
|
-
|
|
29180
|
-
|
|
29181
|
-
|
|
29182
|
-
}
|
|
29183
|
-
if (key.name === 's') {
|
|
29184
|
-
requestSkipCurrentWait();
|
|
29185
|
-
scheduleRender();
|
|
29186
|
-
return;
|
|
29187
|
-
}
|
|
29188
|
-
if (key.name === 'x') {
|
|
29189
|
-
toggleEndAfterCurrentPromptState();
|
|
29190
|
-
scheduleRender();
|
|
29341
|
+
// Note: [🏹] The very same key handling is shared with the plain console mode, see `applyCoderRunControlKey`
|
|
29342
|
+
const controlFeedback = applyCoderRunControlKey(key.name);
|
|
29343
|
+
if (controlFeedback !== undefined) {
|
|
29344
|
+
showControlFeedback(controlFeedback);
|
|
29191
29345
|
return;
|
|
29192
29346
|
}
|
|
29193
29347
|
if ((key.name === 'return' || key.name === 'enter') && pendingEnterResolver) {
|
|
@@ -29213,6 +29367,7 @@ function renderCoderRunUi(startTime, options = {}) {
|
|
|
29213
29367
|
clearTimeout(autoRefreshTimeout);
|
|
29214
29368
|
autoRefreshTimeout = undefined;
|
|
29215
29369
|
}
|
|
29370
|
+
clearControlFeedbackTimeout();
|
|
29216
29371
|
state.off('change', scheduleRender);
|
|
29217
29372
|
process.stdin.off('keypress', keypressHandler);
|
|
29218
29373
|
process.stdout.off('resize', scheduleRender);
|
|
@@ -29273,7 +29428,7 @@ function createCoderRunOptionsForAgent(options) {
|
|
|
29273
29428
|
waitBetweenPrompts: 0,
|
|
29274
29429
|
waitAfterError: 0,
|
|
29275
29430
|
noCommit: options.noCommit,
|
|
29276
|
-
|
|
29431
|
+
gitChanges: options.gitChanges,
|
|
29277
29432
|
normalizeLineEndings: options.normalizeLineEndings,
|
|
29278
29433
|
allowCredits: options.allowCredits,
|
|
29279
29434
|
autoMigrate: false,
|
|
@@ -39841,11 +39996,11 @@ class RunMultipleAgentMessageTaskScheduler {
|
|
|
39841
39996
|
* Validates constraints specific to never-ending agent watch loops.
|
|
39842
39997
|
*/
|
|
39843
39998
|
function validateAgentWatchOptions(commandDisplayName, options) {
|
|
39844
|
-
if (!options.noCommit || options.
|
|
39999
|
+
if (!options.noCommit || options.gitChanges !== 'fail') {
|
|
39845
40000
|
return;
|
|
39846
40001
|
}
|
|
39847
40002
|
throw new NotAllowed(spaceTrim$1(`
|
|
39848
|
-
Flag \`--no-commit\` requires \`--
|
|
40003
|
+
Flag \`--no-commit\` requires \`--git-changes ignore\` when used with \`${commandDisplayName}\`.
|
|
39849
40004
|
|
|
39850
40005
|
Without commits, answered messages remain in the working tree and the next message round would fail the clean
|
|
39851
40006
|
working tree check.
|
|
@@ -40158,7 +40313,7 @@ function createLocalAgentRunOptions(options, localAgentRunnerLimits) {
|
|
|
40158
40313
|
noUi: options.noUi,
|
|
40159
40314
|
thinkingLevel: options.thinkingLevel,
|
|
40160
40315
|
noCommit: true,
|
|
40161
|
-
|
|
40316
|
+
gitChanges: 'ignore',
|
|
40162
40317
|
normalizeLineEndings: false,
|
|
40163
40318
|
allowCredits: options.allowCredits,
|
|
40164
40319
|
autoPush: false,
|
|
@@ -43756,6 +43911,8 @@ function $initializeCoderRunCommand(program) {
|
|
|
43756
43911
|
Features:
|
|
43757
43912
|
- Automatically stages and commits changes with agent identity unless --no-commit is used
|
|
43758
43913
|
- Commits only the prompt file and the files the coding agent has changed, leaving unrelated changes alone
|
|
43914
|
+
- Refuses to start on a dirty working tree unless --git-changes says what should happen with the changes
|
|
43915
|
+
- Optional --git-changes continue resumes the single prompt an interrupted coder left in the [^] status
|
|
43759
43916
|
- Optional post-commit git push with explicit --auto-push opt-in
|
|
43760
43917
|
- Optional pre-prompt git pull with explicit --auto-pull opt-in
|
|
43761
43918
|
- Optional --isolate runs every prompt in its own temporary git worktree and merges it back when verified
|
|
@@ -43836,7 +43993,7 @@ function $initializeCoderRunCommand(program) {
|
|
|
43836
43993
|
waitBetweenPrompts,
|
|
43837
43994
|
waitAfterError,
|
|
43838
43995
|
noCommit: runnerOptions.noCommit,
|
|
43839
|
-
|
|
43996
|
+
gitChanges: runnerOptions.gitChanges,
|
|
43840
43997
|
agentName: runnerOptions.agentName,
|
|
43841
43998
|
model: runnerOptions.model,
|
|
43842
43999
|
agent,
|
|
@@ -43977,7 +44134,7 @@ function $initializeCoderServerCommand(program) {
|
|
|
43977
44134
|
waitBetweenPrompts,
|
|
43978
44135
|
waitAfterError,
|
|
43979
44136
|
noCommit: runnerOptions.noCommit,
|
|
43980
|
-
|
|
44137
|
+
gitChanges: runnerOptions.gitChanges,
|
|
43981
44138
|
agentName: runnerOptions.agentName,
|
|
43982
44139
|
model: runnerOptions.model,
|
|
43983
44140
|
agent,
|
|
@@ -67408,7 +67565,7 @@ function createPromptRunnerOptions(options) {
|
|
|
67408
67565
|
waitBetweenPrompts: 0,
|
|
67409
67566
|
waitAfterError: 0,
|
|
67410
67567
|
noCommit: true,
|
|
67411
|
-
|
|
67568
|
+
gitChanges: 'ignore',
|
|
67412
67569
|
normalizeLineEndings: false,
|
|
67413
67570
|
allowCredits: options.allowCredits,
|
|
67414
67571
|
isVerbose: options.isVerbose,
|
|
@@ -73201,13 +73358,13 @@ function isPromptToBeWritten(file, section) {
|
|
|
73201
73358
|
}
|
|
73202
73359
|
|
|
73203
73360
|
/**
|
|
73204
|
-
* Lists
|
|
73361
|
+
* Lists prompts in one checklist status across all files.
|
|
73205
73362
|
*/
|
|
73206
|
-
function
|
|
73363
|
+
function listPromptsByStatus(files, status) {
|
|
73207
73364
|
const prompts = [];
|
|
73208
73365
|
for (const file of files) {
|
|
73209
73366
|
for (const section of file.sections) {
|
|
73210
|
-
if (section.status ===
|
|
73367
|
+
if (section.status === status) {
|
|
73211
73368
|
prompts.push({ file, section });
|
|
73212
73369
|
}
|
|
73213
73370
|
}
|
|
@@ -73215,6 +73372,13 @@ function listTodoPrompts(files) {
|
|
|
73215
73372
|
return prompts;
|
|
73216
73373
|
}
|
|
73217
73374
|
|
|
73375
|
+
/**
|
|
73376
|
+
* Lists todo prompts across all files.
|
|
73377
|
+
*/
|
|
73378
|
+
function listTodoPrompts(files) {
|
|
73379
|
+
return listPromptsByStatus(files, 'todo');
|
|
73380
|
+
}
|
|
73381
|
+
|
|
73218
73382
|
/**
|
|
73219
73383
|
* Lists todo prompts that still contain authoring placeholders.
|
|
73220
73384
|
*/
|
|
@@ -73792,7 +73956,7 @@ const DEFAULT_WAIT_AFTER_ERROR_MS = 10 * 60 * 1000;
|
|
|
73792
73956
|
/**
|
|
73793
73957
|
* CLI usage text for this script.
|
|
73794
73958
|
*/
|
|
73795
|
-
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] [--
|
|
73959
|
+
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]';
|
|
73796
73960
|
/**
|
|
73797
73961
|
* Top-level flags supported by this command.
|
|
73798
73962
|
*/
|
|
@@ -73819,7 +73983,7 @@ const KNOWN_OPTION_FLAGS = new Set([
|
|
|
73819
73983
|
'--wait-after-error',
|
|
73820
73984
|
'--no-auto',
|
|
73821
73985
|
'--no-commit',
|
|
73822
|
-
'--
|
|
73986
|
+
'--git-changes',
|
|
73823
73987
|
'--no-normalize-line-endings',
|
|
73824
73988
|
'--auto-push',
|
|
73825
73989
|
'--auto-pull',
|
|
@@ -73863,7 +74027,8 @@ function parseRunOptions(args) {
|
|
|
73863
74027
|
const hasLimitFlag = args.includes('--limit');
|
|
73864
74028
|
const limit = parseLimit(readOptionValue(args, '--limit'), hasLimitFlag);
|
|
73865
74029
|
const noCommit = args.includes('--no-commit');
|
|
73866
|
-
const
|
|
74030
|
+
const hasGitChangesFlag = args.includes('--git-changes');
|
|
74031
|
+
const gitChanges = parseGitChangesOption(readOptionValue(args, '--git-changes'), hasGitChangesFlag);
|
|
73867
74032
|
const normalizeLineEndings = !args.includes('--no-normalize-line-endings');
|
|
73868
74033
|
const allowCredits = args.includes('--allow-credits');
|
|
73869
74034
|
const autoMigrate = args.includes('--auto-migrate');
|
|
@@ -73901,7 +74066,7 @@ function parseRunOptions(args) {
|
|
|
73901
74066
|
waitBetweenPrompts,
|
|
73902
74067
|
waitAfterError,
|
|
73903
74068
|
noCommit,
|
|
73904
|
-
|
|
74069
|
+
gitChanges,
|
|
73905
74070
|
normalizeLineEndings,
|
|
73906
74071
|
allowCredits,
|
|
73907
74072
|
autoMigrate,
|
|
@@ -73939,6 +74104,21 @@ function parseTestBeforeOption(value, hasTestBeforeFlag) {
|
|
|
73939
74104
|
}
|
|
73940
74105
|
return value;
|
|
73941
74106
|
}
|
|
74107
|
+
/**
|
|
74108
|
+
* Parses and validates the requested behavior for a dirty working tree.
|
|
74109
|
+
*/
|
|
74110
|
+
function parseGitChangesOption(value, hasGitChangesFlag) {
|
|
74111
|
+
if (value === undefined) {
|
|
74112
|
+
if (hasGitChangesFlag) {
|
|
74113
|
+
exitWithUsageError(`Missing value for --git-changes. Use one of: ${GIT_CHANGES_MODE_VALUES.join(', ')}.`);
|
|
74114
|
+
}
|
|
74115
|
+
return DEFAULT_GIT_CHANGES_MODE;
|
|
74116
|
+
}
|
|
74117
|
+
if (!isGitChangesMode(value)) {
|
|
74118
|
+
exitWithUsageError(`Invalid value for --git-changes: "${value}". Use one of: ${GIT_CHANGES_MODE_VALUES.join(', ')}.`);
|
|
74119
|
+
}
|
|
74120
|
+
return value;
|
|
74121
|
+
}
|
|
73942
74122
|
/**
|
|
73943
74123
|
* Reads a duration-typed CLI flag, applying the provided default when the flag is absent.
|
|
73944
74124
|
*/
|
|
@@ -74396,27 +74576,19 @@ async function sleepWithCountdown(options) {
|
|
|
74396
74576
|
return;
|
|
74397
74577
|
}
|
|
74398
74578
|
const deadlineTimeMs = (_a = options.deadlineTimeMs) !== null && _a !== void 0 ? _a : Date.now() + durationMs;
|
|
74399
|
-
|
|
74400
|
-
|
|
74401
|
-
|
|
74402
|
-
|
|
74403
|
-
|
|
74404
|
-
|
|
74405
|
-
|
|
74406
|
-
|
|
74407
|
-
|
|
74408
|
-
|
|
74409
|
-
|
|
74410
|
-
|
|
74411
|
-
|
|
74412
|
-
}
|
|
74413
|
-
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`${statusMessage}...`);
|
|
74414
|
-
},
|
|
74415
|
-
});
|
|
74416
|
-
}
|
|
74417
|
-
finally {
|
|
74418
|
-
finishSkippableWait(waitToken);
|
|
74419
|
-
}
|
|
74579
|
+
await waitForSkippableWorldTimeDeadline({
|
|
74580
|
+
deadlineTimeMs,
|
|
74581
|
+
pollIntervalMs: WAIT_COUNTDOWN_UPDATE_INTERVAL_MS,
|
|
74582
|
+
onTick: (remainingDurationMs) => {
|
|
74583
|
+
const visibleRemainingDurationMs = Math.min(remainingDurationMs, durationMs);
|
|
74584
|
+
const statusMessage = describeCoderRunWait(waitKind, visibleRemainingDurationMs, durationMs);
|
|
74585
|
+
if (!isRichUiEnabled) {
|
|
74586
|
+
console.info(colors.gray(`${statusMessage}...`));
|
|
74587
|
+
return;
|
|
74588
|
+
}
|
|
74589
|
+
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`${statusMessage}...`);
|
|
74590
|
+
},
|
|
74591
|
+
});
|
|
74420
74592
|
}
|
|
74421
74593
|
|
|
74422
74594
|
/**
|
|
@@ -74431,6 +74603,36 @@ async function resolveCoderContext(contextReference, currentWorkingDirectory) {
|
|
|
74431
74603
|
});
|
|
74432
74604
|
}
|
|
74433
74605
|
|
|
74606
|
+
/**
|
|
74607
|
+
* Listens for the terminal control keys while the plain console output is used.
|
|
74608
|
+
*
|
|
74609
|
+
* Every recognized key press is answered with one printed line, because the plain mode has no frame
|
|
74610
|
+
* which could show the new state on its own. The rich terminal UI installs its own listener in
|
|
74611
|
+
* [`renderCoderRunUi`](../ui/renderCoderRunUi.ts) and shares the very same key handling through
|
|
74612
|
+
* [`applyCoderRunControlKey`](./applyCoderRunControlKey.ts).
|
|
74613
|
+
*
|
|
74614
|
+
* @private internal utility of `ptbk coder` terminal controls
|
|
74615
|
+
*/
|
|
74616
|
+
function listenForCoderRunControls() {
|
|
74617
|
+
if (!process.stdin.isTTY) {
|
|
74618
|
+
return;
|
|
74619
|
+
}
|
|
74620
|
+
readline.emitKeypressEvents(process.stdin);
|
|
74621
|
+
process.stdin.setRawMode(true);
|
|
74622
|
+
process.stdin.on('keypress', (_pressedText, key) => {
|
|
74623
|
+
if (key.ctrl && key.name === 'c') {
|
|
74624
|
+
process.exit();
|
|
74625
|
+
}
|
|
74626
|
+
const controlFeedback = applyCoderRunControlKey(key.name);
|
|
74627
|
+
if (controlFeedback === undefined) {
|
|
74628
|
+
return;
|
|
74629
|
+
}
|
|
74630
|
+
// Note: Using console.log here which adds a new line.
|
|
74631
|
+
// This is intentional to prevent the message from being overwritten.
|
|
74632
|
+
console.log(formatCoderRunControlFeedback(controlFeedback));
|
|
74633
|
+
});
|
|
74634
|
+
}
|
|
74635
|
+
|
|
74434
74636
|
/**
|
|
74435
74637
|
* Checks working tree clean.
|
|
74436
74638
|
*/
|
|
@@ -74449,12 +74651,14 @@ async function isWorkingTreeClean(path) {
|
|
|
74449
74651
|
async function ensureWorkingTreeClean() {
|
|
74450
74652
|
const isClean = await isWorkingTreeClean(process.cwd());
|
|
74451
74653
|
if (!isClean) {
|
|
74452
|
-
throw new
|
|
74654
|
+
throw new NotAllowed(spaceTrim$1(`
|
|
74453
74655
|
Git working tree is not clean.
|
|
74454
74656
|
|
|
74455
74657
|
Please commit or stash your changes before running this script
|
|
74456
|
-
OR
|
|
74457
|
-
|
|
74658
|
+
OR decide what should happen with them:
|
|
74659
|
+
|
|
74660
|
+
- \`--git-changes ignore\` leaves the changes where they are and starts the next prompt anyway
|
|
74661
|
+
- \`--git-changes continue\` resumes the single prompt which was left in the middle of its implementation
|
|
74458
74662
|
|
|
74459
74663
|
Aborting
|
|
74460
74664
|
`));
|
|
@@ -76021,6 +76225,51 @@ function formatPromptAttemptMetadata(status, attemptCount) {
|
|
|
76021
76225
|
return `(failed after ${attemptCount} attempts) `;
|
|
76022
76226
|
}
|
|
76023
76227
|
|
|
76228
|
+
/**
|
|
76229
|
+
* Separator which introduces the harness that started a prompt another harness has taken over.
|
|
76230
|
+
*
|
|
76231
|
+
* It is used both for writing and for reading the attribution, so the status line of a continued prompt
|
|
76232
|
+
* stays parseable when yet another harness continues it later.
|
|
76233
|
+
*/
|
|
76234
|
+
const STARTED_BY_SEPARATOR = ', started by ';
|
|
76235
|
+
/**
|
|
76236
|
+
* Matches the attribution of one status line, which reaches from `by ` up to the step summary.
|
|
76237
|
+
*/
|
|
76238
|
+
const PROMPT_RUNNER_ATTRIBUTION_PATTERN = /(?:^|\s)by (?<attribution>.*?)(?: - |$)/u;
|
|
76239
|
+
/**
|
|
76240
|
+
* Formats who worked on one prompt for its status line.
|
|
76241
|
+
*
|
|
76242
|
+
* Produces `` by Claude Code `claude-opus-5` `` for a prompt implemented by a single harness and
|
|
76243
|
+
* `` by Claude Code `claude-opus-5`, started by OpenAI Codex `gpt-5.6-luna` `` for a prompt which one
|
|
76244
|
+
* harness started and another one continued through `--git-changes continue`.
|
|
76245
|
+
*/
|
|
76246
|
+
function formatPromptRunnerAttribution(options) {
|
|
76247
|
+
const { currentRunnerSignature, startedByRunnerSignature } = options;
|
|
76248
|
+
if (startedByRunnerSignature === undefined || startedByRunnerSignature === currentRunnerSignature) {
|
|
76249
|
+
return `by ${currentRunnerSignature}`;
|
|
76250
|
+
}
|
|
76251
|
+
return `by ${currentRunnerSignature}${STARTED_BY_SEPARATOR}${startedByRunnerSignature}`;
|
|
76252
|
+
}
|
|
76253
|
+
/**
|
|
76254
|
+
* Reads which harness started the work recorded on one prompt status line.
|
|
76255
|
+
*
|
|
76256
|
+
* A line written by a single harness names that harness, and a line of a prompt which was already taken
|
|
76257
|
+
* over keeps naming the harness which started it, so the original author survives any number of takeovers.
|
|
76258
|
+
*/
|
|
76259
|
+
function parsePromptStartedByRunnerSignature(statusLine) {
|
|
76260
|
+
var _a, _b, _c;
|
|
76261
|
+
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();
|
|
76262
|
+
if (!attribution) {
|
|
76263
|
+
return undefined;
|
|
76264
|
+
}
|
|
76265
|
+
const startedBySeparatorIndex = attribution.indexOf(STARTED_BY_SEPARATOR);
|
|
76266
|
+
if (startedBySeparatorIndex === -1) {
|
|
76267
|
+
return attribution;
|
|
76268
|
+
}
|
|
76269
|
+
return attribution.slice(startedBySeparatorIndex + STARTED_BY_SEPARATOR.length).trim() || undefined;
|
|
76270
|
+
}
|
|
76271
|
+
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
76272
|
+
|
|
76024
76273
|
/**
|
|
76025
76274
|
* Builds the shared body of a prompt status line, used by both the in-progress `[^]` and the done `[x]` status.
|
|
76026
76275
|
*
|
|
@@ -76028,14 +76277,18 @@ function formatPromptAttemptMetadata(status, attemptCount) {
|
|
|
76028
76277
|
* ``by OpenAI Codex `gpt-5.6-luna` thinking `max` (ChatGPT account) - Implementation ~$0.2036 10 minutes``.
|
|
76029
76278
|
*/
|
|
76030
76279
|
function buildPromptStatusDetails(options) {
|
|
76031
|
-
const { steps, inProgressStepKind, runnerName, modelName, attemptCount, loginMethod, thinkingLevel } = options;
|
|
76280
|
+
const { steps, inProgressStepKind, runnerName, modelName, startedByRunnerSignature, attemptCount, loginMethod, thinkingLevel, } = options;
|
|
76032
76281
|
const runnerSignature = formatRunnerSignature(runnerName, modelName, thinkingLevel);
|
|
76033
76282
|
const attemptMetadata = formatPromptAttemptMetadata('done', attemptCount);
|
|
76034
76283
|
const loginMethodLabel = formatCodexLoginMethod(loginMethod);
|
|
76035
76284
|
const loginMethodSuffix = loginMethodLabel ? ` (${loginMethodLabel})` : '';
|
|
76285
|
+
const attribution = formatPromptRunnerAttribution({
|
|
76286
|
+
currentRunnerSignature: `${runnerSignature}${loginMethodSuffix}`,
|
|
76287
|
+
startedByRunnerSignature,
|
|
76288
|
+
});
|
|
76036
76289
|
const stepsSummary = formatCoderRunSteps(steps, inProgressStepKind);
|
|
76037
76290
|
const stepsSuffix = stepsSummary === '' ? '' : ` - ${stepsSummary}`;
|
|
76038
|
-
return `${attemptMetadata}
|
|
76291
|
+
return `${attemptMetadata}${attribution}${stepsSuffix}`;
|
|
76039
76292
|
}
|
|
76040
76293
|
|
|
76041
76294
|
/**
|
|
@@ -76078,29 +76331,26 @@ function writePromptStatusLine(file, section, replacementStatusLine) {
|
|
|
76078
76331
|
/**
|
|
76079
76332
|
* Marks a prompt section as done and records the per-step usage pricing and runner details.
|
|
76080
76333
|
*/
|
|
76081
|
-
function markPromptDone(
|
|
76082
|
-
const
|
|
76083
|
-
|
|
76084
|
-
runnerName,
|
|
76085
|
-
modelName,
|
|
76086
|
-
attemptCount,
|
|
76087
|
-
loginMethod,
|
|
76088
|
-
thinkingLevel,
|
|
76089
|
-
});
|
|
76090
|
-
writePromptStatusLine(file, section, `[x] ${statusDetails}`);
|
|
76334
|
+
function markPromptDone(options) {
|
|
76335
|
+
const { file, section, ...statusDetailsOptions } = options;
|
|
76336
|
+
writePromptStatusLine(file, section, `[x] ${buildPromptStatusDetails(statusDetailsOptions)}`);
|
|
76091
76337
|
}
|
|
76092
76338
|
|
|
76093
76339
|
/**
|
|
76094
76340
|
* Marks a prompt section as failed and records runner details.
|
|
76095
76341
|
*/
|
|
76096
|
-
function markPromptFailed(
|
|
76097
|
-
const
|
|
76342
|
+
function markPromptFailed(options) {
|
|
76343
|
+
const { file, section, runnerName, modelName, startedByRunnerSignature, promptExecutionStartedDate, attemptCount } = options;
|
|
76344
|
+
const attribution = formatPromptRunnerAttribution({
|
|
76345
|
+
currentRunnerSignature: formatRunnerSignature(runnerName, modelName),
|
|
76346
|
+
startedByRunnerSignature,
|
|
76347
|
+
});
|
|
76098
76348
|
const attemptMetadata = formatPromptAttemptMetadata('failed', attemptCount);
|
|
76099
76349
|
const duration = moment().diff(promptExecutionStartedDate);
|
|
76100
76350
|
const durationString = moment.duration(duration).humanize();
|
|
76101
76351
|
const failureDetails = attemptMetadata === ''
|
|
76102
|
-
? `failed after ${durationString}
|
|
76103
|
-
: `${attemptMetadata}${durationString}
|
|
76352
|
+
? `failed after ${durationString} ${attribution}`
|
|
76353
|
+
: `${attemptMetadata}${durationString} ${attribution}`;
|
|
76104
76354
|
writePromptStatusLine(file, section, `[!] ${failureDetails}`);
|
|
76105
76355
|
}
|
|
76106
76356
|
|
|
@@ -76192,6 +76442,12 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76192
76442
|
const codexPrompt = appendCoderContext(promptWithAgent, resolvedCoderContext);
|
|
76193
76443
|
// Note: Temporary scripts and runtime logs stay in the original project so they outlive an isolated worktree
|
|
76194
76444
|
const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
|
|
76445
|
+
// Note: Read before the first `[^]` rewrite of this round, which would overwrite the harness recorded so far
|
|
76446
|
+
const startedByRunnerSignature = resolveStartedByRunnerSignature({
|
|
76447
|
+
nextPrompt,
|
|
76448
|
+
runnerMetadata,
|
|
76449
|
+
thinkingLevel: options.thinkingLevel,
|
|
76450
|
+
});
|
|
76195
76451
|
setPromptRoundRunningState({ isRichUiEnabled, promptLabel, scriptPath, uiHandle });
|
|
76196
76452
|
await waitForRequestedPause({
|
|
76197
76453
|
checkpointLabel: 'preparing the current prompt execution',
|
|
@@ -76224,6 +76480,7 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76224
76480
|
onStepStarted: (progress) => recordPromptRoundInProgress({
|
|
76225
76481
|
nextPrompt,
|
|
76226
76482
|
runnerMetadata,
|
|
76483
|
+
startedByRunnerSignature,
|
|
76227
76484
|
thinkingLevel: options.thinkingLevel,
|
|
76228
76485
|
attemptCount,
|
|
76229
76486
|
progress,
|
|
@@ -76234,6 +76491,7 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76234
76491
|
options,
|
|
76235
76492
|
nextPrompt,
|
|
76236
76493
|
runnerMetadata,
|
|
76494
|
+
startedByRunnerSignature,
|
|
76237
76495
|
promptExecutionStartedDate,
|
|
76238
76496
|
result,
|
|
76239
76497
|
commitMessage,
|
|
@@ -76267,6 +76525,7 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76267
76525
|
await finalizeFailedPromptRound({
|
|
76268
76526
|
nextPrompt,
|
|
76269
76527
|
runnerMetadata,
|
|
76528
|
+
startedByRunnerSignature,
|
|
76270
76529
|
promptExecutionStartedDate,
|
|
76271
76530
|
attemptCount,
|
|
76272
76531
|
error: lastError,
|
|
@@ -76279,6 +76538,23 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76279
76538
|
throw lastError;
|
|
76280
76539
|
}, { preserveArtifactsOnSuccess: options.preserveLogs });
|
|
76281
76540
|
}
|
|
76541
|
+
/**
|
|
76542
|
+
* Reads which harness left the prompt of this round in the middle of its implementation.
|
|
76543
|
+
*
|
|
76544
|
+
* Only a prompt resumed through `--git-changes continue` still carries the in-progress `[^]` status when its
|
|
76545
|
+
* round starts, so every other round reports no earlier harness. A harness which continues its own work is
|
|
76546
|
+
* reported as no earlier harness either, because the status line would just name it twice.
|
|
76547
|
+
*/
|
|
76548
|
+
function resolveStartedByRunnerSignature(options) {
|
|
76549
|
+
const { nextPrompt, runnerMetadata, thinkingLevel } = options;
|
|
76550
|
+
if (nextPrompt.section.status !== 'in-progress') {
|
|
76551
|
+
return undefined;
|
|
76552
|
+
}
|
|
76553
|
+
const { line } = resolvePromptStatusLine(nextPrompt.file, nextPrompt.section);
|
|
76554
|
+
const startedByRunnerSignature = parsePromptStartedByRunnerSignature(line);
|
|
76555
|
+
const currentRunnerSignature = formatRunnerSignature(runnerMetadata.runnerName, runnerMetadata.modelName, thinkingLevel);
|
|
76556
|
+
return startedByRunnerSignature === currentRunnerSignature ? undefined : startedByRunnerSignature;
|
|
76557
|
+
}
|
|
76282
76558
|
/**
|
|
76283
76559
|
* Records into the prompt file that the prompt is being implemented right now.
|
|
76284
76560
|
*
|
|
@@ -76287,7 +76563,7 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76287
76563
|
* crashes leaves the `[^]` status behind as the signal that this task was left in the middle.
|
|
76288
76564
|
*/
|
|
76289
76565
|
async function recordPromptRoundInProgress(options) {
|
|
76290
|
-
const { nextPrompt, runnerMetadata, thinkingLevel, attemptCount, progress } = options;
|
|
76566
|
+
const { nextPrompt, runnerMetadata, startedByRunnerSignature, thinkingLevel, attemptCount, progress } = options;
|
|
76291
76567
|
markPromptInProgress({
|
|
76292
76568
|
file: nextPrompt.file,
|
|
76293
76569
|
section: nextPrompt.section,
|
|
@@ -76295,6 +76571,7 @@ async function recordPromptRoundInProgress(options) {
|
|
|
76295
76571
|
inProgressStepKind: progress.startedStepKind,
|
|
76296
76572
|
runnerName: runnerMetadata.runnerName,
|
|
76297
76573
|
modelName: runnerMetadata.modelName,
|
|
76574
|
+
startedByRunnerSignature,
|
|
76298
76575
|
attemptCount,
|
|
76299
76576
|
loginMethod: progress.loginMethod,
|
|
76300
76577
|
thinkingLevel,
|
|
@@ -76352,14 +76629,24 @@ function setPromptRoundRunningState(options) {
|
|
|
76352
76629
|
* Finalizes a successful prompt round, including prompt bookkeeping and commit flow.
|
|
76353
76630
|
*/
|
|
76354
76631
|
async function finalizeSuccessfulPromptRound(options) {
|
|
76355
|
-
const { options: runOptions, nextPrompt, runnerMetadata, promptExecutionStartedDate, result, commitMessage, logPath, roundCommitScope, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76632
|
+
const { options: runOptions, nextPrompt, runnerMetadata, startedByRunnerSignature, promptExecutionStartedDate, result, commitMessage, logPath, roundCommitScope, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76356
76633
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
|
|
76357
76634
|
await waitForRequestedPause({
|
|
76358
76635
|
checkpointLabel: 'recording the successful prompt result',
|
|
76359
76636
|
phase: 'running',
|
|
76360
76637
|
statusMessage: 'Recording prompt result',
|
|
76361
76638
|
});
|
|
76362
|
-
markPromptDone(
|
|
76639
|
+
markPromptDone({
|
|
76640
|
+
file: nextPrompt.file,
|
|
76641
|
+
section: nextPrompt.section,
|
|
76642
|
+
steps: result.steps,
|
|
76643
|
+
runnerName: runnerMetadata.runnerName,
|
|
76644
|
+
modelName: runnerMetadata.modelName,
|
|
76645
|
+
startedByRunnerSignature,
|
|
76646
|
+
attemptCount: result.attemptCount,
|
|
76647
|
+
loginMethod: result.loginMethod,
|
|
76648
|
+
thinkingLevel: runOptions.thinkingLevel,
|
|
76649
|
+
});
|
|
76363
76650
|
// Note: The prompt status is always written into the original project, an isolated round transports
|
|
76364
76651
|
// its own changes back through the merge instead
|
|
76365
76652
|
await writePromptFile(nextPrompt.file);
|
|
@@ -76410,7 +76697,7 @@ async function finalizeSuccessfulPromptRound(options) {
|
|
|
76410
76697
|
* Finalizes a failed prompt round, persisting prompt failure metadata before rethrowing.
|
|
76411
76698
|
*/
|
|
76412
76699
|
async function finalizeFailedPromptRound(options) {
|
|
76413
|
-
const { nextPrompt, runnerMetadata, promptExecutionStartedDate, attemptCount, error, options: runOptions, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76700
|
+
const { nextPrompt, runnerMetadata, startedByRunnerSignature, promptExecutionStartedDate, attemptCount, error, options: runOptions, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76414
76701
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
|
|
76415
76702
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
|
|
76416
76703
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
|
|
@@ -76419,7 +76706,15 @@ async function finalizeFailedPromptRound(options) {
|
|
|
76419
76706
|
phase: 'error',
|
|
76420
76707
|
statusMessage: 'Recording prompt failure',
|
|
76421
76708
|
});
|
|
76422
|
-
markPromptFailed(
|
|
76709
|
+
markPromptFailed({
|
|
76710
|
+
file: nextPrompt.file,
|
|
76711
|
+
section: nextPrompt.section,
|
|
76712
|
+
runnerName: runnerMetadata.runnerName,
|
|
76713
|
+
modelName: runnerMetadata.modelName,
|
|
76714
|
+
startedByRunnerSignature,
|
|
76715
|
+
promptExecutionStartedDate,
|
|
76716
|
+
attemptCount,
|
|
76717
|
+
});
|
|
76423
76718
|
await writePromptFile(nextPrompt.file);
|
|
76424
76719
|
await writePromptErrorLog({
|
|
76425
76720
|
file: nextPrompt.file,
|
|
@@ -77172,6 +77467,50 @@ function printUpcomingTasks(tasks) {
|
|
|
77172
77467
|
}
|
|
77173
77468
|
}
|
|
77174
77469
|
|
|
77470
|
+
/**
|
|
77471
|
+
* Builds a label that identifies the prompt section by index.
|
|
77472
|
+
*/
|
|
77473
|
+
function buildPromptLabel(file, section) {
|
|
77474
|
+
return `${relative(process.cwd(), file.path).replace(/\\/g, '/')}#${section.index + 1}`;
|
|
77475
|
+
}
|
|
77476
|
+
|
|
77477
|
+
/**
|
|
77478
|
+
* Resolves the one prompt which `--git-changes continue` resumes.
|
|
77479
|
+
*
|
|
77480
|
+
* A coder which was killed or crashed leaves exactly one prompt behind in the in-progress `[^]` status,
|
|
77481
|
+
* so anything else means the working tree changes cannot be attributed to a single interrupted prompt.
|
|
77482
|
+
*/
|
|
77483
|
+
function resolveInterruptedPrompt(files) {
|
|
77484
|
+
const interruptedPrompts = listPromptsByStatus(files, 'in-progress');
|
|
77485
|
+
if (interruptedPrompts.length === 0) {
|
|
77486
|
+
throw new NotFoundError(spaceTrim$1(`
|
|
77487
|
+
Flag \`--git-changes continue\` found no interrupted prompt to continue.
|
|
77488
|
+
|
|
77489
|
+
Continuing resumes the prompt an earlier coder left in the middle of its implementation, which is the
|
|
77490
|
+
one marked with the in-progress \`[^]\` status. No prompt carries that status right now.
|
|
77491
|
+
|
|
77492
|
+
Use \`--git-changes ignore\` to start the next \`[ ]\` prompt with the current working tree changes
|
|
77493
|
+
left in place, or \`--git-changes fail\` to commit or stash them first.
|
|
77494
|
+
`));
|
|
77495
|
+
}
|
|
77496
|
+
if (interruptedPrompts.length > 1) {
|
|
77497
|
+
throw new NotAllowed(spaceTrim$1((block) => `
|
|
77498
|
+
Flag \`--git-changes continue\` found ${interruptedPrompts.length} interrupted prompts, but it can
|
|
77499
|
+
continue only one.
|
|
77500
|
+
|
|
77501
|
+
The working tree changes cannot be attributed to a single prompt while more than one of them carries
|
|
77502
|
+
the in-progress \`[^]\` status. Resolve the extra ones by marking them \`[x]\`, \`[!]\` or \`[ ]\`
|
|
77503
|
+
and run the coder again.
|
|
77504
|
+
|
|
77505
|
+
Interrupted prompts:
|
|
77506
|
+
${block(interruptedPrompts
|
|
77507
|
+
.map(({ file, section }) => `- \`${buildPromptLabel(file, section)}\``)
|
|
77508
|
+
.join('\n'))}
|
|
77509
|
+
`));
|
|
77510
|
+
}
|
|
77511
|
+
return interruptedPrompts[0];
|
|
77512
|
+
}
|
|
77513
|
+
|
|
77175
77514
|
/**
|
|
77176
77515
|
* Prints a summary of the prompt that will run next.
|
|
77177
77516
|
*/
|
|
@@ -77238,7 +77577,7 @@ function $detectTerminalAnsiColorDepth() {
|
|
|
77238
77577
|
* visuals are rendered through the shared canvas avatar pipeline into terminal ASCII art, using a
|
|
77239
77578
|
* transparent horizontal canvas instead of the website's framed 1:1 surface.
|
|
77240
77579
|
*
|
|
77241
|
-
* The visual is decorative, so any failure returns `null` and the caller
|
|
77580
|
+
* The visual is decorative, so any failure returns `null` and the caller renders no header visual at all.
|
|
77242
77581
|
*
|
|
77243
77582
|
* @param agentSource Source of the `--agent` book file.
|
|
77244
77583
|
* @returns ANSI-colored ASCII-art renderer or `null` when the visual cannot be rendered.
|
|
@@ -77400,6 +77739,8 @@ async function runCodexPrompts(providedOptions) {
|
|
|
77400
77739
|
let previousRoundEndTime;
|
|
77401
77740
|
let completedRunCount = 0;
|
|
77402
77741
|
let hasRunTestBefore = false;
|
|
77742
|
+
// Note: Only the very first round resumes the interrupted prompt, every later round starts from a clean tree
|
|
77743
|
+
let isContinuingInterruptedPrompt = options.gitChanges === 'continue';
|
|
77403
77744
|
while (just(true)) {
|
|
77404
77745
|
if (options.autoPull && !options.dryRun) {
|
|
77405
77746
|
await waitForRequestedPause({
|
|
@@ -77424,6 +77765,7 @@ async function runCodexPrompts(providedOptions) {
|
|
|
77424
77765
|
progressDisplay,
|
|
77425
77766
|
uiHandle,
|
|
77426
77767
|
promptRunnerIdentity,
|
|
77768
|
+
isContinuingInterruptedPrompt,
|
|
77427
77769
|
});
|
|
77428
77770
|
}
|
|
77429
77771
|
if (!hasRunTestBefore) {
|
|
@@ -77438,6 +77780,7 @@ async function runCodexPrompts(providedOptions) {
|
|
|
77438
77780
|
uiHandle,
|
|
77439
77781
|
waitForRequestedPause,
|
|
77440
77782
|
hasWaitedForStart,
|
|
77783
|
+
isContinuingInterruptedPrompt,
|
|
77441
77784
|
});
|
|
77442
77785
|
hasRunTestBefore = true;
|
|
77443
77786
|
}
|
|
@@ -77452,6 +77795,7 @@ async function runCodexPrompts(providedOptions) {
|
|
|
77452
77795
|
progressDisplay,
|
|
77453
77796
|
uiHandle,
|
|
77454
77797
|
promptRunnerIdentity,
|
|
77798
|
+
isContinuingInterruptedPrompt,
|
|
77455
77799
|
});
|
|
77456
77800
|
hasShownUpcomingTasks || (hasShownUpcomingTasks = showUpcomingTasksOnce({
|
|
77457
77801
|
hasShownUpcomingTasks,
|
|
@@ -77472,7 +77816,12 @@ async function runCodexPrompts(providedOptions) {
|
|
|
77472
77816
|
}
|
|
77473
77817
|
if (options.keepAlive) {
|
|
77474
77818
|
announceKeepAliveStatus(promptQueueSnapshot, isRichUiEnabled, uiHandle);
|
|
77475
|
-
|
|
77819
|
+
// Note: The keep-alive poll runs in the `waiting` phase, where `S Skip current waiting`
|
|
77820
|
+
// is offered, so pressing `S` looks for new prompts right away
|
|
77821
|
+
await waitForSkippableWorldTimeDeadline({
|
|
77822
|
+
deadlineTimeMs: Date.now() + KEEP_ALIVE_POLL_INTERVAL_MS,
|
|
77823
|
+
pollIntervalMs: KEEP_ALIVE_POLL_INTERVAL_MS,
|
|
77824
|
+
});
|
|
77476
77825
|
continue;
|
|
77477
77826
|
}
|
|
77478
77827
|
finishWhenNoPromptIsAvailable(promptQueueSnapshot, isRichUiEnabled, uiHandle);
|
|
@@ -77508,7 +77857,7 @@ async function runCodexPrompts(providedOptions) {
|
|
|
77508
77857
|
progressDisplay,
|
|
77509
77858
|
uiHandle,
|
|
77510
77859
|
});
|
|
77511
|
-
if (
|
|
77860
|
+
if (isCleanWorkingTreeRequired(options.gitChanges, isContinuingInterruptedPrompt)) {
|
|
77512
77861
|
await waitForRequestedPause({
|
|
77513
77862
|
checkpointLabel: 'checking the git working tree',
|
|
77514
77863
|
phase: 'loading',
|
|
@@ -77532,6 +77881,7 @@ async function runCodexPrompts(providedOptions) {
|
|
|
77532
77881
|
uiHandle,
|
|
77533
77882
|
waitForRequestedPause,
|
|
77534
77883
|
});
|
|
77884
|
+
isContinuingInterruptedPrompt = false;
|
|
77535
77885
|
previousRoundStartTime = currentRoundStartTime;
|
|
77536
77886
|
previousRoundEndTime = Date.now();
|
|
77537
77887
|
completedRunCount += 1;
|
|
@@ -77575,9 +77925,9 @@ function validateRunCodexPromptOptions(options) {
|
|
|
77575
77925
|
Flag \`--allow-destructive-auto-migrate\` requires \`--auto-migrate\`.
|
|
77576
77926
|
`));
|
|
77577
77927
|
}
|
|
77578
|
-
if (options.noCommit && !options.waitForUser &&
|
|
77928
|
+
if (options.noCommit && !options.waitForUser && options.gitChanges !== 'ignore') {
|
|
77579
77929
|
throw new NotAllowed(spaceTrim$1(`
|
|
77580
|
-
Flag \`--no-commit\` requires \`--
|
|
77930
|
+
Flag \`--no-commit\` requires \`--git-changes ignore\` when running in auto mode (the default; pass \`--no-auto\` for interactive confirmation).
|
|
77581
77931
|
|
|
77582
77932
|
Without commits, the next prompt round would fail the clean working tree check.
|
|
77583
77933
|
`));
|
|
@@ -77596,6 +77946,13 @@ function validateRunCodexPromptOptions(options) {
|
|
|
77596
77946
|
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.
|
|
77597
77947
|
`));
|
|
77598
77948
|
}
|
|
77949
|
+
if (options.isIsolated && options.gitChanges === 'continue') {
|
|
77950
|
+
throw new NotAllowed(spaceTrim$1(`
|
|
77951
|
+
Flag \`--isolate\` cannot be combined with \`--git-changes continue\`.
|
|
77952
|
+
|
|
77953
|
+
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.
|
|
77954
|
+
`));
|
|
77955
|
+
}
|
|
77599
77956
|
if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit <= 0)) {
|
|
77600
77957
|
throw new NotAllowed(spaceTrim$1(`
|
|
77601
77958
|
Flag \`--limit\` expects a positive integer.
|
|
@@ -77604,6 +77961,18 @@ function validateRunCodexPromptOptions(options) {
|
|
|
77604
77961
|
`));
|
|
77605
77962
|
}
|
|
77606
77963
|
}
|
|
77964
|
+
/**
|
|
77965
|
+
* Decides whether the working tree has to be verified clean before the next prompt starts.
|
|
77966
|
+
*
|
|
77967
|
+
* `--git-changes continue` waives the check for the single round which resumes the interrupted prompt,
|
|
77968
|
+
* because that round is started exactly for the uncommitted changes the interrupted prompt left behind.
|
|
77969
|
+
*/
|
|
77970
|
+
function isCleanWorkingTreeRequired(gitChanges, isContinuingInterruptedPrompt) {
|
|
77971
|
+
if (gitChanges === 'ignore') {
|
|
77972
|
+
return false;
|
|
77973
|
+
}
|
|
77974
|
+
return !isContinuingInterruptedPrompt;
|
|
77975
|
+
}
|
|
77607
77976
|
/**
|
|
77608
77977
|
* Pulls the latest repository state before loading prompts when the feature is enabled.
|
|
77609
77978
|
*/
|
|
@@ -77662,7 +78031,7 @@ function normalizeRunOptions(options) {
|
|
|
77662
78031
|
* Runs the optional pre-coding verification and, when requested, its one repair prompt.
|
|
77663
78032
|
*/
|
|
77664
78033
|
async function runTestBeforeIfNeeded(options) {
|
|
77665
|
-
const { options: runOptions, runner, runnerMetadata, resolvedCoderContext, resolvedAgentSystemMessage, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, hasWaitedForStart, } = options;
|
|
78034
|
+
const { options: runOptions, runner, runnerMetadata, resolvedCoderContext, resolvedAgentSystemMessage, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, hasWaitedForStart, isContinuingInterruptedPrompt, } = options;
|
|
77666
78035
|
if (runOptions.testBefore === 'no') {
|
|
77667
78036
|
return hasWaitedForStart;
|
|
77668
78037
|
}
|
|
@@ -77673,7 +78042,7 @@ async function runTestBeforeIfNeeded(options) {
|
|
|
77673
78042
|
Pass one with ${'`--test <test-command>`'} or use the default ${'`npm test`'} command by providing the mode through the CLI.
|
|
77674
78043
|
`));
|
|
77675
78044
|
}
|
|
77676
|
-
if (
|
|
78045
|
+
if (isCleanWorkingTreeRequired(runOptions.gitChanges, isContinuingInterruptedPrompt)) {
|
|
77677
78046
|
await waitForRequestedPause({
|
|
77678
78047
|
checkpointLabel: 'checking the git working tree before testing',
|
|
77679
78048
|
phase: 'loading',
|
|
@@ -77842,9 +78211,9 @@ function initializeRunUi(uiHandle, runnerName, actualRunnerModel, options) {
|
|
|
77842
78211
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Running prompts with ${runnerName}`);
|
|
77843
78212
|
}
|
|
77844
78213
|
/**
|
|
77845
|
-
* Prepares the `--agent` book avatar ASCII-art renderer and shows it
|
|
78214
|
+
* Prepares the `--agent` book avatar ASCII-art renderer and shows it above the dashboard boxes.
|
|
77846
78215
|
*
|
|
77847
|
-
*
|
|
78216
|
+
* Leaves the header empty when no agent is selected, the UI is disabled, or the visual cannot be rendered.
|
|
77848
78217
|
*/
|
|
77849
78218
|
async function initializeRunUiAgentVisual(uiHandle, agentSource) {
|
|
77850
78219
|
if (!uiHandle || !agentSource) {
|
|
@@ -77859,7 +78228,7 @@ async function initializeRunUiAgentVisual(uiHandle, agentSource) {
|
|
|
77859
78228
|
* Loads prompt files, updates progress displays, and selects the next runnable prompt.
|
|
77860
78229
|
*/
|
|
77861
78230
|
async function loadPromptQueueSnapshot(options) {
|
|
77862
|
-
const { options: runOptions, isRichUiEnabled, progressDisplay, uiHandle, promptRunnerIdentity } = options;
|
|
78231
|
+
const { options: runOptions, isRichUiEnabled, progressDisplay, uiHandle, promptRunnerIdentity, isContinuingInterruptedPrompt, } = options;
|
|
77863
78232
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentScriptPath(undefined);
|
|
77864
78233
|
const promptFiles = await loadPromptFiles(PROMPTS_DIR$1);
|
|
77865
78234
|
const stats = summarizePrompts(promptFiles, runOptions.priorityFilter);
|
|
@@ -77871,7 +78240,11 @@ async function loadPromptQueueSnapshot(options) {
|
|
|
77871
78240
|
return {
|
|
77872
78241
|
promptFiles,
|
|
77873
78242
|
stats,
|
|
77874
|
-
|
|
78243
|
+
// Note: A resumed prompt is picked by its `[^]` status alone, the priority and runner filters select
|
|
78244
|
+
// which prompt is started next, not which unfinished work is continued
|
|
78245
|
+
nextPrompt: isContinuingInterruptedPrompt
|
|
78246
|
+
? resolveInterruptedPrompt(promptFiles)
|
|
78247
|
+
: findNextTodoPrompt(promptFiles, runOptions.priorityFilter, promptRunnerIdentity),
|
|
77875
78248
|
};
|
|
77876
78249
|
}
|
|
77877
78250
|
/**
|