@promptbook/cli 0.112.0-44 → 0.112.0-46

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.
Files changed (69) hide show
  1. package/esm/index.es.js +868 -524
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/scripts/run-codex-prompts/common/CoderRunTimer.d.ts +31 -0
  4. package/esm/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +23 -0
  5. package/esm/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +13 -4
  6. package/esm/scripts/run-codex-prompts/common/progressFormatting.d.ts +16 -0
  7. package/esm/scripts/run-codex-prompts/common/runGoScript/$runGoScript.d.ts +1 -1
  8. package/esm/scripts/run-codex-prompts/common/runGoScript/$runGoScriptUntilMarkerIdle.d.ts +1 -1
  9. package/esm/scripts/run-codex-prompts/common/runGoScript/$runGoScriptWithOutput.d.ts +1 -1
  10. package/esm/scripts/run-codex-prompts/common/runGoScript/shouldDeleteTemporaryArtifact.d.ts +7 -0
  11. package/esm/scripts/run-codex-prompts/common/runGoScript/withPromptRuntimeLog.d.ts +4 -3
  12. package/esm/scripts/run-codex-prompts/common/runGoScript/withTempScript.d.ts +1 -1
  13. package/esm/scripts/run-codex-prompts/git/commitChanges.d.ts +3 -1
  14. package/esm/scripts/run-codex-prompts/testing/runPromptTestCommand.d.ts +1 -0
  15. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +15 -20
  16. package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +28 -0
  17. package/esm/scripts/run-codex-prompts/ui/coderRunUiRefresh.d.ts +17 -0
  18. package/esm/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +6 -3
  19. package/esm/src/avatars/Avatar.d.ts +7 -0
  20. package/esm/src/avatars/avatarRenderingUtils.d.ts +117 -0
  21. package/esm/src/avatars/index.d.ts +6 -0
  22. package/esm/src/avatars/renderAvatarVisual.d.ts +9 -0
  23. package/esm/src/avatars/types/AvatarDefinition.d.ts +20 -0
  24. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +96 -0
  25. package/esm/src/avatars/visuals/avatarVisualRegistry.d.ts +16 -0
  26. package/esm/src/avatars/visuals/avatarVisualRegistry.test.d.ts +1 -0
  27. package/esm/src/avatars/visuals/fractalAvatarVisual.d.ts +7 -0
  28. package/esm/src/avatars/visuals/minecraftAvatarVisual.d.ts +7 -0
  29. package/esm/src/avatars/visuals/octopus2AvatarVisual.d.ts +7 -0
  30. package/esm/src/avatars/visuals/octopusAvatarVisual.d.ts +7 -0
  31. package/esm/src/avatars/visuals/pixelArtAvatarVisual.d.ts +7 -0
  32. package/esm/src/commitments/STYLE/STYLE.d.ts +9 -2
  33. package/esm/src/version.d.ts +1 -1
  34. package/package.json +1 -1
  35. package/umd/index.umd.js +868 -524
  36. package/umd/index.umd.js.map +1 -1
  37. package/umd/scripts/run-codex-prompts/common/CoderRunTimer.d.ts +31 -0
  38. package/umd/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +23 -0
  39. package/umd/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +13 -4
  40. package/umd/scripts/run-codex-prompts/common/progressFormatting.d.ts +16 -0
  41. package/umd/scripts/run-codex-prompts/common/runGoScript/$runGoScript.d.ts +1 -1
  42. package/umd/scripts/run-codex-prompts/common/runGoScript/$runGoScriptUntilMarkerIdle.d.ts +1 -1
  43. package/umd/scripts/run-codex-prompts/common/runGoScript/$runGoScriptWithOutput.d.ts +1 -1
  44. package/umd/scripts/run-codex-prompts/common/runGoScript/shouldDeleteTemporaryArtifact.d.ts +7 -0
  45. package/umd/scripts/run-codex-prompts/common/runGoScript/withPromptRuntimeLog.d.ts +4 -3
  46. package/umd/scripts/run-codex-prompts/common/runGoScript/withTempScript.d.ts +1 -1
  47. package/umd/scripts/run-codex-prompts/git/commitChanges.d.ts +3 -1
  48. package/umd/scripts/run-codex-prompts/testing/runPromptTestCommand.d.ts +1 -0
  49. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +15 -20
  50. package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +28 -0
  51. package/umd/scripts/run-codex-prompts/ui/coderRunUiRefresh.d.ts +17 -0
  52. package/umd/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +6 -3
  53. package/umd/src/avatars/Avatar.d.ts +7 -0
  54. package/umd/src/avatars/avatarRenderingUtils.d.ts +117 -0
  55. package/umd/src/avatars/index.d.ts +6 -0
  56. package/umd/src/avatars/renderAvatarVisual.d.ts +9 -0
  57. package/umd/src/avatars/types/AvatarDefinition.d.ts +20 -0
  58. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +96 -0
  59. package/umd/src/avatars/visuals/avatarVisualRegistry.d.ts +16 -0
  60. package/umd/src/avatars/visuals/avatarVisualRegistry.test.d.ts +1 -0
  61. package/umd/src/avatars/visuals/fractalAvatarVisual.d.ts +7 -0
  62. package/umd/src/avatars/visuals/minecraftAvatarVisual.d.ts +7 -0
  63. package/umd/src/avatars/visuals/octopus2AvatarVisual.d.ts +7 -0
  64. package/umd/src/avatars/visuals/octopusAvatarVisual.d.ts +7 -0
  65. package/umd/src/avatars/visuals/pixelArtAvatarVisual.d.ts +7 -0
  66. package/umd/src/commitments/STYLE/STYLE.d.ts +9 -2
  67. package/umd/src/version.d.ts +1 -1
  68. package/esm/scripts/run-codex-prompts/common/runGoScript/PromptRoundArtifacts.d.ts +0 -35
  69. package/umd/scripts/run-codex-prompts/common/runGoScript/PromptRoundArtifacts.d.ts +0 -35
package/esm/index.es.js CHANGED
@@ -58,7 +58,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
58
58
  * @generated
59
59
  * @see https://github.com/webgptorg/promptbook
60
60
  */
61
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-44';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-46';
62
62
  /**
63
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
64
64
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -3005,6 +3005,8 @@ function $initializeCoderRunCommand(program) {
3005
3005
  Features:
3006
3006
  - Automatically stages and commits changes with agent identity
3007
3007
  - Optional post-commit git push with explicit --auto-push opt-in
3008
+ - Optional --preserve-logs keeps temp prompt/log artifacts after successful rounds
3009
+ - Optional --no-ui keeps plain streaming console output for logging and debugging
3008
3010
  - Supports GPG signing of commits
3009
3011
  - Optional post-prompt verification with test-feedback retries
3010
3012
  - Progress tracking and interactive controls
@@ -3020,19 +3022,21 @@ function $initializeCoderRunCommand(program) {
3020
3022
  `));
3021
3023
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
3022
3024
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
3025
+ command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
3026
+ command.option('--no-ui', 'Disable the rich terminal UI and keep plain streaming console output for logging and debugging');
3023
3027
  command.addOption(new Option('--thinking-level <thinking-level>', `Set reasoning effort for supported runners (${THINKING_LEVEL_VALUES.join(', ')})`).choices([...THINKING_LEVEL_VALUES]));
3024
3028
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
3025
3029
  command.option('--no-wait', 'Skip user prompts between processing');
3026
3030
  command.option('--ignore-git-changes', 'Skip clean working tree check before running prompts', false);
3027
3031
  command.option('--allow-credits', 'Allow OpenAI Codex runner to spend credits when rate limits are exhausted', false);
3028
- command.option('--preserve-logs', 'Keep generated runner shells and runtime logs after successful prompt rounds; failures keep them automatically', false);
3029
3032
  command.option('--no-normalize-line-endings', 'Disable automatic LF normalization for files changed in each coding round');
3030
3033
  command.option('--auto-push', 'Automatically git push after each commit', false);
3031
3034
  command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
3032
3035
  command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
3033
3036
  command.action(handleActionErrors(async (cliOptions) => {
3034
- const { dryRun, agent, model, context, test, thinkingLevel, priority, wait, ignoreGitChanges, allowCredits, preserveLogs, normalizeLineEndings, autoMigrate, allowDestructiveAutoMigrate, autoPush, } = cliOptions;
3037
+ const { dryRun, agent, model, context, test, preserveLogs, ui, thinkingLevel, priority, wait, ignoreGitChanges, allowCredits, normalizeLineEndings, autoMigrate, allowDestructiveAutoMigrate, autoPush, } = cliOptions;
3035
3038
  const testCommand = normalizeCommandOptionValue(test);
3039
+ const noUi = !ui;
3036
3040
  // Validate agent
3037
3041
  let agentName = undefined;
3038
3042
  if (agent) {
@@ -3062,11 +3066,12 @@ function $initializeCoderRunCommand(program) {
3062
3066
  model,
3063
3067
  context,
3064
3068
  testCommand,
3069
+ preserveLogs,
3070
+ noUi,
3065
3071
  thinkingLevel,
3066
3072
  priority,
3067
3073
  normalizeLineEndings,
3068
3074
  allowCredits,
3069
- preserveLogs,
3070
3075
  autoMigrate,
3071
3076
  allowDestructiveAutoMigrate,
3072
3077
  autoPush,
@@ -18386,7 +18391,7 @@ class DeleteCommitmentDefinition extends BaseCommitmentDefinition {
18386
18391
  DELETE Casual conversational style
18387
18392
  REMOVE All emoji usage
18388
18393
  GOAL Provide professional business communications
18389
- STYLE Use formal language and proper business etiquette
18394
+ WRITING RULES Use formal language and proper business etiquette
18390
18395
  \`\`\`
18391
18396
 
18392
18397
  \`\`\`book
@@ -18397,7 +18402,7 @@ class DeleteCommitmentDefinition extends BaseCommitmentDefinition {
18397
18402
  DISCARD Technical jargon explanations
18398
18403
  CANCEL Advanced troubleshooting procedures
18399
18404
  GOAL Help users with simple, easy-to-follow solutions
18400
- STYLE Use plain language that anyone can understand
18405
+ WRITING RULES Use plain language that anyone can understand
18401
18406
  \`\`\`
18402
18407
 
18403
18408
  \`\`\`book
@@ -18414,11 +18419,11 @@ class DeleteCommitmentDefinition extends BaseCommitmentDefinition {
18414
18419
  Concise Information Provider
18415
18420
 
18416
18421
  PERSONA You are a helpful assistant who provides detailed explanations
18417
- STYLE Include examples, analogies, and comprehensive context
18422
+ WRITING RULES Include examples, analogies, and comprehensive context
18418
18423
  CANCEL Detailed explanation style
18419
18424
  DISCARD Examples and analogies
18420
18425
  GOAL Provide brief, direct answers without unnecessary elaboration
18421
- STYLE Be concise and to the point
18426
+ WRITING RULES Be concise and to the point
18422
18427
  \`\`\`
18423
18428
  `);
18424
18429
  }
@@ -18602,7 +18607,7 @@ class FormatCommitmentDefinition extends BaseCommitmentDefinition {
18602
18607
  PERSONA You are a data analysis expert
18603
18608
  FORMAT Present results in structured tables
18604
18609
  FORMAT Include confidence scores for all predictions
18605
- STYLE Be concise and precise in explanations
18610
+ WRITING RULES Be concise and precise in explanations
18606
18611
  \`\`\`
18607
18612
  `);
18608
18613
  }
@@ -18996,7 +19001,7 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
18996
19001
 
18997
19002
  GOAL Help students understand mathematical concepts clearly
18998
19003
  GOAL Ensure all explanations are age-appropriate and accessible
18999
- STYLE Use simple language and provide step-by-step explanations
19004
+ WRITING RULES Use simple language and provide step-by-step explanations
19000
19005
  \`\`\`
19001
19006
 
19002
19007
  \`\`\`book
@@ -19410,7 +19415,7 @@ class KnowledgeCommitmentDefinition extends BaseCommitmentDefinition {
19410
19415
  KNOWLEDGE Academic research requires careful citation and verification
19411
19416
  KNOWLEDGE https://example.com/research-guidelines.pdf
19412
19417
  ACTION Can help with literature reviews and data analysis
19413
- STYLE Present information in clear, academic format
19418
+ WRITING RULES Present information in clear, academic format
19414
19419
  \`\`\`
19415
19420
  `);
19416
19421
  }
@@ -21161,7 +21166,7 @@ class MetaImageCommitmentDefinition extends BaseCommitmentDefinition {
21161
21166
 
21162
21167
  META IMAGE https://example.com/professional-avatar.jpg
21163
21168
  PERSONA You are a professional business assistant
21164
- STYLE Maintain a formal and courteous tone
21169
+ WRITING RULES Maintain a formal and courteous tone
21165
21170
  \`\`\`
21166
21171
 
21167
21172
  \`\`\`book
@@ -21169,7 +21174,7 @@ class MetaImageCommitmentDefinition extends BaseCommitmentDefinition {
21169
21174
 
21170
21175
  META IMAGE /assets/creative-bot-avatar.png
21171
21176
  PERSONA You are a creative and inspiring assistant
21172
- STYLE Be enthusiastic and encouraging
21177
+ WRITING RULES Be enthusiastic and encouraging
21173
21178
  ACTION Can help with brainstorming and ideation
21174
21179
  \`\`\`
21175
21180
  `);
@@ -21328,7 +21333,7 @@ class MetaLinkCommitmentDefinition extends BaseCommitmentDefinition {
21328
21333
  META LINK https://twitter.com/devhandle
21329
21334
  PERSONA You are an experienced open source developer
21330
21335
  ACTION Can help with code reviews and architecture decisions
21331
- STYLE Be direct and technical in explanations
21336
+ WRITING RULES Be direct and technical in explanations
21332
21337
  \`\`\`
21333
21338
  `);
21334
21339
  }
@@ -21534,7 +21539,7 @@ class ModelCommitmentDefinition extends BaseCommitmentDefinition {
21534
21539
  MODEL TEMPERATURE 0.8
21535
21540
  MODEL TOP_P 0.9
21536
21541
  MODEL MAX_TOKENS 2048
21537
- STYLE Be imaginative and expressive
21542
+ WRITING RULES Be imaginative and expressive
21538
21543
  ACTION Can help with storytelling and character development
21539
21544
  \`\`\`
21540
21545
 
@@ -21746,7 +21751,7 @@ class NoteCommitmentDefinition extends BaseCommitmentDefinition {
21746
21751
  NOTE Uses RAG for accessing latest research papers
21747
21752
  PERSONA You are a knowledgeable research assistant
21748
21753
  ACTION Can help with literature reviews and citations
21749
- STYLE Present information in academic format
21754
+ WRITING RULES Present information in academic format
21750
21755
  \`\`\`
21751
21756
  `);
21752
21757
  }
@@ -22040,7 +22045,7 @@ class RuleCommitmentDefinition extends BaseCommitmentDefinition {
22040
22045
  RULE Always ask for clarification if the user's request is ambiguous
22041
22046
  RULE Be polite and professional in all interactions
22042
22047
  RULES Never provide medical or legal advice
22043
- STYLE Maintain a friendly and helpful tone
22048
+ WRITING RULES Maintain a friendly and helpful tone
22044
22049
  \`\`\`
22045
22050
 
22046
22051
  \`\`\`book
@@ -22309,8 +22314,8 @@ class ScenarioCommitmentDefinition extends BaseCommitmentDefinition {
22309
22314
  /**
22310
22315
  * STYLE commitment definition
22311
22316
  *
22312
- * The STYLE commitment defines how the agent should format and present its responses.
22313
- * This includes tone, writing style, formatting preferences, and communication patterns.
22317
+ * Deprecated legacy writing-style commitment kept for backward compatibility.
22318
+ * New books should prefer `WRITING RULES` for writing-only constraints.
22314
22319
  *
22315
22320
  * Example usage in agent source:
22316
22321
  *
@@ -22329,7 +22334,16 @@ class StyleCommitmentDefinition extends BaseCommitmentDefinition {
22329
22334
  * Short one-line description of STYLE.
22330
22335
  */
22331
22336
  get description() {
22332
- return 'Control the tone and writing style of responses.';
22337
+ return 'Deprecated legacy writing-style commitment. Prefer `WRITING RULES` for new books.';
22338
+ }
22339
+ /**
22340
+ * Optional UI/docs-only deprecation metadata.
22341
+ */
22342
+ get deprecation() {
22343
+ return {
22344
+ message: 'Use `WRITING RULES` for writing-only constraints such as tone, length, formatting, or emoji usage.',
22345
+ replacedBy: ['WRITING RULES'],
22346
+ };
22333
22347
  }
22334
22348
  /**
22335
22349
  * Icon for this commitment.
@@ -22344,15 +22358,34 @@ class StyleCommitmentDefinition extends BaseCommitmentDefinition {
22344
22358
  return spaceTrim$1(`
22345
22359
  # ${this.type}
22346
22360
 
22347
- Defines how the agent should format and present its responses (tone, writing style, formatting).
22361
+ Deprecated legacy commitment for writing and presentation instructions.
22362
+
22363
+ ## Migration
22364
+
22365
+ - Existing \`${this.type}\` books still parse and compile.
22366
+ - New books should prefer \`WRITING RULES\`.
22367
+ - Use \`WRITING SAMPLE\` when you want to anchor voice by example instead of stating constraints directly.
22368
+ - The plural alias \`STYLES\` is the same legacy commitment family.
22348
22369
 
22349
22370
  ## Key aspects
22350
22371
 
22351
- - Both terms work identically and can be used interchangeably.
22372
+ - \`${this.type}\` remains functional for backward compatibility only.
22352
22373
  - Later style instructions can override earlier ones.
22353
22374
  - Style affects both tone and presentation format.
22354
22375
 
22355
- ## Examples
22376
+ ## Preferred replacement
22377
+
22378
+ \`\`\`book
22379
+ Technical Writer
22380
+
22381
+ GOAL Help the user understand technical topics with practical, accurate guidance.
22382
+ WRITING RULES Write in a professional but friendly tone.
22383
+ WRITING RULES Use bullet points for lists.
22384
+ WRITING RULES Always provide code examples when explaining programming concepts.
22385
+ FORMAT Use markdown formatting with clear headings
22386
+ \`\`\`
22387
+
22388
+ ## Legacy compatibility examples
22356
22389
 
22357
22390
  \`\`\`book
22358
22391
  Technical Writer
@@ -23093,7 +23126,7 @@ class TemplateCommitmentDefinition extends BaseCommitmentDefinition {
23093
23126
 
23094
23127
  PERSONA You are a helpful customer support representative
23095
23128
  TEMPLATE Always structure your response with: 1) Acknowledgment, 2) Solution, 3) Follow-up question
23096
- STYLE Be professional and empathetic
23129
+ WRITING RULES Be professional and empathetic
23097
23130
  \`\`\`
23098
23131
 
23099
23132
  \`\`\`book
@@ -23537,7 +23570,7 @@ class UseBrowserCommitmentDefinition extends BaseCommitmentDefinition {
23537
23570
 
23538
23571
  PERSONA You are a news analyst who stays up-to-date with current events
23539
23572
  USE BROWSER
23540
- STYLE Present news in a balanced and objective manner
23573
+ WRITING RULES Present news in a balanced and objective manner
23541
23574
  ACTION Can search for and summarize news articles
23542
23575
  \`\`\`
23543
23576
 
@@ -44243,7 +44276,7 @@ var findRefactorCandidates$1 = /*#__PURE__*/Object.freeze({
44243
44276
  /**
44244
44277
  * CLI usage text for this script.
44245
44278
  */
44246
- const USAGE = 'Usage: run-codex-prompts [--dry-run] [--agent <agent-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--preserve-logs] [--auto-migrate] [--allow-destructive-auto-migrate] [--no-wait] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push]';
44279
+ const USAGE = 'Usage: run-codex-prompts [--dry-run] [--agent <agent-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--preserve-logs] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--no-wait] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push]';
44247
44280
  /**
44248
44281
  * Top-level flags supported by this command.
44249
44282
  */
@@ -44253,10 +44286,11 @@ const KNOWN_OPTION_FLAGS = new Set([
44253
44286
  '--model',
44254
44287
  '--context',
44255
44288
  '--test',
44289
+ '--preserve-logs',
44290
+ '--no-ui',
44256
44291
  '--thinking-level',
44257
44292
  '--priority',
44258
44293
  '--allow-credits',
44259
- '--preserve-logs',
44260
44294
  '--auto-migrate',
44261
44295
  '--allow-destructive-auto-migrate',
44262
44296
  '--no-wait',
@@ -44286,6 +44320,8 @@ function parseRunOptions(args) {
44286
44320
  const context = readOptionValue(args, '--context');
44287
44321
  const hasTestCommandFlag = args.includes('--test');
44288
44322
  const testCommand = readVariadicOptionValue(args, '--test');
44323
+ const preserveLogs = args.includes('--preserve-logs');
44324
+ const noUi = args.includes('--no-ui');
44289
44325
  const hasThinkingLevelFlag = args.includes('--thinking-level');
44290
44326
  const thinkingLevelValue = readOptionValue(args, '--thinking-level');
44291
44327
  const hasPriorityFlag = args.includes('--priority');
@@ -44293,7 +44329,6 @@ function parseRunOptions(args) {
44293
44329
  const ignoreGitChanges = args.includes('--ignore-git-changes');
44294
44330
  const normalizeLineEndings = !args.includes('--no-normalize-line-endings');
44295
44331
  const allowCredits = args.includes('--allow-credits');
44296
- const preserveLogs = args.includes('--preserve-logs');
44297
44332
  const autoMigrate = args.includes('--auto-migrate');
44298
44333
  const allowDestructiveAutoMigrate = args.includes('--allow-destructive-auto-migrate');
44299
44334
  const autoPush = args.includes('--auto-push');
@@ -44319,10 +44354,11 @@ function parseRunOptions(args) {
44319
44354
  ignoreGitChanges,
44320
44355
  normalizeLineEndings,
44321
44356
  allowCredits,
44322
- preserveLogs,
44323
44357
  autoMigrate,
44324
44358
  allowDestructiveAutoMigrate,
44325
44359
  autoPush,
44360
+ preserveLogs,
44361
+ noUi,
44326
44362
  agentName,
44327
44363
  model,
44328
44364
  context,
@@ -44403,18 +44439,10 @@ function appendCoderContext(prompt, context) {
44403
44439
  return `${normalizedPrompt}\n\n${normalizedContext}`;
44404
44440
  }
44405
44441
 
44406
- /**
44407
- * Refresh interval for the progress header in milliseconds.
44408
- */
44409
- const PROGRESS_REFRESH_INTERVAL_MS = 1000;
44410
- /**
44411
- * Number of terminal lines reserved for the sticky progress header.
44412
- */
44413
- const PROGRESS_HEADER_RESERVED_LINES = 1;
44414
44442
  /**
44415
44443
  * Calendar formats used when displaying the estimated completion time.
44416
44444
  */
44417
- const ESTIMATED_DONE_CALENDAR_FORMATS$1 = {
44445
+ const ESTIMATED_DONE_CALENDAR_FORMATS = {
44418
44446
  sameDay: '[Today] h:mm',
44419
44447
  nextDay: '[Tomorrow] h:mm',
44420
44448
  nextWeek: 'dddd h:mm',
@@ -44422,6 +44450,121 @@ const ESTIMATED_DONE_CALENDAR_FORMATS$1 = {
44422
44450
  lastWeek: 'dddd h:mm',
44423
44451
  sameElse: 'MMM D h:mm',
44424
44452
  };
44453
+ /**
44454
+ * Formats a duration into a compact string such as "3h 12m" or "45s".
44455
+ */
44456
+ function formatDurationBrief(duration) {
44457
+ const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
44458
+ const hours = Math.floor(totalSeconds / 3600);
44459
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
44460
+ const seconds = totalSeconds % 60;
44461
+ const parts = [];
44462
+ if (hours > 0) {
44463
+ parts.push(`${hours}h`);
44464
+ }
44465
+ if (minutes > 0) {
44466
+ parts.push(`${minutes}m`);
44467
+ }
44468
+ if (!parts.length && seconds > 0) {
44469
+ parts.push(`${seconds}s`);
44470
+ }
44471
+ if (!parts.length) {
44472
+ parts.push('0s');
44473
+ }
44474
+ return parts.join(' ');
44475
+ }
44476
+
44477
+ /**
44478
+ * Builds a session-scoped progress snapshot from prompt stats and elapsed active time.
44479
+ */
44480
+ function buildCoderRunProgressSnapshot(stats, elapsedDuration, initialDone) {
44481
+ const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
44482
+ const sessionDone = Math.max(0, stats.done - initialDone);
44483
+ const sessionRemaining = stats.forAgent;
44484
+ const sessionTotal = sessionDone + sessionRemaining;
44485
+ const currentPromptIndex = sessionTotal > 0 ? Math.min(sessionDone + 1, sessionTotal) : 0;
44486
+ const percentage = sessionTotal > 0 ? Math.round((sessionDone / sessionTotal) * 100) : 0;
44487
+ const elapsedText = formatDurationBrief(elapsedDuration);
44488
+ let estimatedTotalText = 'estimating...';
44489
+ let estimatedLabel = 'after first completion';
44490
+ let isEstimatedTotalKnown = false;
44491
+ if (sessionTotal > 0 && sessionDone > 0) {
44492
+ const estimatedTotalMs = (elapsedDuration.asMilliseconds() * sessionTotal) / sessionDone;
44493
+ const estimatedRemainingMs = Math.max(0, estimatedTotalMs - elapsedDuration.asMilliseconds());
44494
+ const estimatedTotalDuration = moment.duration(estimatedTotalMs);
44495
+ const estimatedCompletion = moment().add(estimatedRemainingMs, 'milliseconds');
44496
+ estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
44497
+ estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
44498
+ isEstimatedTotalKnown = true;
44499
+ }
44500
+ return {
44501
+ totalPrompts,
44502
+ sessionDone,
44503
+ sessionRemaining,
44504
+ sessionTotal,
44505
+ currentPromptIndex,
44506
+ skippedPrompts: stats.belowMinimumPriority,
44507
+ toBeWrittenPrompts: stats.toBeWritten,
44508
+ percentage,
44509
+ elapsedText,
44510
+ estimatedTotalText,
44511
+ estimatedLabel,
44512
+ isEstimatedTotalKnown,
44513
+ };
44514
+ }
44515
+
44516
+ /**
44517
+ * Tracks active coder-run time while excluding pauses and user-confirmation waits.
44518
+ */
44519
+ class CoderRunTimer {
44520
+ /**
44521
+ * Creates a timer anchored at the provided start time.
44522
+ */
44523
+ constructor(startTime, isPausedInitially = false) {
44524
+ this.startTime = startTime;
44525
+ /**
44526
+ * Total milliseconds spent in paused state across the run.
44527
+ */
44528
+ this.pausedMs = 0;
44529
+ if (isPausedInitially) {
44530
+ this.pausedSince = startTime.clone();
44531
+ }
44532
+ }
44533
+ /**
44534
+ * Pauses active-time tracking until `resume()` is called.
44535
+ */
44536
+ pause() {
44537
+ if (this.pausedSince === undefined) {
44538
+ this.pausedSince = moment();
44539
+ }
44540
+ }
44541
+ /**
44542
+ * Resumes active-time tracking after a pause.
44543
+ */
44544
+ resume() {
44545
+ if (this.pausedSince !== undefined) {
44546
+ this.pausedMs += moment().diff(this.pausedSince);
44547
+ this.pausedSince = undefined;
44548
+ }
44549
+ }
44550
+ /**
44551
+ * Returns the currently accumulated active duration.
44552
+ */
44553
+ getElapsedDuration() {
44554
+ const wallMs = moment().diff(this.startTime);
44555
+ const currentPauseMs = this.pausedSince !== undefined ? moment().diff(this.pausedSince) : 0;
44556
+ return moment.duration(Math.max(0, wallMs - this.pausedMs - currentPauseMs));
44557
+ }
44558
+ }
44559
+
44560
+ /**
44561
+ * Refresh interval for the progress header in milliseconds.
44562
+ */
44563
+ const PROGRESS_REFRESH_INTERVAL_MS = 1000;
44564
+ /**
44565
+ * Number of terminal lines reserved for the sticky progress header.
44566
+ */
44567
+ const PROGRESS_HEADER_RESERVED_LINES = 3;
44425
44568
  /**
44426
44569
  * Compact CLI progress display that stays pinned at the top of the terminal.
44427
44570
  */
@@ -44429,11 +44572,12 @@ class CliProgressDisplay {
44429
44572
  /**
44430
44573
  * Creates a new display that uses the provided start time when computing estimates.
44431
44574
  */
44432
- constructor(startTime) {
44433
- this.startTime = startTime;
44575
+ constructor(startTime, minimumPriority) {
44576
+ this.minimumPriority = minimumPriority;
44434
44577
  this.stats = { done: 0, forAgent: 0, belowMinimumPriority: 0, toBeWritten: 0 };
44435
44578
  this.isHeaderReserved = false;
44436
44579
  this.isInteractive = Boolean(process.stdout.isTTY);
44580
+ this.timer = new CoderRunTimer(startTime);
44437
44581
  if (!this.isInteractive) {
44438
44582
  return;
44439
44583
  }
@@ -44452,6 +44596,20 @@ class CliProgressDisplay {
44452
44596
  this.stats = stats;
44453
44597
  this.render();
44454
44598
  }
44599
+ /**
44600
+ * Pauses the active timer while the runner is waiting for user input.
44601
+ */
44602
+ pauseTimer() {
44603
+ this.timer.pause();
44604
+ this.render();
44605
+ }
44606
+ /**
44607
+ * Resumes the active timer after a pause.
44608
+ */
44609
+ resumeTimer() {
44610
+ this.timer.resume();
44611
+ this.render();
44612
+ }
44455
44613
  /**
44456
44614
  * Stops the automatic refresh cycle and renders the final header once more.
44457
44615
  */
@@ -44469,14 +44627,17 @@ class CliProgressDisplay {
44469
44627
  * Repaint the header without disturbing the current cursor position.
44470
44628
  */
44471
44629
  render() {
44630
+ var _a;
44472
44631
  if (!this.isInteractive) {
44473
44632
  return;
44474
44633
  }
44475
- const line = this.buildProgressLine();
44634
+ const lines = this.buildProgressLines();
44476
44635
  process.stdout.write('\x1b[s');
44477
- cursorTo(process.stdout, 0, 0);
44478
- clearLine(process.stdout, 0);
44479
- process.stdout.write(line);
44636
+ for (let lineIndex = 0; lineIndex < PROGRESS_HEADER_RESERVED_LINES; lineIndex++) {
44637
+ cursorTo(process.stdout, 0, lineIndex);
44638
+ clearLine(process.stdout, 0);
44639
+ process.stdout.write((_a = lines[lineIndex]) !== null && _a !== void 0 ? _a : '');
44640
+ }
44480
44641
  process.stdout.write('\x1b[u');
44481
44642
  }
44482
44643
  /**
@@ -44490,72 +44651,74 @@ class CliProgressDisplay {
44490
44651
  this.isHeaderReserved = true;
44491
44652
  }
44492
44653
  /**
44493
- * Builds the coloured progress text padded to the terminal width.
44654
+ * Builds the colored progress text padded to the terminal width.
44494
44655
  */
44495
- buildProgressLine() {
44656
+ buildProgressLines() {
44496
44657
  var _a, _b;
44497
- const snapshot = buildProgressSnapshot(this.stats, this.startTime, (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done);
44498
- const sessionLabel = `${snapshot.sessionDone}/${snapshot.sessionTotal} Prompts`;
44499
- const totalLabel = `(${snapshot.totalPrompts} total)`;
44500
- const baseLine = `${sessionLabel} ${totalLabel} | ${snapshot.percentage}% | ${snapshot.elapsedText}/${snapshot.estimatedTotalText} | Estimated done ${snapshot.estimatedLabel}`;
44501
- const columns = (_b = process.stdout.columns) !== null && _b !== void 0 ? _b : baseLine.length;
44502
- const padded = baseLine.padEnd(columns > baseLine.length ? columns : baseLine.length);
44503
- return colors.bgWhite(colors.black(padded));
44658
+ const snapshot = buildCoderRunProgressSnapshot(this.stats, this.timer.getElapsedDuration(), (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done);
44659
+ const columns = Math.max(40, (_b = process.stdout.columns) !== null && _b !== void 0 ? _b : 80);
44660
+ const workingLine = snapshot.sessionTotal > 0
44661
+ ? [
44662
+ `Working on ${snapshot.currentPromptIndex}/${snapshot.sessionTotal} prompts`,
44663
+ `Priority >=${this.minimumPriority}`,
44664
+ `Repo total ${snapshot.totalPrompts}`,
44665
+ ].join(' | ')
44666
+ : [`No runnable prompts`, `Priority >=${this.minimumPriority}`, `Repo total ${snapshot.totalPrompts}`].join(' | ');
44667
+ const detailParts = [
44668
+ `Done ${snapshot.sessionDone}/${snapshot.sessionTotal} this run`,
44669
+ `Elapsed ${snapshot.elapsedText} / ${snapshot.estimatedTotalText}`,
44670
+ `Est. done ${snapshot.estimatedLabel}`,
44671
+ ];
44672
+ if (snapshot.skippedPrompts > 0) {
44673
+ detailParts.splice(1, 0, `Skipping ${snapshot.skippedPrompts} prompts with Priority <${this.minimumPriority}`);
44674
+ }
44675
+ if (snapshot.toBeWrittenPrompts > 0) {
44676
+ detailParts.splice(detailParts.length - 2, 0, `Write first ${formatPromptCount$1(snapshot.toBeWrittenPrompts)}`);
44677
+ }
44678
+ const progressLabel = `${snapshot.percentage}% complete (${snapshot.sessionDone}/${snapshot.sessionTotal} done)`;
44679
+ const progressBar = buildProgressBar$1(snapshot.percentage, progressLabel, columns);
44680
+ return [
44681
+ colors.bgCyan.black(padPlainText(workingLine, columns)),
44682
+ colors.bgBlack.white(padPlainText(detailParts.join(' | '), columns)),
44683
+ colors.bgBlack(progressBar),
44684
+ ];
44504
44685
  }
44505
44686
  }
44506
44687
  /**
44507
- * Calculates progress metrics shown in the sticky header.
44688
+ * Builds a colored progress bar with an explicit completion label.
44508
44689
  */
44509
- function buildProgressSnapshot(stats, startTime, initialDone) {
44510
- const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
44511
- const completedPrompts = stats.done;
44512
- const sessionDone = Math.max(0, completedPrompts - initialDone);
44513
- const sessionTotal = sessionDone + stats.forAgent;
44514
- const percentage = totalPrompts > 0 ? Math.round((completedPrompts / totalPrompts) * 100) : 0;
44515
- const elapsedDuration = moment.duration(moment().diff(startTime));
44516
- const elapsedText = formatDurationBrief$1(elapsedDuration);
44517
- let estimatedTotalText = '—';
44518
- let estimatedLabel = 'unknown';
44519
- if (totalPrompts > 0 && completedPrompts > 0) {
44520
- const estimatedTotalMs = (elapsedDuration.asMilliseconds() * totalPrompts) / completedPrompts;
44521
- const estimatedTotalDuration = moment.duration(estimatedTotalMs);
44522
- const estimatedCompletion = startTime.clone().add(estimatedTotalDuration);
44523
- estimatedTotalText = formatDurationBrief$1(estimatedTotalDuration);
44524
- estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS$1);
44525
- }
44526
- return {
44527
- totalPrompts,
44528
- completedPrompts,
44529
- sessionDone,
44530
- sessionTotal,
44531
- percentage,
44532
- elapsedText,
44533
- estimatedTotalText,
44534
- estimatedLabel,
44535
- };
44690
+ function buildProgressBar$1(percentage, label, width) {
44691
+ const safeLabel = ` ${label}`;
44692
+ const barWidth = Math.max(10, width - safeLabel.length);
44693
+ const filledWidth = Math.round((percentage / 100) * barWidth);
44694
+ const emptyWidth = Math.max(0, barWidth - filledWidth);
44695
+ return `${colors.green('█'.repeat(filledWidth))}${colors.gray('░'.repeat(emptyWidth))}${safeLabel}`;
44536
44696
  }
44537
44697
  /**
44538
- * Formats a duration into a compact string such as "3h 12m" or "45s".
44698
+ * Pads or truncates one plain-text header line to the terminal width.
44539
44699
  */
44540
- function formatDurationBrief$1(duration) {
44541
- const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
44542
- const hours = Math.floor(totalSeconds / 3600);
44543
- const minutes = Math.floor((totalSeconds % 3600) / 60);
44544
- const seconds = totalSeconds % 60;
44545
- const parts = [];
44546
- if (hours > 0) {
44547
- parts.push(`${hours}h`);
44548
- }
44549
- if (minutes > 0) {
44550
- parts.push(`${minutes}m`);
44551
- }
44552
- if (!parts.length && seconds > 0) {
44553
- parts.push(`${seconds}s`);
44554
- }
44555
- if (!parts.length) {
44556
- parts.push('0s');
44700
+ function padPlainText(text, width) {
44701
+ if (text.length > width) {
44702
+ if (width <= 3) {
44703
+ return '.'.repeat(width);
44704
+ }
44705
+ return `${text.slice(0, width - 3)}...`;
44557
44706
  }
44558
- return parts.join(' ');
44707
+ return text.padEnd(width);
44708
+ }
44709
+ /**
44710
+ * Formats a prompt count with the correct singular/plural noun.
44711
+ */
44712
+ function formatPromptCount$1(count) {
44713
+ return `${count} prompt${count === 1 ? '' : 's'}`;
44714
+ }
44715
+
44716
+ /**
44717
+ * Formats commit message lines for console display.
44718
+ */
44719
+ function formatCommitMessageForDisplay(message) {
44720
+ const lines = message.split(/\r?\n/);
44721
+ return lines.map((line) => colors.bgBlue.white(` ${line} `)).join('\n');
44559
44722
  }
44560
44723
 
44561
44724
  /**
@@ -44769,14 +44932,6 @@ function normalizeCrLfToLf(content) {
44769
44932
  return normalized.subarray(0, writeIndex);
44770
44933
  }
44771
44934
 
44772
- /**
44773
- * Formats commit message lines for console display.
44774
- */
44775
- function formatCommitMessageForDisplay(message) {
44776
- const lines = message.split(/\r?\n/);
44777
- return lines.map((line) => colors.bgBlue.white(` ${line} `)).join('\n');
44778
- }
44779
-
44780
44935
  /**
44781
44936
  * Prints the formatted commit message preview.
44782
44937
  */
@@ -44822,82 +44977,36 @@ function buildScriptLogPath(scriptPath) {
44822
44977
  }
44823
44978
 
44824
44979
  /**
44825
- * Runs one prompt-processing round with a dedicated temporary runtime log file and optionally defers cleanup to the round tracker.
44980
+ * Decides whether one temporary prompt artifact should be deleted after a round finishes.
44981
+ */
44982
+ function shouldDeleteTemporaryArtifact({ preserveArtifactsOnSuccess, hasFailed, }) {
44983
+ return !preserveArtifactsOnSuccess && !hasFailed;
44984
+ }
44985
+
44986
+ /**
44987
+ * Runs one prompt-processing round with a dedicated temporary runtime log file that is cleaned up only after successful non-preserved runs.
44826
44988
  */
44827
- async function withPromptRuntimeLog(scriptPath, handler, promptRoundArtifacts) {
44989
+ async function withPromptRuntimeLog(scriptPath, handler, options) {
44828
44990
  const logPath = buildScriptLogPath(scriptPath);
44991
+ let hasFailed = false;
44829
44992
  await unlink(logPath).catch(() => undefined);
44830
- promptRoundArtifacts === null || promptRoundArtifacts === void 0 ? void 0 : promptRoundArtifacts.track(logPath, 'runtime-log');
44831
44993
  try {
44832
44994
  return await handler(logPath);
44833
44995
  }
44996
+ catch (error) {
44997
+ hasFailed = true;
44998
+ throw error;
44999
+ }
44834
45000
  finally {
44835
- if (!promptRoundArtifacts) {
45001
+ if (shouldDeleteTemporaryArtifact({
45002
+ preserveArtifactsOnSuccess: options === null || options === void 0 ? void 0 : options.preserveArtifactsOnSuccess,
45003
+ hasFailed,
45004
+ })) {
44836
45005
  await unlink(logPath).catch(() => undefined);
44837
45006
  }
44838
45007
  }
44839
45008
  }
44840
45009
 
44841
- /**
44842
- * Shared artifact kinds preserved for debugging when a prompt round fails.
44843
- */
44844
- const FAILURE_PRESERVED_ARTIFACT_KINDS = new Set(['runner-script', 'runtime-log']);
44845
- /**
44846
- * Shared artifact kinds preserved after a successful prompt round when explicitly requested.
44847
- */
44848
- const SUCCESS_PRESERVED_ARTIFACT_KINDS = new Set(['runner-script', 'runtime-log']);
44849
- /**
44850
- * Empty preserved-artifact set used for successful rounds without `--preserve-logs`.
44851
- */
44852
- const NO_PRESERVED_ARTIFACT_KINDS = new Set();
44853
- /**
44854
- * Tracks temporary prompt-round artifacts and deletes only those not preserved for the final round outcome.
44855
- */
44856
- class PromptRoundArtifacts {
44857
- /**
44858
- * Creates a new prompt-round artifact tracker.
44859
- */
44860
- constructor(preservedArtifactKindsByOutcome) {
44861
- this.preservedArtifactKindsByOutcome = preservedArtifactKindsByOutcome;
44862
- this.trackedArtifacts = new Map();
44863
- }
44864
- /**
44865
- * Registers one temporary artifact for round-final cleanup.
44866
- */
44867
- track(path, kind) {
44868
- this.trackedArtifacts.set(path, kind);
44869
- }
44870
- /**
44871
- * Cleans up all tracked artifacts that should not survive the final round outcome.
44872
- */
44873
- async cleanup(outcome) {
44874
- const preservedArtifactKinds = this.preservedArtifactKindsByOutcome[outcome];
44875
- const trackedArtifacts = [...this.trackedArtifacts.entries()];
44876
- this.trackedArtifacts.clear();
44877
- await Promise.all(trackedArtifacts.map(async ([path, kind]) => {
44878
- if (preservedArtifactKinds.has(kind)) {
44879
- return;
44880
- }
44881
- await unlink(path).catch(() => undefined);
44882
- }));
44883
- }
44884
- }
44885
- /**
44886
- * Creates the default artifact-retention policy used by `ptbk coder run`.
44887
- */
44888
- function createCoderRunPromptRoundArtifacts(isPreserveLogs) {
44889
- return new PromptRoundArtifacts({
44890
- success: isPreserveLogs ? SUCCESS_PRESERVED_ARTIFACT_KINDS : NO_PRESERVED_ARTIFACT_KINDS,
44891
- failure: FAILURE_PRESERVED_ARTIFACT_KINDS,
44892
- });
44893
- }
44894
- /**
44895
- * Derives the tracked artifact kind from one temporary shell path.
44896
- */
44897
- function getPromptRoundArtifactKindFromScriptPath(scriptPath) {
44898
- return scriptPath.toLowerCase().endsWith('.test.sh') ? 'test-script' : 'runner-script';
44899
- }
44900
-
44901
45010
  /**
44902
45011
  * Waits for the user to press Enter before continuing.
44903
45012
  */
@@ -45335,7 +45444,8 @@ function stringifyUnknownError$1(error) {
45335
45444
 
45336
45445
  /**
45337
45446
  * Commits staged changes with the provided message using the dedicated coding-agent identity when configured,
45338
- * otherwise falls back to the default Git configuration. Remote pushing is opt-in via `options.autoPush`.
45447
+ * otherwise falls back to the default Git configuration. Remote pushing is opt-in via `options.autoPush`,
45448
+ * while `options.excludePaths` can keep temporary artifacts out of the created commit.
45339
45449
  */
45340
45450
  async function commitChanges(message, options) {
45341
45451
  const projectPath = process.cwd();
@@ -45345,11 +45455,7 @@ async function commitChanges(message, options) {
45345
45455
  try {
45346
45456
  const agentEnv = buildAgentGitEnv();
45347
45457
  const signingFlag = buildAgentGitSigningFlag();
45348
- await runGitCommand({
45349
- command: 'git add .',
45350
- cwd: projectPath,
45351
- env: agentEnv,
45352
- });
45458
+ await stageCommitChanges(projectPath, agentEnv, options === null || options === void 0 ? void 0 : options.excludePaths);
45353
45459
  await runGitCommand({
45354
45460
  command: buildGitCommitCommand(commitMessagePath, signingFlag),
45355
45461
  cwd: projectPath,
@@ -45363,6 +45469,56 @@ async function commitChanges(message, options) {
45363
45469
  await unlink(commitMessagePath).catch(() => undefined);
45364
45470
  }
45365
45471
  }
45472
+ /**
45473
+ * Stages repository changes and optionally unstages temporary files that should not end up inside the commit.
45474
+ */
45475
+ async function stageCommitChanges(projectPath, agentEnv, excludePaths) {
45476
+ await runGitCommand({
45477
+ command: 'git add .',
45478
+ cwd: projectPath,
45479
+ env: agentEnv,
45480
+ });
45481
+ const excludedGitPaths = normalizeExcludedGitPaths(projectPath, excludePaths);
45482
+ if (excludedGitPaths.length === 0) {
45483
+ return;
45484
+ }
45485
+ await runGitCommand({
45486
+ command: `git reset --quiet HEAD -- ${excludedGitPaths.map(quoteShellPath).join(' ')}`,
45487
+ cwd: projectPath,
45488
+ env: agentEnv,
45489
+ isVerbose: false,
45490
+ });
45491
+ }
45492
+ /**
45493
+ * Converts excluded filesystem paths into unique repository-relative Git paths.
45494
+ */
45495
+ function normalizeExcludedGitPaths(projectPath, excludePaths) {
45496
+ if (!excludePaths || excludePaths.length === 0) {
45497
+ return [];
45498
+ }
45499
+ return [
45500
+ ...new Set(excludePaths
45501
+ .map((excludePath) => normalizeExcludedGitPath(projectPath, excludePath))
45502
+ .filter((gitPath) => Boolean(gitPath))),
45503
+ ];
45504
+ }
45505
+ /**
45506
+ * Converts one excluded filesystem path into a Git-friendly repository-relative path.
45507
+ */
45508
+ function normalizeExcludedGitPath(projectPath, excludePath) {
45509
+ const absoluteExcludePath = resolve(projectPath, excludePath);
45510
+ const relativeExcludePath = relative(projectPath, absoluteExcludePath).replace(/\\/gu, '/');
45511
+ if (relativeExcludePath === '' || relativeExcludePath === '.' || relativeExcludePath.startsWith('../')) {
45512
+ return undefined;
45513
+ }
45514
+ return relativeExcludePath;
45515
+ }
45516
+ /**
45517
+ * Quotes one Git path for safe shell execution.
45518
+ */
45519
+ function quoteShellPath(path) {
45520
+ return JSON.stringify(path);
45521
+ }
45366
45522
  /**
45367
45523
  * Branded error used when pushing committed changes fails.
45368
45524
  */
@@ -46737,6 +46893,15 @@ function buildPromptLabelForDisplay(file, section) {
46737
46893
  return `${relative(process.cwd(), file.path).replace(/\\/g, '/')}#${section.startLine + 1}`;
46738
46894
  }
46739
46895
 
46896
+ /**
46897
+ * Extracts a short summary line from a prompt section.
46898
+ */
46899
+ function buildPromptSummary(file, section) {
46900
+ const lines = buildCodexPrompt(file, section).split(/\r?\n/);
46901
+ const firstLine = lines.find((line) => line.trim() !== '');
46902
+ return (firstLine === null || firstLine === void 0 ? void 0 : firstLine.trim()) || '(empty prompt)';
46903
+ }
46904
+
46740
46905
  /**
46741
46906
  * Builds the script path for a prompt section.
46742
46907
  */
@@ -46796,15 +46961,6 @@ function findNextTodoPrompt(files, minimumPriority = 0) {
46796
46961
  return nextPrompt;
46797
46962
  }
46798
46963
 
46799
- /**
46800
- * Extracts a short summary line from a prompt section.
46801
- */
46802
- function buildPromptSummary(file, section) {
46803
- const lines = buildCodexPrompt(file, section).split(/\r?\n/);
46804
- const firstLine = lines.find((line) => line.trim() !== '');
46805
- return (firstLine === null || firstLine === void 0 ? void 0 : firstLine.trim()) || '(empty prompt)';
46806
- }
46807
-
46808
46964
  /**
46809
46965
  * Lists upcoming tasks that are ready to run (no authoring placeholders).
46810
46966
  */
@@ -47356,25 +47512,29 @@ async function runBashScriptWithOutput(options) {
47356
47512
  }
47357
47513
 
47358
47514
  /**
47359
- * Creates a temporary script file, runs a handler, and either cleans it up immediately or defers cleanup to the round tracker.
47515
+ * Creates a temporary script file, runs a handler, and cleans it up unless preservation is requested or the run fails.
47360
47516
  */
47361
47517
  async function withTempScript(options, handler) {
47362
- const { scriptPath, scriptContent, promptRoundArtifacts } = options;
47518
+ const { scriptPath, scriptContent } = options;
47519
+ let hasFailed = false;
47363
47520
  await mkdir(dirname$1(scriptPath), { recursive: true });
47364
47521
  await writeFile(scriptPath, scriptContent, 'utf-8');
47365
- promptRoundArtifacts === null || promptRoundArtifacts === void 0 ? void 0 : promptRoundArtifacts.track(scriptPath, getPromptRoundArtifactKindFromScriptPath(scriptPath));
47366
47522
  try {
47367
47523
  return await handler(scriptPath);
47368
47524
  }
47525
+ catch (error) {
47526
+ hasFailed = true;
47527
+ throw error;
47528
+ }
47369
47529
  finally {
47370
- if (!promptRoundArtifacts) {
47530
+ if (shouldDeleteTemporaryArtifact({ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess, hasFailed })) {
47371
47531
  await unlink(scriptPath).catch(() => undefined);
47372
47532
  }
47373
47533
  }
47374
47534
  }
47375
47535
 
47376
47536
  /**
47377
- * Creates a temporary script file, runs it, captures output, and cleans it up immediately unless a round tracker defers that cleanup.
47537
+ * Creates a temporary script file, runs it, captures output, and cleans it up unless preservation is requested or the run fails.
47378
47538
  */
47379
47539
  async function $runGoScriptWithOutput(options) {
47380
47540
  return await withTempScript(options, async (scriptPath) => {
@@ -47477,7 +47637,7 @@ class ClaudeCodeRunner {
47477
47637
  scriptPath: options.scriptPath,
47478
47638
  scriptContent,
47479
47639
  logPath: options.logPath,
47480
- promptRoundArtifacts: options.promptRoundArtifacts,
47640
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47481
47641
  });
47482
47642
  const usage = parseClaudeCodeJsonOutput(output);
47483
47643
  return { usage };
@@ -47485,7 +47645,7 @@ class ClaudeCodeRunner {
47485
47645
  }
47486
47646
 
47487
47647
  /**
47488
- * Creates a temporary script file, runs it, and cleans it up immediately unless a round tracker defers that cleanup.
47648
+ * Creates a temporary script file, runs it, and cleans it up unless preservation is requested or the run fails.
47489
47649
  */
47490
47650
  async function $runGoScript(options) {
47491
47651
  await withTempScript(options, async (scriptPath) => {
@@ -47541,7 +47701,7 @@ class ClineRunner {
47541
47701
  scriptPath: options.scriptPath,
47542
47702
  scriptContent,
47543
47703
  logPath: options.logPath,
47544
- promptRoundArtifacts: options.promptRoundArtifacts,
47704
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47545
47705
  });
47546
47706
  return { usage: UNCERTAIN_USAGE };
47547
47707
  }
@@ -47666,7 +47826,7 @@ class GeminiRunner {
47666
47826
  scriptPath: options.scriptPath,
47667
47827
  scriptContent,
47668
47828
  logPath: options.logPath,
47669
- promptRoundArtifacts: options.promptRoundArtifacts,
47829
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47670
47830
  });
47671
47831
  const usage = parseGeminiUsageFromOutput(output, options.prompt, this.options.model);
47672
47832
  return { usage };
@@ -47723,7 +47883,7 @@ class GitHubCopilotRunner {
47723
47883
  scriptPath: options.scriptPath,
47724
47884
  scriptContent,
47725
47885
  logPath: options.logPath,
47726
- promptRoundArtifacts: options.promptRoundArtifacts,
47886
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47727
47887
  });
47728
47888
  return { usage: UNCERTAIN_USAGE };
47729
47889
  }
@@ -47899,7 +48059,7 @@ async function runScriptUntilMarkerIdle(options) {
47899
48059
  }
47900
48060
 
47901
48061
  /**
47902
- * Creates a temporary script file, runs it, waits for a completion marker and idle time, and defers cleanup when a round tracker is provided.
48062
+ * Creates a temporary script file, runs it, waits for a completion marker and idle time, and cleans it up unless preservation is requested or the run fails.
47903
48063
  * Returns the captured output for post-processing.
47904
48064
  */
47905
48065
  async function $runGoScriptUntilMarkerIdle(options) {
@@ -48288,7 +48448,7 @@ class OpenAiCodexRunner {
48288
48448
  completionLineMatcher: CODEX_COMPLETION_LINE,
48289
48449
  idleTimeoutMs: CODEX_COMPLETION_IDLE_MS,
48290
48450
  logPath: options.logPath,
48291
- promptRoundArtifacts: options.promptRoundArtifacts,
48451
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48292
48452
  });
48293
48453
  this.rateLimitBackoff.reset();
48294
48454
  return { usage: buildCodexUsageFromOutput(output, this.options.model) };
@@ -48423,7 +48583,7 @@ class OpencodeRunner {
48423
48583
  scriptPath: options.scriptPath,
48424
48584
  scriptContent,
48425
48585
  logPath: options.logPath,
48426
- promptRoundArtifacts: options.promptRoundArtifacts,
48586
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48427
48587
  });
48428
48588
  }
48429
48589
  catch (error) {
@@ -48451,6 +48611,7 @@ async function runPromptTestCommand(options) {
48451
48611
  ${options.command}
48452
48612
  `),
48453
48613
  logPath: options.logPath,
48614
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48454
48615
  });
48455
48616
  }
48456
48617
 
@@ -48475,7 +48636,7 @@ async function runPromptWithTestFeedback(options) {
48475
48636
  scriptPath: options.scriptPath,
48476
48637
  projectPath: options.projectPath,
48477
48638
  logPath: options.logPath,
48478
- promptRoundArtifacts: options.promptRoundArtifacts,
48639
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48479
48640
  });
48480
48641
  return { ...result, attemptCount: 1 };
48481
48642
  }
@@ -48488,7 +48649,7 @@ async function runPromptWithTestFeedback(options) {
48488
48649
  scriptPath: options.scriptPath,
48489
48650
  projectPath: options.projectPath,
48490
48651
  logPath: options.logPath,
48491
- promptRoundArtifacts: options.promptRoundArtifacts,
48652
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48492
48653
  });
48493
48654
  console.info(colors.gray(`Running verification command after attempt #${attemptCount}: ${normalizedTestCommand}`));
48494
48655
  try {
@@ -48497,6 +48658,7 @@ async function runPromptWithTestFeedback(options) {
48497
48658
  projectPath: options.projectPath,
48498
48659
  scriptPath: buildPromptTestScriptPath(options.scriptPath),
48499
48660
  logPath: options.logPath,
48661
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48500
48662
  });
48501
48663
  return { ...result, attemptCount };
48502
48664
  }
@@ -48578,24 +48740,213 @@ function buildPromptTestScriptPath(scriptPath) {
48578
48740
  }
48579
48741
 
48580
48742
  /**
48581
- * Maximum number of agent output lines kept in the scrolling output area.
48582
- *
48583
- * @private internal constant of coder run UI
48743
+ * Maximum number of output lines reserved for agent output in the UI.
48584
48744
  */
48585
- const MAX_AGENT_OUTPUT_LINES = 12;
48745
+ const MAX_VISIBLE_OUTPUT_LINES = 8;
48586
48746
  /**
48587
- * Calendar formats used when displaying the estimated completion time.
48747
+ * Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
48748
+ */
48749
+ function buildCoderRunUiFrame(options) {
48750
+ const totalWidth = Math.max(56, Math.min(options.terminalWidth, 96));
48751
+ const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
48752
+ const promptStatusPrefix = isPromptActive ? `${colors.yellow(`${options.spinner} `)}` : '';
48753
+ const sessionScopeLine = options.progress.sessionTotal > 0
48754
+ ? `Working on ${options.progress.currentPromptIndex}/${options.progress.sessionTotal} prompts with Priority ≥${options.config.priority}`
48755
+ : `No runnable prompts with Priority ≥${options.config.priority}`;
48756
+ const sessionCountLine = `Done ${options.progress.sessionDone}/${options.progress.sessionTotal} this run · Repo total ${options.progress.totalPrompts}`;
48757
+ const sessionQueueParts = [];
48758
+ if (options.progress.skippedPrompts > 0) {
48759
+ sessionQueueParts.push(`Skipping ${formatPromptCount(options.progress.skippedPrompts)} with Priority <${options.config.priority}`);
48760
+ }
48761
+ if (options.progress.toBeWrittenPrompts > 0) {
48762
+ sessionQueueParts.push(`Write first ${formatPromptCount(options.progress.toBeWrittenPrompts)}`);
48763
+ }
48764
+ const sessionLines = [
48765
+ `${buildPhaseBadge(options.phase, options.pauseState)} ${fitPlainText(options.statusMessage, totalWidth - 18)}`,
48766
+ sessionScopeLine,
48767
+ sessionCountLine,
48768
+ ...(sessionQueueParts.length > 0 ? [sessionQueueParts.join(' · ')] : []),
48769
+ `Elapsed ${options.progress.elapsedText} · Est. total ${options.progress.estimatedTotalText} · Est. done ${options.progress.estimatedLabel}`,
48770
+ buildProgressBar(options.progress.percentage, totalWidth - 6, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
48771
+ ];
48772
+ const metadataParts = [options.config.agentName || 'No agent selected'];
48773
+ if (options.config.modelName) {
48774
+ metadataParts.push(options.config.modelName);
48775
+ }
48776
+ if (options.config.thinkingLevel) {
48777
+ metadataParts.push(`thinking ${options.config.thinkingLevel}`);
48778
+ }
48779
+ const runnerDetails = [
48780
+ [`${colors.bgCyan.black(' PTBK ')}`, colors.bgBlue.white(' CODER '), colors.bold.white(' Promptbook Coder')]
48781
+ .join(''),
48782
+ metadataParts.join(' · '),
48783
+ buildConfigSummaryLine(options.config),
48784
+ ];
48785
+ const currentTaskLines = options.currentPromptLabel
48786
+ ? [
48787
+ `${promptStatusPrefix}${colors.bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
48788
+ `Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
48789
+ ...options.detailLines.map((detailLine) => `• ${detailLine}`),
48790
+ ]
48791
+ : [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
48792
+ const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
48793
+ const controls = buildControlPills(options.pauseState, options.pendingEnterLabel).join(' ');
48794
+ const frame = [
48795
+ ...renderBox('Brand', runnerDetails, totalWidth, colors.cyan.bold),
48796
+ ...renderBox('Session', sessionLines, totalWidth, colors.yellow.bold),
48797
+ ...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors.magenta.bold),
48798
+ ...renderBox('Live output', visibleOutputLines, totalWidth, colors.green.bold),
48799
+ ];
48800
+ if (options.errors.length > 0) {
48801
+ frame.push(...renderBox('Errors', options.errors.map((errorLine) => `${colors.red('✗')} ${errorLine}`), totalWidth, colors.red.bold));
48802
+ }
48803
+ frame.push(...renderBox('Controls', [controls], totalWidth, colors.white.bold));
48804
+ return frame;
48805
+ }
48806
+ /**
48807
+ * Builds the fixed-height live output section so streaming updates do not keep resizing the frame.
48808
+ */
48809
+ function buildVisibleOutputLines(agentOutputLines) {
48810
+ const visibleOutputLines = agentOutputLines.length > 0
48811
+ ? agentOutputLines.slice(-MAX_VISIBLE_OUTPUT_LINES).map((line) => `› ${stripAnsi(line)}`)
48812
+ : ['No live agent output yet.'];
48813
+ while (visibleOutputLines.length < MAX_VISIBLE_OUTPUT_LINES) {
48814
+ visibleOutputLines.push('');
48815
+ }
48816
+ return visibleOutputLines;
48817
+ }
48818
+ /**
48819
+ * Renders a framed box with a colored title and padded body lines.
48820
+ */
48821
+ function renderBox(title, lines, totalWidth, colorizeTitle) {
48822
+ const bodyWidth = Math.max(10, totalWidth - 4);
48823
+ const titleText = ` ${title} `;
48824
+ const topBorder = colors.gray('┌') +
48825
+ colorizeTitle(titleText) +
48826
+ colors.gray('─'.repeat(Math.max(0, totalWidth - 2 - titleText.length)) + '┐');
48827
+ const body = lines.map((line) => {
48828
+ const paddedLine = padAnsiText(line, bodyWidth);
48829
+ return colors.gray('│ ') + paddedLine + colors.gray(' │');
48830
+ });
48831
+ const bottomBorder = colors.gray(`└${'─'.repeat(totalWidth - 2)}┘`);
48832
+ return [topBorder, ...body, bottomBorder];
48833
+ }
48834
+ /**
48835
+ * Builds the compact config summary line shown in the branding box.
48836
+ */
48837
+ function buildConfigSummaryLine(config) {
48838
+ const parts = [`Priority ≥${config.priority}`];
48839
+ if (config.context) {
48840
+ parts.unshift(`Context ${config.context}`);
48841
+ }
48842
+ if (config.testCommand) {
48843
+ parts.push(`Test ${config.testCommand}`);
48844
+ }
48845
+ return parts.join(' · ');
48846
+ }
48847
+ /**
48848
+ * Builds the colored phase badge shown in the session box.
48849
+ */
48850
+ function buildPhaseBadge(phase, pauseState) {
48851
+ if (pauseState !== 'RUNNING' || phase === 'paused') {
48852
+ return colors.bgYellow.black(' PAUSED ');
48853
+ }
48854
+ switch (phase) {
48855
+ case 'loading':
48856
+ case 'initializing':
48857
+ return colors.bgCyan.black(' LOADING ');
48858
+ case 'running':
48859
+ return colors.bgGreen.black(' RUNNING ');
48860
+ case 'verifying':
48861
+ return colors.bgMagenta.white(' VERIFYING ');
48862
+ case 'waiting':
48863
+ return colors.bgBlue.white(' WAITING ');
48864
+ case 'done':
48865
+ return colors.bgGreen.black(' DONE ');
48866
+ case 'error':
48867
+ return colors.bgRed.white(' ERROR ');
48868
+ default:
48869
+ return colors.bgWhite.black(' READY ');
48870
+ }
48871
+ }
48872
+ /**
48873
+ * Builds the progress bar shown in the session box.
48874
+ */
48875
+ function buildProgressBar(percentage, availableWidth, label) {
48876
+ const percentageLabel = label;
48877
+ const barWidth = Math.max(10, availableWidth - percentageLabel.length - 1);
48878
+ const filledWidth = Math.round((percentage / 100) * barWidth);
48879
+ const emptyWidth = Math.max(0, barWidth - filledWidth);
48880
+ return `${colors.green('█'.repeat(filledWidth))}${colors.gray('░'.repeat(emptyWidth))} ${percentageLabel}`;
48881
+ }
48882
+ /**
48883
+ * Formats a prompt count with singular/plural wording.
48884
+ */
48885
+ function formatPromptCount(count) {
48886
+ return `${count} prompt${count === 1 ? '' : 's'}`;
48887
+ }
48888
+ /**
48889
+ * Builds the control pills shown in the footer box.
48890
+ */
48891
+ function buildControlPills(pauseState, pendingEnterLabel) {
48892
+ const pills = [];
48893
+ if (pendingEnterLabel) {
48894
+ pills.push(colors.bgWhite.black(' ENTER ') + colors.white(` ${pendingEnterLabel}`));
48895
+ }
48896
+ pills.push(pauseState === 'RUNNING'
48897
+ ? colors.bgYellow.black(' P ') + colors.white(' Pause')
48898
+ : colors.bgYellow.black(' P ') + colors.white(' Resume'));
48899
+ pills.push(colors.bgRed.white(' CTRL+C ') + colors.white(' Exit'));
48900
+ return pills;
48901
+ }
48902
+ /**
48903
+ * Pads or truncates a possibly ANSI-colored line to the target visible width.
48904
+ */
48905
+ function padAnsiText(text, width) {
48906
+ const fittedText = fitAnsiText(text, width);
48907
+ return fittedText + ' '.repeat(Math.max(0, width - visibleLength(fittedText)));
48908
+ }
48909
+ /**
48910
+ * Truncates a possibly ANSI-colored line to the target visible width.
48911
+ */
48912
+ function fitAnsiText(text, width) {
48913
+ if (visibleLength(text) <= width) {
48914
+ return text;
48915
+ }
48916
+ return fitPlainText(stripAnsi(text), width);
48917
+ }
48918
+ /**
48919
+ * Truncates a plain-text line to the target width with an ellipsis.
48920
+ */
48921
+ function fitPlainText(text, width) {
48922
+ if (text.length <= width) {
48923
+ return text;
48924
+ }
48925
+ if (width <= 3) {
48926
+ return '.'.repeat(width);
48927
+ }
48928
+ return `${text.slice(0, width - 3)}...`;
48929
+ }
48930
+ /**
48931
+ * Measures visible string width by stripping ANSI escape codes.
48932
+ */
48933
+ function visibleLength(text) {
48934
+ return stripAnsi(text).length;
48935
+ }
48936
+ /**
48937
+ * Strips ANSI escape codes from a string.
48938
+ */
48939
+ function stripAnsi(text) {
48940
+ // eslint-disable-next-line no-control-regex
48941
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
48942
+ }
48943
+
48944
+ /**
48945
+ * Maximum number of agent output lines kept in the scrolling output area.
48588
48946
  *
48589
48947
  * @private internal constant of coder run UI
48590
48948
  */
48591
- const ESTIMATED_DONE_CALENDAR_FORMATS = {
48592
- sameDay: '[Today] h:mm',
48593
- nextDay: '[Tomorrow] h:mm',
48594
- nextWeek: 'dddd h:mm',
48595
- lastDay: '[Yesterday] h:mm',
48596
- lastWeek: 'dddd h:mm',
48597
- sameElse: 'MMM D h:mm',
48598
- };
48949
+ const MAX_AGENT_OUTPUT_LINES = 12;
48599
48950
  /**
48600
48951
  * Reactive state manager for the coder run terminal UI.
48601
48952
  *
@@ -48611,35 +48962,27 @@ class CoderRunUiState extends EventEmitter {
48611
48962
  this.currentPromptLabel = '';
48612
48963
  this.currentAttempt = 1;
48613
48964
  this.maxAttempts = 3;
48965
+ this.detailLines = [];
48614
48966
  this.agentOutputLines = [];
48615
48967
  this.phase = 'initializing';
48616
48968
  this.statusMessage = 'Initializing...';
48617
48969
  this.errors = [];
48618
48970
  this.stats = { done: 0, forAgent: 0, belowMinimumPriority: 0, toBeWritten: 0 };
48619
- /**
48620
- * Total milliseconds the timer was paused/waiting (excluded from elapsed display).
48621
- */
48622
- this.pausedMs = 0;
48623
- this.startTime = startTime;
48624
- // Timer starts paused — callers call `resumeTimer()` when actual work begins.
48625
- this.pausedSince = startTime.clone();
48971
+ this.timer = new CoderRunTimer(startTime, true);
48626
48972
  }
48627
48973
  /**
48628
48974
  * Pauses the elapsed timer (e.g. while waiting for user input or paused state).
48629
48975
  */
48630
48976
  pauseTimer() {
48631
- if (this.pausedSince === undefined) {
48632
- this.pausedSince = moment();
48633
- }
48977
+ this.timer.pause();
48978
+ this.emitChange();
48634
48979
  }
48635
48980
  /**
48636
48981
  * Resumes the elapsed timer after a pause.
48637
48982
  */
48638
48983
  resumeTimer() {
48639
- if (this.pausedSince !== undefined) {
48640
- this.pausedMs += moment().diff(this.pausedSince);
48641
- this.pausedSince = undefined;
48642
- }
48984
+ this.timer.resume();
48985
+ this.emitChange();
48643
48986
  }
48644
48987
  /**
48645
48988
  * Replaces the configuration shown in the UI header.
@@ -48663,41 +49006,15 @@ class CoderRunUiState extends EventEmitter {
48663
49006
  */
48664
49007
  getProgress() {
48665
49008
  var _a;
48666
- const stats = this.stats;
48667
- const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
48668
- const sessionDone = Math.max(0, stats.done - ((_a = this.initialDone) !== null && _a !== void 0 ? _a : stats.done));
48669
- const sessionTotal = sessionDone + stats.forAgent;
48670
- const percentage = totalPrompts > 0 ? Math.round((stats.done / totalPrompts) * 100) : 0;
48671
- const wallMs = moment().diff(this.startTime);
48672
- const currentPauseMs = this.pausedSince !== undefined ? moment().diff(this.pausedSince) : 0;
48673
- const activeMs = Math.max(0, wallMs - this.pausedMs - currentPauseMs);
48674
- const elapsedDuration = moment.duration(activeMs);
48675
- const elapsedText = formatDurationBrief(elapsedDuration);
48676
- let estimatedTotalText = '\u2014';
48677
- let estimatedLabel = 'unknown';
48678
- if (totalPrompts > 0 && stats.done > 0) {
48679
- const estimatedTotalMs = (elapsedDuration.asMilliseconds() * totalPrompts) / stats.done;
48680
- const estimatedRemainingMs = estimatedTotalMs - elapsedDuration.asMilliseconds();
48681
- const estimatedTotalDuration = moment.duration(estimatedTotalMs);
48682
- const estimatedCompletion = moment().add(estimatedRemainingMs, 'milliseconds');
48683
- estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
48684
- estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
48685
- }
48686
- return {
48687
- totalPrompts,
48688
- sessionDone,
48689
- sessionTotal,
48690
- percentage,
48691
- elapsedText,
48692
- estimatedTotalText,
48693
- estimatedLabel,
48694
- };
49009
+ return buildCoderRunProgressSnapshot(this.stats, this.timer.getElapsedDuration(), (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done);
48695
49010
  }
48696
49011
  /**
48697
49012
  * Sets the label of the prompt currently being processed and resets per-prompt state.
48698
49013
  */
48699
49014
  setCurrentPrompt(label) {
48700
49015
  this.currentPromptLabel = label;
49016
+ this.detailLines = [];
49017
+ this.pendingEnterLabel = undefined;
48701
49018
  this.agentOutputLines = [];
48702
49019
  this.currentAttempt = 1;
48703
49020
  this.emitChange();
@@ -48737,6 +49054,20 @@ class CoderRunUiState extends EventEmitter {
48737
49054
  this.statusMessage = message;
48738
49055
  this.emitChange();
48739
49056
  }
49057
+ /**
49058
+ * Replaces the contextual detail lines shown beneath the current prompt status.
49059
+ */
49060
+ setDetailLines(detailLines) {
49061
+ this.detailLines = detailLines.filter((detailLine) => detailLine.trim() !== '');
49062
+ this.emitChange();
49063
+ }
49064
+ /**
49065
+ * Sets or clears the Enter-key action label shown in the controls panel.
49066
+ */
49067
+ setPendingEnterLabel(pendingEnterLabel) {
49068
+ this.pendingEnterLabel = pendingEnterLabel;
49069
+ this.emitChange();
49070
+ }
48740
49071
  /**
48741
49072
  * Appends an error message to the error list shown in the UI.
48742
49073
  */
@@ -48748,50 +49079,35 @@ class CoderRunUiState extends EventEmitter {
48748
49079
  this.emit('change');
48749
49080
  }
48750
49081
  }
48751
- /**
48752
- * Formats a duration into a compact string such as "3h 12m" or "45s".
48753
- *
48754
- * @private internal utility of coder run UI
48755
- */
48756
- function formatDurationBrief(duration) {
48757
- const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
48758
- const hours = Math.floor(totalSeconds / 3600);
48759
- const minutes = Math.floor((totalSeconds % 3600) / 60);
48760
- const seconds = totalSeconds % 60;
48761
- const parts = [];
48762
- if (hours > 0) {
48763
- parts.push(`${hours}h`);
48764
- }
48765
- if (minutes > 0) {
48766
- parts.push(`${minutes}m`);
48767
- }
48768
- if (!parts.length && seconds > 0) {
48769
- parts.push(`${seconds}s`);
48770
- }
48771
- if (!parts.length) {
48772
- parts.push('0s');
48773
- }
48774
- return parts.join(' ');
48775
- }
48776
49082
 
48777
49083
  /**
48778
- * Refresh interval for the terminal UI in milliseconds.
49084
+ * Refresh cadence used only while the rich coder UI needs animated updates.
48779
49085
  *
48780
49086
  * @private internal constant of coder run UI
48781
49087
  */
48782
- const UI_REFRESH_INTERVAL_MS = 200;
49088
+ const ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS = 1000;
48783
49089
  /**
48784
- * Character width used for the text progress bar.
49090
+ * Phases that still benefit from automatic refreshes because the frame can change
49091
+ * over time even without new runner output.
48785
49092
  *
48786
49093
  * @private internal constant of coder run UI
48787
49094
  */
48788
- const PROGRESS_BAR_WIDTH = 40;
49095
+ const AUTO_REFRESH_PHASES = ['initializing', 'loading', 'running', 'verifying'];
48789
49096
  /**
48790
- * Maximum number of output lines reserved for agent output in the UI.
49097
+ * Returns the automatic refresh interval for the current UI state.
48791
49098
  *
48792
- * @private internal constant of coder run UI
49099
+ * Waiting, paused, and completed states return `undefined` so the rich UI stays
49100
+ * perfectly still until actual state changes arrive.
49101
+ *
49102
+ * @private internal utility of coder run UI
48793
49103
  */
48794
- const MAX_VISIBLE_OUTPUT_LINES = 8;
49104
+ function getCoderRunUiAutoRefreshInterval(phase, pauseState) {
49105
+ if (pauseState !== 'RUNNING') {
49106
+ return undefined;
49107
+ }
49108
+ return AUTO_REFRESH_PHASES.includes(phase) ? ACTIVE_CODER_RUN_UI_REFRESH_INTERVAL_MS : undefined;
49109
+ }
49110
+
48795
49111
  /**
48796
49112
  * Spinner animation frames.
48797
49113
  *
@@ -48810,38 +49126,20 @@ const SPINNER_FRAMES = [
48810
49126
  '\u280F',
48811
49127
  ];
48812
49128
  /**
48813
- * Strips ANSI escape codes from a string.
48814
- *
48815
- * @private internal utility of coder run UI
48816
- */
48817
- function stripAnsi(text) {
48818
- // eslint-disable-next-line no-control-regex
48819
- return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
48820
- }
48821
- /**
48822
- * Returns the usable terminal width, capped at 80.
49129
+ * Returns the usable terminal width, capped at 96.
48823
49130
  *
48824
49131
  * @private internal utility of coder run UI
48825
49132
  */
48826
49133
  function getTerminalWidth() {
48827
- return Math.min(process.stdout.columns || 80, 80);
48828
- }
48829
- /**
48830
- * Builds a text progress bar string from a percentage.
48831
- *
48832
- * @private internal utility of coder run UI
48833
- */
48834
- function buildProgressBar(percentage) {
48835
- const filled = Math.round((percentage / 100) * PROGRESS_BAR_WIDTH);
48836
- const empty = PROGRESS_BAR_WIDTH - filled;
48837
- return colors.green('\u2588'.repeat(filled)) + colors.gray('\u2591'.repeat(empty)) + ` ${percentage}%`;
49134
+ return Math.min(process.stdout.columns || 80, 96);
48838
49135
  }
48839
49136
  /**
48840
49137
  * Boots the ANSI terminal UI for `ptbk coder run`.
48841
49138
  *
48842
- * The UI reserves a fixed number of terminal lines and repaints them periodically.
48843
- * Between repaints, any console output from runners is captured and fed into the
48844
- * scrolling agent-output area.
49139
+ * The UI reserves a fixed number of terminal lines and refreshes them incrementally.
49140
+ * While a prompt is actively running, it schedules lightweight timed refreshes for
49141
+ * the spinner/progress area; otherwise it redraws only when real state changes arrive.
49142
+ * Any console output from runners is captured and fed into the scrolling agent-output area.
48845
49143
  *
48846
49144
  * On non-interactive (non-TTY) terminals the UI is skipped entirely and
48847
49145
  * only the state object is provided.
@@ -48855,21 +49153,20 @@ function renderCoderRunUi(startTime) {
48855
49153
  state,
48856
49154
  startCapturingAgentOutput: () => { },
48857
49155
  stopCapturingAgentOutput: () => { },
49156
+ waitForEnter: async () => { },
48858
49157
  cleanup: () => { },
48859
49158
  };
48860
49159
  }
48861
- // --- Console interception ---
48862
49160
  const originalConsoleInfo = console.info;
48863
49161
  const originalConsoleWarn = console.warn;
48864
49162
  const originalConsoleError = console.error;
48865
49163
  const originalConsoleLog = console.log;
48866
49164
  let isCapturing = false;
49165
+ let pendingEnterResolver;
48867
49166
  console.info = (...args) => {
48868
49167
  if (isCapturing) {
48869
49168
  state.addAgentOutput(args.map(String).join(' '));
48870
49169
  }
48871
- // In UI mode, non-captured output is intentionally suppressed
48872
- // so it does not interfere with the repainted frame.
48873
49170
  };
48874
49171
  console.warn = (...args) => {
48875
49172
  if (isCapturing) {
@@ -48886,192 +49183,205 @@ function renderCoderRunUi(startTime) {
48886
49183
  state.addAgentOutput(args.map(String).join(' '));
48887
49184
  }
48888
49185
  };
48889
- // --- Keyboard input (pause) ---
48890
49186
  const readline = require('readline');
48891
49187
  readline.emitKeypressEvents(process.stdin);
48892
49188
  if (process.stdin.isTTY) {
48893
49189
  process.stdin.setRawMode(true);
48894
49190
  }
48895
- const keypressHandler = (_str, key) => {
48896
- if (key.ctrl && key.name === 'c') {
48897
- cleanup();
48898
- process.exit(0);
48899
- }
48900
- if (key.name === 'p') {
48901
- const current = getPauseState();
48902
- if (current === 'RUNNING') {
48903
- requestPause();
48904
- }
48905
- else {
48906
- requestResume();
48907
- }
48908
- }
48909
- };
48910
- process.stdin.on('keypress', keypressHandler);
48911
- // --- Rendering ---
48912
49191
  let spinnerFrame = 0;
48913
- let previousFrameLineCount = 0;
49192
+ let previousFrameLines = [];
48914
49193
  let isRendering = false;
48915
49194
  let renderScheduled = false;
49195
+ let autoRefreshTimeout;
49196
+ let isDisposed = false;
48916
49197
  /**
48917
49198
  * Schedules a render on the next tick if one isn't already pending.
48918
49199
  * Prevents overlapping renders that cause cursor desync.
48919
49200
  */
48920
49201
  function scheduleRender() {
48921
- if (renderScheduled) {
49202
+ if (renderScheduled || isDisposed) {
48922
49203
  return;
48923
49204
  }
48924
49205
  renderScheduled = true;
48925
49206
  setImmediate(() => {
48926
49207
  renderScheduled = false;
49208
+ if (isDisposed) {
49209
+ return;
49210
+ }
48927
49211
  render();
48928
49212
  });
48929
49213
  }
48930
49214
  /**
48931
- * Clears previously rendered lines and writes a new frame.
49215
+ * Re-schedules automatic animation refreshes only while the frame can change by itself.
48932
49216
  */
48933
- function render() {
48934
- if (isRendering) {
49217
+ function scheduleAutoRefresh() {
49218
+ if (autoRefreshTimeout) {
49219
+ clearTimeout(autoRefreshTimeout);
49220
+ autoRefreshTimeout = undefined;
49221
+ }
49222
+ const autoRefreshInterval = getCoderRunUiAutoRefreshInterval(state.phase, getPauseState());
49223
+ if (autoRefreshInterval === undefined) {
49224
+ return;
49225
+ }
49226
+ autoRefreshTimeout = setTimeout(() => {
49227
+ autoRefreshTimeout = undefined;
49228
+ scheduleRender();
49229
+ }, autoRefreshInterval);
49230
+ }
49231
+ /**
49232
+ * Moves the cursor relative to the bottom of the current frame and rewrites one line in place.
49233
+ */
49234
+ function rewriteFrameLine(frameLineCount, lineIndex, line) {
49235
+ const linesUpFromBottom = Math.max(0, frameLineCount - 1 - lineIndex);
49236
+ if (linesUpFromBottom > 0) {
49237
+ process.stdout.write(`\x1b[${linesUpFromBottom}A`);
49238
+ }
49239
+ clearLine(process.stdout, 0);
49240
+ cursorTo(process.stdout, 0);
49241
+ process.stdout.write(line);
49242
+ cursorTo(process.stdout, 0);
49243
+ if (linesUpFromBottom > 0) {
49244
+ process.stdout.write(`\x1b[${linesUpFromBottom}B`);
49245
+ cursorTo(process.stdout, 0);
49246
+ }
49247
+ }
49248
+ /**
49249
+ * Fully rewrites the reserved frame area.
49250
+ */
49251
+ function renderFullFrame(lines) {
49252
+ var _a;
49253
+ const previousFrameLineCount = previousFrameLines.length;
49254
+ const linesToRewriteCount = Math.max(previousFrameLineCount, lines.length);
49255
+ if (previousFrameLineCount > 1) {
49256
+ process.stdout.write(`\x1b[${previousFrameLineCount - 1}A`);
49257
+ }
49258
+ for (let i = 0; i < linesToRewriteCount; i++) {
49259
+ clearLine(process.stdout, 0);
49260
+ cursorTo(process.stdout, 0);
49261
+ process.stdout.write((_a = lines[i]) !== null && _a !== void 0 ? _a : '');
49262
+ if (i < linesToRewriteCount - 1) {
49263
+ process.stdout.write('\n');
49264
+ }
49265
+ }
49266
+ const clearedTrailingLines = linesToRewriteCount - lines.length;
49267
+ if (clearedTrailingLines > 0) {
49268
+ process.stdout.write(`\x1b[${clearedTrailingLines}A`);
49269
+ }
49270
+ cursorTo(process.stdout, 0);
49271
+ }
49272
+ /**
49273
+ * Updates only the frame rows whose visible content changed.
49274
+ */
49275
+ function renderChangedLines(lines) {
49276
+ for (let i = 0; i < lines.length; i++) {
49277
+ if (previousFrameLines[i] === lines[i]) {
49278
+ continue;
49279
+ }
49280
+ rewriteFrameLine(lines.length, i, lines[i]);
49281
+ }
49282
+ }
49283
+ /**
49284
+ * Builds the current frame snapshot from the latest state.
49285
+ */
49286
+ function buildFrameLines() {
49287
+ return buildCoderRunUiFrame({
49288
+ terminalWidth: getTerminalWidth(),
49289
+ spinner: SPINNER_FRAMES[spinnerFrame],
49290
+ pauseState: getPauseState(),
49291
+ config: state.config,
49292
+ phase: state.phase,
49293
+ currentPromptLabel: state.currentPromptLabel,
49294
+ currentAttempt: state.currentAttempt,
49295
+ maxAttempts: state.maxAttempts,
49296
+ statusMessage: state.statusMessage,
49297
+ detailLines: state.detailLines,
49298
+ pendingEnterLabel: state.pendingEnterLabel,
49299
+ agentOutputLines: state.agentOutputLines,
49300
+ errors: state.errors,
49301
+ progress: state.getProgress(),
49302
+ });
49303
+ }
49304
+ /**
49305
+ * Clears previously rendered lines and writes a new frame only where needed.
49306
+ */
49307
+ function render(options) {
49308
+ if (isRendering || isDisposed) {
48935
49309
  return;
48936
49310
  }
48937
49311
  isRendering = true;
48938
49312
  try {
48939
- const lines = buildFrame();
48940
- // Move cursor up to clear the previous frame.
48941
- if (previousFrameLineCount > 0) {
48942
- process.stdout.write(`\x1b[${previousFrameLineCount}A`);
48943
- }
48944
- for (let i = 0; i < lines.length; i++) {
48945
- clearLine(process.stdout, 0);
48946
- cursorTo(process.stdout, 0);
48947
- process.stdout.write(lines[i]);
48948
- if (i < lines.length - 1) {
48949
- process.stdout.write('\n');
48950
- }
49313
+ const lines = buildFrameLines();
49314
+ if (previousFrameLines.length === 0 || previousFrameLines.length !== lines.length) {
49315
+ renderFullFrame(lines);
48951
49316
  }
48952
- // Clear any leftover lines from a previous longer frame.
48953
- if (lines.length < previousFrameLineCount) {
48954
- for (let i = lines.length; i < previousFrameLineCount; i++) {
48955
- process.stdout.write('\n');
48956
- clearLine(process.stdout, 0);
48957
- cursorTo(process.stdout, 0);
48958
- }
48959
- // Move back up to the end of the current frame.
48960
- const overshoot = previousFrameLineCount - lines.length;
48961
- if (overshoot > 0) {
48962
- process.stdout.write(`\x1b[${overshoot}A`);
48963
- }
49317
+ else {
49318
+ renderChangedLines(lines);
48964
49319
  }
48965
- previousFrameLineCount = lines.length;
49320
+ previousFrameLines = [...lines];
48966
49321
  spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
49322
+ if (!(options === null || options === void 0 ? void 0 : options.skipAutoRefresh)) {
49323
+ scheduleAutoRefresh();
49324
+ }
48967
49325
  }
48968
49326
  finally {
48969
49327
  isRendering = false;
48970
49328
  }
48971
49329
  }
48972
- /**
48973
- * Builds the complete frame as an array of terminal lines.
48974
- */
48975
- function buildFrame() {
48976
- const w = getTerminalWidth();
48977
- const sep = colors.gray('\u2500'.repeat(w - 2));
48978
- const spinner = SPINNER_FRAMES[spinnerFrame];
48979
- const { config, phase, currentPromptLabel, currentAttempt, maxAttempts, statusMessage, agentOutputLines, errors, } = state;
48980
- const progress = state.getProgress();
48981
- const isPaused = getPauseState() !== 'RUNNING';
48982
- const isActive = phase === 'running' || phase === 'verifying' || phase === 'loading';
48983
- const lines = [];
48984
- // --- Branding ---
48985
- lines.push(colors.bold.cyan('\u2728 Promptbook Coder'));
48986
- // --- Config ---
48987
- let configLine1 = `Agent: ${colors.bold.green(config.agentName)}`;
48988
- if (config.modelName) {
48989
- configLine1 += ` \u2502 Model: ${colors.bold(config.modelName)}`;
48990
- }
48991
- if (config.thinkingLevel) {
48992
- configLine1 += ` \u2502 Thinking: ${colors.bold(config.thinkingLevel)}`;
48993
- }
48994
- lines.push(configLine1);
48995
- let configLine2 = '';
48996
- if (config.context) {
48997
- configLine2 += `Context: ${colors.yellow(config.context)} \u2502 `;
48998
- }
48999
- configLine2 += `Priority: \u2265${config.priority}`;
49000
- if (config.testCommand) {
49001
- configLine2 += ` \u2502 Test: ${colors.gray(config.testCommand)}`;
49002
- }
49003
- lines.push(configLine2);
49004
- // --- Separator ---
49005
- lines.push(sep);
49006
- // --- Progress ---
49007
- const progressSummary = [
49008
- `${progress.sessionDone}/${progress.sessionTotal} Prompts (${progress.totalPrompts} total)`,
49009
- `${progress.elapsedText}/${progress.estimatedTotalText}`,
49010
- `Est. done ${progress.estimatedLabel}`,
49011
- ].join(' \u2502 ');
49012
- lines.push(progressSummary);
49013
- lines.push(buildProgressBar(progress.percentage));
49014
- // --- Separator ---
49015
- lines.push(sep);
49016
- // --- Current prompt ---
49017
- if (currentPromptLabel) {
49018
- const spinnerPrefix = isActive ? colors.yellow(`${spinner} `) : ' ';
49019
- lines.push(spinnerPrefix + colors.bold(currentPromptLabel));
49020
- lines.push(colors.gray(`Attempt ${currentAttempt}/${maxAttempts} \u2502 ${statusMessage}`));
49330
+ const keypressHandler = (_str, key) => {
49331
+ if (key.ctrl && key.name === 'c') {
49332
+ cleanup();
49333
+ process.exit(0);
49021
49334
  }
49022
- else {
49023
- lines.push(colors.gray(statusMessage));
49024
- }
49025
- // --- Agent output ---
49026
- if (agentOutputLines.length > 0) {
49027
- lines.push('');
49028
- lines.push(colors.gray.bold('Agent output:'));
49029
- const visibleLines = agentOutputLines.slice(-MAX_VISIBLE_OUTPUT_LINES);
49030
- for (const line of visibleLines) {
49031
- const cleanLine = stripAnsi(line);
49032
- // Truncate to terminal width.
49033
- const truncated = cleanLine.length > w - 2 ? cleanLine.slice(0, w - 5) + '...' : cleanLine;
49034
- lines.push(colors.gray(truncated));
49035
- }
49036
- }
49037
- // --- Errors ---
49038
- if (errors.length > 0) {
49039
- lines.push('');
49040
- for (const err of errors) {
49041
- lines.push(colors.red(`\u2717 ${err}`));
49042
- }
49043
- }
49044
- // --- Separator ---
49045
- lines.push(sep);
49046
- // --- Controls ---
49047
- const pauseLabel = isPaused
49048
- ? colors.bgYellow.black(' PAUSED ') + colors.gray(' [P] Resume \u2502 Ctrl+C Exit')
49049
- : colors.gray('[P] Pause \u2502 Ctrl+C Exit');
49050
- lines.push(pauseLabel);
49051
- return lines;
49052
- }
49053
- // Initial render.
49335
+ if (key.name === 'p') {
49336
+ if (getPauseState() === 'RUNNING') {
49337
+ requestPause();
49338
+ }
49339
+ else {
49340
+ requestResume();
49341
+ }
49342
+ scheduleRender();
49343
+ return;
49344
+ }
49345
+ if ((key.name === 'return' || key.name === 'enter') && pendingEnterResolver) {
49346
+ const resolvePendingEnter = pendingEnterResolver;
49347
+ pendingEnterResolver = undefined;
49348
+ state.setPendingEnterLabel(undefined);
49349
+ resolvePendingEnter();
49350
+ }
49351
+ };
49352
+ process.stdin.on('keypress', keypressHandler);
49353
+ process.stdout.on('resize', scheduleRender);
49054
49354
  process.stdout.write('\n');
49055
49355
  render();
49056
- const interval = setInterval(scheduleRender, UI_REFRESH_INTERVAL_MS);
49057
- // Listen for state changes and schedule a re-render (debounced).
49058
49356
  state.on('change', scheduleRender);
49059
- // --- Cleanup ---
49357
+ /**
49358
+ * Tears down the terminal UI and restores console / stdin state.
49359
+ */
49060
49360
  function cleanup() {
49061
- clearInterval(interval);
49361
+ if (isDisposed) {
49362
+ return;
49363
+ }
49364
+ if (autoRefreshTimeout) {
49365
+ clearTimeout(autoRefreshTimeout);
49366
+ autoRefreshTimeout = undefined;
49367
+ }
49062
49368
  state.off('change', scheduleRender);
49063
49369
  process.stdin.off('keypress', keypressHandler);
49370
+ process.stdout.off('resize', scheduleRender);
49064
49371
  if (process.stdin.isTTY) {
49065
49372
  process.stdin.setRawMode(false);
49066
49373
  }
49374
+ const resolvePendingEnter = pendingEnterResolver;
49375
+ pendingEnterResolver = undefined;
49376
+ resolvePendingEnter === null || resolvePendingEnter === void 0 ? void 0 : resolvePendingEnter();
49067
49377
  isCapturing = false;
49068
49378
  console.info = originalConsoleInfo;
49069
49379
  console.warn = originalConsoleWarn;
49070
49380
  console.error = originalConsoleError;
49071
49381
  console.log = originalConsoleLog;
49072
- // Render one final frame so the user sees the last state.
49073
- render();
49382
+ render({ skipAutoRefresh: true });
49074
49383
  process.stdout.write('\n');
49384
+ isDisposed = true;
49075
49385
  }
49076
49386
  return {
49077
49387
  state,
@@ -49081,6 +49391,19 @@ function renderCoderRunUi(startTime) {
49081
49391
  stopCapturingAgentOutput() {
49082
49392
  isCapturing = false;
49083
49393
  },
49394
+ waitForEnter(actionLabel) {
49395
+ if (pendingEnterResolver) {
49396
+ throw new Error('Coder run UI is already waiting for Enter.');
49397
+ }
49398
+ state.setPendingEnterLabel(actionLabel);
49399
+ scheduleRender();
49400
+ return new Promise((resolve) => {
49401
+ pendingEnterResolver = () => {
49402
+ scheduleRender();
49403
+ resolve();
49404
+ };
49405
+ });
49406
+ },
49084
49407
  cleanup,
49085
49408
  };
49086
49409
  }
@@ -49148,11 +49471,11 @@ async function runCodexPrompts(providedOptions) {
49148
49471
  `));
49149
49472
  }
49150
49473
  const runStartDate = moment();
49151
- const isUiMode = !options.dryRun && Boolean(process.stdout.isTTY);
49152
- const progressDisplay = options.dryRun || isUiMode ? undefined : new CliProgressDisplay(runStartDate);
49153
- const uiHandle = isUiMode ? renderCoderRunUi(runStartDate) : undefined;
49474
+ const isRichUiEnabled = !options.dryRun && !options.noUi && Boolean(process.stdout.isTTY);
49475
+ const progressDisplay = options.dryRun || options.noUi || isRichUiEnabled ? undefined : new CliProgressDisplay(runStartDate, options.priority);
49476
+ const uiHandle = isRichUiEnabled ? renderCoderRunUi(runStartDate) : undefined;
49154
49477
  // When the Ink UI is active it handles keyboard input itself, so skip the raw stdin listener.
49155
- if (!isUiMode) {
49478
+ if (!isRichUiEnabled) {
49156
49479
  listenForPause();
49157
49480
  }
49158
49481
  try {
@@ -49268,17 +49591,19 @@ async function runCodexPrompts(providedOptions) {
49268
49591
  let hasWaitedForStart = false;
49269
49592
  while (just(true)) {
49270
49593
  await checkPause({
49271
- silent: isUiMode,
49594
+ silent: isRichUiEnabled,
49272
49595
  onPaused: () => {
49596
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
49273
49597
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49274
49598
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('paused');
49275
49599
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Paused');
49276
49600
  },
49277
49601
  onResumed: () => {
49602
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
49278
49603
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49279
49604
  },
49280
49605
  });
49281
- if (isUiMode) {
49606
+ if (isRichUiEnabled) {
49282
49607
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
49283
49608
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Loading prompts...');
49284
49609
  }
@@ -49286,17 +49611,17 @@ async function runCodexPrompts(providedOptions) {
49286
49611
  const stats = summarizePrompts(promptFiles, options.priority);
49287
49612
  progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.update(stats);
49288
49613
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.updateProgress(stats);
49289
- if (!isUiMode) {
49614
+ if (!isRichUiEnabled) {
49290
49615
  printStats(stats, options.priority);
49291
49616
  }
49292
49617
  const nextPrompt = findNextTodoPrompt(promptFiles, options.priority);
49293
49618
  if (!hasShownUpcomingTasks) {
49294
- if (stats.toBeWritten > 0 && !isUiMode) {
49619
+ if (stats.toBeWritten > 0 && !isRichUiEnabled) {
49295
49620
  console.info(colors.yellow('Following prompts need to be written:'));
49296
49621
  printPromptsToBeWritten(promptFiles, options.priority);
49297
49622
  console.info('');
49298
49623
  }
49299
- if (!isUiMode) {
49624
+ if (!isRichUiEnabled) {
49300
49625
  printUpcomingTasks(listUpcomingTasks(promptFiles, options.priority));
49301
49626
  }
49302
49627
  hasShownUpcomingTasks = true;
@@ -49306,7 +49631,7 @@ async function runCodexPrompts(providedOptions) {
49306
49631
  const message = 'No prompts ready for agent.';
49307
49632
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
49308
49633
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
49309
- if (!isUiMode) {
49634
+ if (!isRichUiEnabled) {
49310
49635
  console.info(colors.yellow(message));
49311
49636
  }
49312
49637
  }
@@ -49314,16 +49639,28 @@ async function runCodexPrompts(providedOptions) {
49314
49639
  const message = 'All prompts are done.';
49315
49640
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
49316
49641
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
49317
- if (!isUiMode) {
49642
+ if (!isRichUiEnabled) {
49318
49643
  console.info(colors.green(message));
49319
49644
  }
49320
49645
  }
49321
49646
  return;
49322
49647
  }
49648
+ const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
49323
49649
  if (options.waitForUser) {
49650
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
49324
49651
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49325
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(hasWaitedForStart ? 'Waiting... Press Enter to continue' : 'Waiting... Press Enter to start');
49326
- await waitForPromptStart(nextPrompt.file, nextPrompt.section, !hasWaitedForStart);
49652
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
49653
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
49654
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(hasWaitedForStart ? 'Waiting for confirmation to continue' : 'Waiting for confirmation to start');
49655
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines([buildPromptSummary(nextPrompt.file, nextPrompt.section)]);
49656
+ if (isRichUiEnabled) {
49657
+ await (uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.waitForEnter(hasWaitedForStart ? 'Continue' : 'Start'));
49658
+ }
49659
+ else {
49660
+ await waitForPromptStart(nextPrompt.file, nextPrompt.section, !hasWaitedForStart);
49661
+ }
49662
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines([]);
49663
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
49327
49664
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49328
49665
  hasWaitedForStart = true;
49329
49666
  }
@@ -49333,9 +49670,7 @@ async function runCodexPrompts(providedOptions) {
49333
49670
  const commitMessage = buildCommitMessage(nextPrompt.file, nextPrompt.section);
49334
49671
  const codexPrompt = appendCoderContext(buildCodexPrompt(nextPrompt.file, nextPrompt.section), resolvedCoderContext);
49335
49672
  const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
49336
- const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
49337
- const promptRoundArtifacts = createCoderRunPromptRoundArtifacts(options.preserveLogs);
49338
- if (isUiMode) {
49673
+ if (isRichUiEnabled) {
49339
49674
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
49340
49675
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('running');
49341
49676
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Running');
@@ -49345,69 +49680,78 @@ async function runCodexPrompts(providedOptions) {
49345
49680
  }
49346
49681
  const promptExecutionStartedDate = moment();
49347
49682
  let attemptCount = 1;
49348
- let promptRoundOutcome = 'failure';
49349
49683
  const roundChangedFilesSnapshot = options.normalizeLineEndings
49350
49684
  ? await captureChangedFilesSnapshot(process.cwd())
49351
49685
  : undefined;
49352
- try {
49353
- await withPromptRuntimeLog(scriptPath, async (logPath) => {
49354
- try {
49355
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
49356
- const result = await runPromptWithTestFeedback({
49357
- runner,
49358
- prompt: codexPrompt,
49359
- scriptPath,
49360
- projectPath: process.cwd(),
49361
- promptLabel,
49362
- testCommand: options.testCommand,
49363
- logPath,
49364
- promptRoundArtifacts,
49365
- onAttemptStarted: (nextAttemptCount) => {
49366
- attemptCount = nextAttemptCount;
49367
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(nextAttemptCount);
49368
- if (nextAttemptCount > 1) {
49369
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Retrying (attempt ${nextAttemptCount})`);
49370
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('verifying');
49371
- }
49372
- },
49373
- });
49374
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49375
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Committing changes');
49376
- markPromptDone(nextPrompt.file, nextPrompt.section, result.usage, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, result.attemptCount);
49377
- await writePromptFile(nextPrompt.file);
49378
- await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49379
- if (options.waitForUser) {
49380
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49381
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Waiting... Press Enter to commit');
49686
+ await withPromptRuntimeLog(scriptPath, async (logPath) => {
49687
+ try {
49688
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
49689
+ const result = await runPromptWithTestFeedback({
49690
+ runner,
49691
+ prompt: codexPrompt,
49692
+ scriptPath,
49693
+ projectPath: process.cwd(),
49694
+ promptLabel,
49695
+ testCommand: options.testCommand,
49696
+ preserveArtifactsOnSuccess: options.preserveLogs,
49697
+ logPath,
49698
+ onAttemptStarted: (nextAttemptCount) => {
49699
+ attemptCount = nextAttemptCount;
49700
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(nextAttemptCount);
49701
+ if (nextAttemptCount > 1) {
49702
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Retrying (attempt ${nextAttemptCount})`);
49703
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('verifying');
49704
+ }
49705
+ },
49706
+ });
49707
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49708
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Committing changes');
49709
+ markPromptDone(nextPrompt.file, nextPrompt.section, result.usage, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, result.attemptCount);
49710
+ await writePromptFile(nextPrompt.file);
49711
+ await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49712
+ if (options.waitForUser) {
49713
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
49714
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49715
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
49716
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Review the commit preview and confirm to continue');
49717
+ if (isRichUiEnabled) {
49718
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines(formatCommitMessageForDisplay(commitMessage)
49719
+ .split(/\r?\n/)
49720
+ .map((line) => line.trim()));
49721
+ await (uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.waitForEnter('Commit'));
49722
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines([]);
49723
+ }
49724
+ else {
49382
49725
  printCommitMessage(commitMessage);
49383
49726
  await waitForEnter(colors.bgWhite('Press Enter to commit and continue...'));
49384
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49385
49727
  }
49386
- await commitChanges(commitMessage, { autoPush: options.autoPush });
49387
- await runPostPromptAutoMigrationIfEnabled(options);
49388
- promptRoundOutcome = 'success';
49389
- }
49390
- catch (error) {
49391
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49392
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
49393
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
49394
- markPromptFailed(nextPrompt.file, nextPrompt.section, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, attemptCount);
49395
- await writePromptFile(nextPrompt.file);
49396
- await writePromptErrorLog({
49397
- file: nextPrompt.file,
49398
- section: nextPrompt.section,
49399
- runnerName: runnerMetadata.runnerName,
49400
- modelName: runnerMetadata.modelName,
49401
- error,
49402
- });
49403
- await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49404
- throw error;
49728
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
49729
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49405
49730
  }
49406
- }, promptRoundArtifacts);
49407
- }
49408
- finally {
49409
- await promptRoundArtifacts.cleanup(promptRoundOutcome);
49410
- }
49731
+ await commitChanges(commitMessage, {
49732
+ autoPush: options.autoPush,
49733
+ // Keep the live runtime log out of default commits because it is deleted after a successful round.
49734
+ excludePaths: options.preserveLogs ? undefined : [logPath],
49735
+ });
49736
+ await runPostPromptAutoMigrationIfEnabled(options);
49737
+ }
49738
+ catch (error) {
49739
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49740
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
49741
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
49742
+ markPromptFailed(nextPrompt.file, nextPrompt.section, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, attemptCount);
49743
+ await writePromptFile(nextPrompt.file);
49744
+ await writePromptErrorLog({
49745
+ file: nextPrompt.file,
49746
+ section: nextPrompt.section,
49747
+ runnerName: runnerMetadata.runnerName,
49748
+ modelName: runnerMetadata.modelName,
49749
+ error,
49750
+ });
49751
+ await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49752
+ throw error;
49753
+ }
49754
+ }, { preserveArtifactsOnSuccess: options.preserveLogs });
49411
49755
  }
49412
49756
  }
49413
49757
  finally {