@promptbook/cli 0.112.0-44 → 0.112.0-45

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 (59) hide show
  1. package/esm/index.es.js +675 -491
  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/testing/runPromptTestCommand.d.ts +1 -0
  14. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +15 -20
  15. package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +28 -0
  16. package/esm/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +2 -0
  17. package/esm/src/avatars/Avatar.d.ts +7 -0
  18. package/esm/src/avatars/avatarRenderingUtils.d.ts +117 -0
  19. package/esm/src/avatars/index.d.ts +6 -0
  20. package/esm/src/avatars/renderAvatarVisual.d.ts +9 -0
  21. package/esm/src/avatars/types/AvatarDefinition.d.ts +20 -0
  22. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +96 -0
  23. package/esm/src/avatars/visuals/avatarVisualRegistry.d.ts +16 -0
  24. package/esm/src/avatars/visuals/minecraftAvatarVisual.d.ts +7 -0
  25. package/esm/src/avatars/visuals/octopusAvatarVisual.d.ts +7 -0
  26. package/esm/src/avatars/visuals/pixelArtAvatarVisual.d.ts +7 -0
  27. package/esm/src/commitments/STYLE/STYLE.d.ts +9 -2
  28. package/esm/src/version.d.ts +1 -1
  29. package/package.json +1 -1
  30. package/umd/index.umd.js +675 -491
  31. package/umd/index.umd.js.map +1 -1
  32. package/umd/scripts/run-codex-prompts/common/CoderRunTimer.d.ts +31 -0
  33. package/umd/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +23 -0
  34. package/umd/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +13 -4
  35. package/umd/scripts/run-codex-prompts/common/progressFormatting.d.ts +16 -0
  36. package/umd/scripts/run-codex-prompts/common/runGoScript/$runGoScript.d.ts +1 -1
  37. package/umd/scripts/run-codex-prompts/common/runGoScript/$runGoScriptUntilMarkerIdle.d.ts +1 -1
  38. package/umd/scripts/run-codex-prompts/common/runGoScript/$runGoScriptWithOutput.d.ts +1 -1
  39. package/umd/scripts/run-codex-prompts/common/runGoScript/shouldDeleteTemporaryArtifact.d.ts +7 -0
  40. package/umd/scripts/run-codex-prompts/common/runGoScript/withPromptRuntimeLog.d.ts +4 -3
  41. package/umd/scripts/run-codex-prompts/common/runGoScript/withTempScript.d.ts +1 -1
  42. package/umd/scripts/run-codex-prompts/testing/runPromptTestCommand.d.ts +1 -0
  43. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +15 -20
  44. package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +28 -0
  45. package/umd/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +2 -0
  46. package/umd/src/avatars/Avatar.d.ts +7 -0
  47. package/umd/src/avatars/avatarRenderingUtils.d.ts +117 -0
  48. package/umd/src/avatars/index.d.ts +6 -0
  49. package/umd/src/avatars/renderAvatarVisual.d.ts +9 -0
  50. package/umd/src/avatars/types/AvatarDefinition.d.ts +20 -0
  51. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +96 -0
  52. package/umd/src/avatars/visuals/avatarVisualRegistry.d.ts +16 -0
  53. package/umd/src/avatars/visuals/minecraftAvatarVisual.d.ts +7 -0
  54. package/umd/src/avatars/visuals/octopusAvatarVisual.d.ts +7 -0
  55. package/umd/src/avatars/visuals/pixelArtAvatarVisual.d.ts +7 -0
  56. package/umd/src/commitments/STYLE/STYLE.d.ts +9 -2
  57. package/umd/src/version.d.ts +1 -1
  58. package/esm/scripts/run-codex-prompts/common/runGoScript/PromptRoundArtifacts.d.ts +0 -35
  59. 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-45';
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.
44826
44981
  */
44827
- async function withPromptRuntimeLog(scriptPath, handler, promptRoundArtifacts) {
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.
44988
+ */
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
  */
@@ -46737,6 +46846,15 @@ function buildPromptLabelForDisplay(file, section) {
46737
46846
  return `${relative(process.cwd(), file.path).replace(/\\/g, '/')}#${section.startLine + 1}`;
46738
46847
  }
46739
46848
 
46849
+ /**
46850
+ * Extracts a short summary line from a prompt section.
46851
+ */
46852
+ function buildPromptSummary(file, section) {
46853
+ const lines = buildCodexPrompt(file, section).split(/\r?\n/);
46854
+ const firstLine = lines.find((line) => line.trim() !== '');
46855
+ return (firstLine === null || firstLine === void 0 ? void 0 : firstLine.trim()) || '(empty prompt)';
46856
+ }
46857
+
46740
46858
  /**
46741
46859
  * Builds the script path for a prompt section.
46742
46860
  */
@@ -46796,15 +46914,6 @@ function findNextTodoPrompt(files, minimumPriority = 0) {
46796
46914
  return nextPrompt;
46797
46915
  }
46798
46916
 
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
46917
  /**
46809
46918
  * Lists upcoming tasks that are ready to run (no authoring placeholders).
46810
46919
  */
@@ -47356,25 +47465,29 @@ async function runBashScriptWithOutput(options) {
47356
47465
  }
47357
47466
 
47358
47467
  /**
47359
- * Creates a temporary script file, runs a handler, and either cleans it up immediately or defers cleanup to the round tracker.
47468
+ * Creates a temporary script file, runs a handler, and cleans it up unless preservation is requested or the run fails.
47360
47469
  */
47361
47470
  async function withTempScript(options, handler) {
47362
- const { scriptPath, scriptContent, promptRoundArtifacts } = options;
47471
+ const { scriptPath, scriptContent } = options;
47472
+ let hasFailed = false;
47363
47473
  await mkdir(dirname$1(scriptPath), { recursive: true });
47364
47474
  await writeFile(scriptPath, scriptContent, 'utf-8');
47365
- promptRoundArtifacts === null || promptRoundArtifacts === void 0 ? void 0 : promptRoundArtifacts.track(scriptPath, getPromptRoundArtifactKindFromScriptPath(scriptPath));
47366
47475
  try {
47367
47476
  return await handler(scriptPath);
47368
47477
  }
47478
+ catch (error) {
47479
+ hasFailed = true;
47480
+ throw error;
47481
+ }
47369
47482
  finally {
47370
- if (!promptRoundArtifacts) {
47483
+ if (shouldDeleteTemporaryArtifact({ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess, hasFailed })) {
47371
47484
  await unlink(scriptPath).catch(() => undefined);
47372
47485
  }
47373
47486
  }
47374
47487
  }
47375
47488
 
47376
47489
  /**
47377
- * Creates a temporary script file, runs it, captures output, and cleans it up immediately unless a round tracker defers that cleanup.
47490
+ * Creates a temporary script file, runs it, captures output, and cleans it up unless preservation is requested or the run fails.
47378
47491
  */
47379
47492
  async function $runGoScriptWithOutput(options) {
47380
47493
  return await withTempScript(options, async (scriptPath) => {
@@ -47477,7 +47590,7 @@ class ClaudeCodeRunner {
47477
47590
  scriptPath: options.scriptPath,
47478
47591
  scriptContent,
47479
47592
  logPath: options.logPath,
47480
- promptRoundArtifacts: options.promptRoundArtifacts,
47593
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47481
47594
  });
47482
47595
  const usage = parseClaudeCodeJsonOutput(output);
47483
47596
  return { usage };
@@ -47485,7 +47598,7 @@ class ClaudeCodeRunner {
47485
47598
  }
47486
47599
 
47487
47600
  /**
47488
- * Creates a temporary script file, runs it, and cleans it up immediately unless a round tracker defers that cleanup.
47601
+ * Creates a temporary script file, runs it, and cleans it up unless preservation is requested or the run fails.
47489
47602
  */
47490
47603
  async function $runGoScript(options) {
47491
47604
  await withTempScript(options, async (scriptPath) => {
@@ -47541,7 +47654,7 @@ class ClineRunner {
47541
47654
  scriptPath: options.scriptPath,
47542
47655
  scriptContent,
47543
47656
  logPath: options.logPath,
47544
- promptRoundArtifacts: options.promptRoundArtifacts,
47657
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47545
47658
  });
47546
47659
  return { usage: UNCERTAIN_USAGE };
47547
47660
  }
@@ -47666,7 +47779,7 @@ class GeminiRunner {
47666
47779
  scriptPath: options.scriptPath,
47667
47780
  scriptContent,
47668
47781
  logPath: options.logPath,
47669
- promptRoundArtifacts: options.promptRoundArtifacts,
47782
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47670
47783
  });
47671
47784
  const usage = parseGeminiUsageFromOutput(output, options.prompt, this.options.model);
47672
47785
  return { usage };
@@ -47723,7 +47836,7 @@ class GitHubCopilotRunner {
47723
47836
  scriptPath: options.scriptPath,
47724
47837
  scriptContent,
47725
47838
  logPath: options.logPath,
47726
- promptRoundArtifacts: options.promptRoundArtifacts,
47839
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47727
47840
  });
47728
47841
  return { usage: UNCERTAIN_USAGE };
47729
47842
  }
@@ -47899,7 +48012,7 @@ async function runScriptUntilMarkerIdle(options) {
47899
48012
  }
47900
48013
 
47901
48014
  /**
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.
48015
+ * 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
48016
  * Returns the captured output for post-processing.
47904
48017
  */
47905
48018
  async function $runGoScriptUntilMarkerIdle(options) {
@@ -48288,7 +48401,7 @@ class OpenAiCodexRunner {
48288
48401
  completionLineMatcher: CODEX_COMPLETION_LINE,
48289
48402
  idleTimeoutMs: CODEX_COMPLETION_IDLE_MS,
48290
48403
  logPath: options.logPath,
48291
- promptRoundArtifacts: options.promptRoundArtifacts,
48404
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48292
48405
  });
48293
48406
  this.rateLimitBackoff.reset();
48294
48407
  return { usage: buildCodexUsageFromOutput(output, this.options.model) };
@@ -48423,7 +48536,7 @@ class OpencodeRunner {
48423
48536
  scriptPath: options.scriptPath,
48424
48537
  scriptContent,
48425
48538
  logPath: options.logPath,
48426
- promptRoundArtifacts: options.promptRoundArtifacts,
48539
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48427
48540
  });
48428
48541
  }
48429
48542
  catch (error) {
@@ -48451,6 +48564,7 @@ async function runPromptTestCommand(options) {
48451
48564
  ${options.command}
48452
48565
  `),
48453
48566
  logPath: options.logPath,
48567
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48454
48568
  });
48455
48569
  }
48456
48570
 
@@ -48475,7 +48589,7 @@ async function runPromptWithTestFeedback(options) {
48475
48589
  scriptPath: options.scriptPath,
48476
48590
  projectPath: options.projectPath,
48477
48591
  logPath: options.logPath,
48478
- promptRoundArtifacts: options.promptRoundArtifacts,
48592
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48479
48593
  });
48480
48594
  return { ...result, attemptCount: 1 };
48481
48595
  }
@@ -48488,7 +48602,7 @@ async function runPromptWithTestFeedback(options) {
48488
48602
  scriptPath: options.scriptPath,
48489
48603
  projectPath: options.projectPath,
48490
48604
  logPath: options.logPath,
48491
- promptRoundArtifacts: options.promptRoundArtifacts,
48605
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48492
48606
  });
48493
48607
  console.info(colors.gray(`Running verification command after attempt #${attemptCount}: ${normalizedTestCommand}`));
48494
48608
  try {
@@ -48497,6 +48611,7 @@ async function runPromptWithTestFeedback(options) {
48497
48611
  projectPath: options.projectPath,
48498
48612
  scriptPath: buildPromptTestScriptPath(options.scriptPath),
48499
48613
  logPath: options.logPath,
48614
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48500
48615
  });
48501
48616
  return { ...result, attemptCount };
48502
48617
  }
@@ -48578,24 +48693,203 @@ function buildPromptTestScriptPath(scriptPath) {
48578
48693
  }
48579
48694
 
48580
48695
  /**
48581
- * Maximum number of agent output lines kept in the scrolling output area.
48582
- *
48583
- * @private internal constant of coder run UI
48696
+ * Maximum number of output lines reserved for agent output in the UI.
48584
48697
  */
48585
- const MAX_AGENT_OUTPUT_LINES = 12;
48698
+ const MAX_VISIBLE_OUTPUT_LINES = 8;
48586
48699
  /**
48587
- * Calendar formats used when displaying the estimated completion time.
48700
+ * Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
48701
+ */
48702
+ function buildCoderRunUiFrame(options) {
48703
+ const totalWidth = Math.max(56, Math.min(options.terminalWidth, 96));
48704
+ const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
48705
+ const promptStatusPrefix = isPromptActive ? `${colors.yellow(`${options.spinner} `)}` : '';
48706
+ const sessionScopeLine = options.progress.sessionTotal > 0
48707
+ ? `Working on ${options.progress.currentPromptIndex}/${options.progress.sessionTotal} prompts with Priority ≥${options.config.priority}`
48708
+ : `No runnable prompts with Priority ≥${options.config.priority}`;
48709
+ const sessionCountLine = `Done ${options.progress.sessionDone}/${options.progress.sessionTotal} this run · Repo total ${options.progress.totalPrompts}`;
48710
+ const sessionQueueParts = [];
48711
+ if (options.progress.skippedPrompts > 0) {
48712
+ sessionQueueParts.push(`Skipping ${formatPromptCount(options.progress.skippedPrompts)} with Priority <${options.config.priority}`);
48713
+ }
48714
+ if (options.progress.toBeWrittenPrompts > 0) {
48715
+ sessionQueueParts.push(`Write first ${formatPromptCount(options.progress.toBeWrittenPrompts)}`);
48716
+ }
48717
+ const sessionLines = [
48718
+ `${buildPhaseBadge(options.phase, options.pauseState)} ${fitPlainText(options.statusMessage, totalWidth - 18)}`,
48719
+ sessionScopeLine,
48720
+ sessionCountLine,
48721
+ ...(sessionQueueParts.length > 0 ? [sessionQueueParts.join(' · ')] : []),
48722
+ `Elapsed ${options.progress.elapsedText} · Est. total ${options.progress.estimatedTotalText} · Est. done ${options.progress.estimatedLabel}`,
48723
+ buildProgressBar(options.progress.percentage, totalWidth - 6, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
48724
+ ];
48725
+ const metadataParts = [options.config.agentName || 'No agent selected'];
48726
+ if (options.config.modelName) {
48727
+ metadataParts.push(options.config.modelName);
48728
+ }
48729
+ if (options.config.thinkingLevel) {
48730
+ metadataParts.push(`thinking ${options.config.thinkingLevel}`);
48731
+ }
48732
+ const runnerDetails = [
48733
+ [`${colors.bgCyan.black(' PTBK ')}`, colors.bgBlue.white(' CODER '), colors.bold.white(' Promptbook Coder')]
48734
+ .join(''),
48735
+ metadataParts.join(' · '),
48736
+ buildConfigSummaryLine(options.config),
48737
+ ];
48738
+ const currentTaskLines = options.currentPromptLabel
48739
+ ? [
48740
+ `${promptStatusPrefix}${colors.bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
48741
+ `Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
48742
+ ...options.detailLines.map((detailLine) => `• ${detailLine}`),
48743
+ ]
48744
+ : [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
48745
+ const visibleOutputLines = options.agentOutputLines.length > 0
48746
+ ? options.agentOutputLines.slice(-MAX_VISIBLE_OUTPUT_LINES).map((line) => `› ${stripAnsi(line)}`)
48747
+ : ['No live agent output yet.'];
48748
+ const controls = buildControlPills(options.pauseState, options.pendingEnterLabel).join(' ');
48749
+ const frame = [
48750
+ ...renderBox('Brand', runnerDetails, totalWidth, colors.cyan.bold),
48751
+ ...renderBox('Session', sessionLines, totalWidth, colors.yellow.bold),
48752
+ ...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors.magenta.bold),
48753
+ ...renderBox('Live output', visibleOutputLines, totalWidth, colors.green.bold),
48754
+ ];
48755
+ if (options.errors.length > 0) {
48756
+ frame.push(...renderBox('Errors', options.errors.map((errorLine) => `${colors.red('✗')} ${errorLine}`), totalWidth, colors.red.bold));
48757
+ }
48758
+ frame.push(...renderBox('Controls', [controls], totalWidth, colors.white.bold));
48759
+ return frame;
48760
+ }
48761
+ /**
48762
+ * Renders a framed box with a colored title and padded body lines.
48763
+ */
48764
+ function renderBox(title, lines, totalWidth, colorizeTitle) {
48765
+ const bodyWidth = Math.max(10, totalWidth - 4);
48766
+ const titleText = ` ${title} `;
48767
+ const topBorder = colors.gray('┌') +
48768
+ colorizeTitle(titleText) +
48769
+ colors.gray('─'.repeat(Math.max(0, totalWidth - 2 - titleText.length)) + '┐');
48770
+ const body = lines.map((line) => {
48771
+ const paddedLine = padAnsiText(line, bodyWidth);
48772
+ return colors.gray('│ ') + paddedLine + colors.gray(' │');
48773
+ });
48774
+ const bottomBorder = colors.gray(`└${'─'.repeat(totalWidth - 2)}┘`);
48775
+ return [topBorder, ...body, bottomBorder];
48776
+ }
48777
+ /**
48778
+ * Builds the compact config summary line shown in the branding box.
48779
+ */
48780
+ function buildConfigSummaryLine(config) {
48781
+ const parts = [`Priority ≥${config.priority}`];
48782
+ if (config.context) {
48783
+ parts.unshift(`Context ${config.context}`);
48784
+ }
48785
+ if (config.testCommand) {
48786
+ parts.push(`Test ${config.testCommand}`);
48787
+ }
48788
+ return parts.join(' · ');
48789
+ }
48790
+ /**
48791
+ * Builds the colored phase badge shown in the session box.
48792
+ */
48793
+ function buildPhaseBadge(phase, pauseState) {
48794
+ if (pauseState !== 'RUNNING' || phase === 'paused') {
48795
+ return colors.bgYellow.black(' PAUSED ');
48796
+ }
48797
+ switch (phase) {
48798
+ case 'loading':
48799
+ case 'initializing':
48800
+ return colors.bgCyan.black(' LOADING ');
48801
+ case 'running':
48802
+ return colors.bgGreen.black(' RUNNING ');
48803
+ case 'verifying':
48804
+ return colors.bgMagenta.white(' VERIFYING ');
48805
+ case 'waiting':
48806
+ return colors.bgBlue.white(' WAITING ');
48807
+ case 'done':
48808
+ return colors.bgGreen.black(' DONE ');
48809
+ case 'error':
48810
+ return colors.bgRed.white(' ERROR ');
48811
+ default:
48812
+ return colors.bgWhite.black(' READY ');
48813
+ }
48814
+ }
48815
+ /**
48816
+ * Builds the progress bar shown in the session box.
48817
+ */
48818
+ function buildProgressBar(percentage, availableWidth, label) {
48819
+ const percentageLabel = label;
48820
+ const barWidth = Math.max(10, availableWidth - percentageLabel.length - 1);
48821
+ const filledWidth = Math.round((percentage / 100) * barWidth);
48822
+ const emptyWidth = Math.max(0, barWidth - filledWidth);
48823
+ return `${colors.green('█'.repeat(filledWidth))}${colors.gray('░'.repeat(emptyWidth))} ${percentageLabel}`;
48824
+ }
48825
+ /**
48826
+ * Formats a prompt count with singular/plural wording.
48827
+ */
48828
+ function formatPromptCount(count) {
48829
+ return `${count} prompt${count === 1 ? '' : 's'}`;
48830
+ }
48831
+ /**
48832
+ * Builds the control pills shown in the footer box.
48833
+ */
48834
+ function buildControlPills(pauseState, pendingEnterLabel) {
48835
+ const pills = [];
48836
+ if (pendingEnterLabel) {
48837
+ pills.push(colors.bgWhite.black(' ENTER ') + colors.white(` ${pendingEnterLabel}`));
48838
+ }
48839
+ pills.push(pauseState === 'RUNNING'
48840
+ ? colors.bgYellow.black(' P ') + colors.white(' Pause')
48841
+ : colors.bgYellow.black(' P ') + colors.white(' Resume'));
48842
+ pills.push(colors.bgRed.white(' CTRL+C ') + colors.white(' Exit'));
48843
+ return pills;
48844
+ }
48845
+ /**
48846
+ * Pads or truncates a possibly ANSI-colored line to the target visible width.
48847
+ */
48848
+ function padAnsiText(text, width) {
48849
+ const fittedText = fitAnsiText(text, width);
48850
+ return fittedText + ' '.repeat(Math.max(0, width - visibleLength(fittedText)));
48851
+ }
48852
+ /**
48853
+ * Truncates a possibly ANSI-colored line to the target visible width.
48854
+ */
48855
+ function fitAnsiText(text, width) {
48856
+ if (visibleLength(text) <= width) {
48857
+ return text;
48858
+ }
48859
+ return fitPlainText(stripAnsi(text), width);
48860
+ }
48861
+ /**
48862
+ * Truncates a plain-text line to the target width with an ellipsis.
48863
+ */
48864
+ function fitPlainText(text, width) {
48865
+ if (text.length <= width) {
48866
+ return text;
48867
+ }
48868
+ if (width <= 3) {
48869
+ return '.'.repeat(width);
48870
+ }
48871
+ return `${text.slice(0, width - 3)}...`;
48872
+ }
48873
+ /**
48874
+ * Measures visible string width by stripping ANSI escape codes.
48875
+ */
48876
+ function visibleLength(text) {
48877
+ return stripAnsi(text).length;
48878
+ }
48879
+ /**
48880
+ * Strips ANSI escape codes from a string.
48881
+ */
48882
+ function stripAnsi(text) {
48883
+ // eslint-disable-next-line no-control-regex
48884
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
48885
+ }
48886
+
48887
+ /**
48888
+ * Maximum number of agent output lines kept in the scrolling output area.
48588
48889
  *
48589
48890
  * @private internal constant of coder run UI
48590
48891
  */
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
- };
48892
+ const MAX_AGENT_OUTPUT_LINES = 12;
48599
48893
  /**
48600
48894
  * Reactive state manager for the coder run terminal UI.
48601
48895
  *
@@ -48611,35 +48905,27 @@ class CoderRunUiState extends EventEmitter {
48611
48905
  this.currentPromptLabel = '';
48612
48906
  this.currentAttempt = 1;
48613
48907
  this.maxAttempts = 3;
48908
+ this.detailLines = [];
48614
48909
  this.agentOutputLines = [];
48615
48910
  this.phase = 'initializing';
48616
48911
  this.statusMessage = 'Initializing...';
48617
48912
  this.errors = [];
48618
48913
  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();
48914
+ this.timer = new CoderRunTimer(startTime, true);
48626
48915
  }
48627
48916
  /**
48628
48917
  * Pauses the elapsed timer (e.g. while waiting for user input or paused state).
48629
48918
  */
48630
48919
  pauseTimer() {
48631
- if (this.pausedSince === undefined) {
48632
- this.pausedSince = moment();
48633
- }
48920
+ this.timer.pause();
48921
+ this.emitChange();
48634
48922
  }
48635
48923
  /**
48636
48924
  * Resumes the elapsed timer after a pause.
48637
48925
  */
48638
48926
  resumeTimer() {
48639
- if (this.pausedSince !== undefined) {
48640
- this.pausedMs += moment().diff(this.pausedSince);
48641
- this.pausedSince = undefined;
48642
- }
48927
+ this.timer.resume();
48928
+ this.emitChange();
48643
48929
  }
48644
48930
  /**
48645
48931
  * Replaces the configuration shown in the UI header.
@@ -48663,41 +48949,15 @@ class CoderRunUiState extends EventEmitter {
48663
48949
  */
48664
48950
  getProgress() {
48665
48951
  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
- };
48952
+ return buildCoderRunProgressSnapshot(this.stats, this.timer.getElapsedDuration(), (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done);
48695
48953
  }
48696
48954
  /**
48697
48955
  * Sets the label of the prompt currently being processed and resets per-prompt state.
48698
48956
  */
48699
48957
  setCurrentPrompt(label) {
48700
48958
  this.currentPromptLabel = label;
48959
+ this.detailLines = [];
48960
+ this.pendingEnterLabel = undefined;
48701
48961
  this.agentOutputLines = [];
48702
48962
  this.currentAttempt = 1;
48703
48963
  this.emitChange();
@@ -48737,6 +48997,20 @@ class CoderRunUiState extends EventEmitter {
48737
48997
  this.statusMessage = message;
48738
48998
  this.emitChange();
48739
48999
  }
49000
+ /**
49001
+ * Replaces the contextual detail lines shown beneath the current prompt status.
49002
+ */
49003
+ setDetailLines(detailLines) {
49004
+ this.detailLines = detailLines.filter((detailLine) => detailLine.trim() !== '');
49005
+ this.emitChange();
49006
+ }
49007
+ /**
49008
+ * Sets or clears the Enter-key action label shown in the controls panel.
49009
+ */
49010
+ setPendingEnterLabel(pendingEnterLabel) {
49011
+ this.pendingEnterLabel = pendingEnterLabel;
49012
+ this.emitChange();
49013
+ }
48740
49014
  /**
48741
49015
  * Appends an error message to the error list shown in the UI.
48742
49016
  */
@@ -48748,31 +49022,6 @@ class CoderRunUiState extends EventEmitter {
48748
49022
  this.emit('change');
48749
49023
  }
48750
49024
  }
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
49025
 
48777
49026
  /**
48778
49027
  * Refresh interval for the terminal UI in milliseconds.
@@ -48780,18 +49029,6 @@ function formatDurationBrief(duration) {
48780
49029
  * @private internal constant of coder run UI
48781
49030
  */
48782
49031
  const UI_REFRESH_INTERVAL_MS = 200;
48783
- /**
48784
- * Character width used for the text progress bar.
48785
- *
48786
- * @private internal constant of coder run UI
48787
- */
48788
- const PROGRESS_BAR_WIDTH = 40;
48789
- /**
48790
- * Maximum number of output lines reserved for agent output in the UI.
48791
- *
48792
- * @private internal constant of coder run UI
48793
- */
48794
- const MAX_VISIBLE_OUTPUT_LINES = 8;
48795
49032
  /**
48796
49033
  * Spinner animation frames.
48797
49034
  *
@@ -48810,31 +49047,12 @@ const SPINNER_FRAMES = [
48810
49047
  '\u280F',
48811
49048
  ];
48812
49049
  /**
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.
49050
+ * Returns the usable terminal width, capped at 96.
48823
49051
  *
48824
49052
  * @private internal utility of coder run UI
48825
49053
  */
48826
49054
  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}%`;
49055
+ return Math.min(process.stdout.columns || 80, 96);
48838
49056
  }
48839
49057
  /**
48840
49058
  * Boots the ANSI terminal UI for `ptbk coder run`.
@@ -48855,21 +49073,20 @@ function renderCoderRunUi(startTime) {
48855
49073
  state,
48856
49074
  startCapturingAgentOutput: () => { },
48857
49075
  stopCapturingAgentOutput: () => { },
49076
+ waitForEnter: async () => { },
48858
49077
  cleanup: () => { },
48859
49078
  };
48860
49079
  }
48861
- // --- Console interception ---
48862
49080
  const originalConsoleInfo = console.info;
48863
49081
  const originalConsoleWarn = console.warn;
48864
49082
  const originalConsoleError = console.error;
48865
49083
  const originalConsoleLog = console.log;
48866
49084
  let isCapturing = false;
49085
+ let pendingEnterResolver;
48867
49086
  console.info = (...args) => {
48868
49087
  if (isCapturing) {
48869
49088
  state.addAgentOutput(args.map(String).join(' '));
48870
49089
  }
48871
- // In UI mode, non-captured output is intentionally suppressed
48872
- // so it does not interfere with the repainted frame.
48873
49090
  };
48874
49091
  console.warn = (...args) => {
48875
49092
  if (isCapturing) {
@@ -48886,29 +49103,11 @@ function renderCoderRunUi(startTime) {
48886
49103
  state.addAgentOutput(args.map(String).join(' '));
48887
49104
  }
48888
49105
  };
48889
- // --- Keyboard input (pause) ---
48890
49106
  const readline = require('readline');
48891
49107
  readline.emitKeypressEvents(process.stdin);
48892
49108
  if (process.stdin.isTTY) {
48893
49109
  process.stdin.setRawMode(true);
48894
49110
  }
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
49111
  let spinnerFrame = 0;
48913
49112
  let previousFrameLineCount = 0;
48914
49113
  let isRendering = false;
@@ -48936,8 +49135,22 @@ function renderCoderRunUi(startTime) {
48936
49135
  }
48937
49136
  isRendering = true;
48938
49137
  try {
48939
- const lines = buildFrame();
48940
- // Move cursor up to clear the previous frame.
49138
+ const lines = buildCoderRunUiFrame({
49139
+ terminalWidth: getTerminalWidth(),
49140
+ spinner: SPINNER_FRAMES[spinnerFrame],
49141
+ pauseState: getPauseState(),
49142
+ config: state.config,
49143
+ phase: state.phase,
49144
+ currentPromptLabel: state.currentPromptLabel,
49145
+ currentAttempt: state.currentAttempt,
49146
+ maxAttempts: state.maxAttempts,
49147
+ statusMessage: state.statusMessage,
49148
+ detailLines: state.detailLines,
49149
+ pendingEnterLabel: state.pendingEnterLabel,
49150
+ agentOutputLines: state.agentOutputLines,
49151
+ errors: state.errors,
49152
+ progress: state.getProgress(),
49153
+ });
48941
49154
  if (previousFrameLineCount > 0) {
48942
49155
  process.stdout.write(`\x1b[${previousFrameLineCount}A`);
48943
49156
  }
@@ -48949,14 +49162,12 @@ function renderCoderRunUi(startTime) {
48949
49162
  process.stdout.write('\n');
48950
49163
  }
48951
49164
  }
48952
- // Clear any leftover lines from a previous longer frame.
48953
49165
  if (lines.length < previousFrameLineCount) {
48954
49166
  for (let i = lines.length; i < previousFrameLineCount; i++) {
48955
49167
  process.stdout.write('\n');
48956
49168
  clearLine(process.stdout, 0);
48957
49169
  cursorTo(process.stdout, 0);
48958
49170
  }
48959
- // Move back up to the end of the current frame.
48960
49171
  const overshoot = previousFrameLineCount - lines.length;
48961
49172
  if (overshoot > 0) {
48962
49173
  process.stdout.write(`\x1b[${overshoot}A`);
@@ -48969,94 +49180,35 @@ function renderCoderRunUi(startTime) {
48969
49180
  isRendering = false;
48970
49181
  }
48971
49182
  }
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}`));
49183
+ const keypressHandler = (_str, key) => {
49184
+ if (key.ctrl && key.name === 'c') {
49185
+ cleanup();
49186
+ process.exit(0);
49021
49187
  }
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.
49188
+ if (key.name === 'p') {
49189
+ if (getPauseState() === 'RUNNING') {
49190
+ requestPause();
49191
+ }
49192
+ else {
49193
+ requestResume();
49194
+ }
49195
+ return;
49196
+ }
49197
+ if ((key.name === 'return' || key.name === 'enter') && pendingEnterResolver) {
49198
+ const resolvePendingEnter = pendingEnterResolver;
49199
+ pendingEnterResolver = undefined;
49200
+ state.setPendingEnterLabel(undefined);
49201
+ resolvePendingEnter();
49202
+ }
49203
+ };
49204
+ process.stdin.on('keypress', keypressHandler);
49054
49205
  process.stdout.write('\n');
49055
49206
  render();
49056
49207
  const interval = setInterval(scheduleRender, UI_REFRESH_INTERVAL_MS);
49057
- // Listen for state changes and schedule a re-render (debounced).
49058
49208
  state.on('change', scheduleRender);
49059
- // --- Cleanup ---
49209
+ /**
49210
+ * Tears down the terminal UI and restores console / stdin state.
49211
+ */
49060
49212
  function cleanup() {
49061
49213
  clearInterval(interval);
49062
49214
  state.off('change', scheduleRender);
@@ -49064,12 +49216,14 @@ function renderCoderRunUi(startTime) {
49064
49216
  if (process.stdin.isTTY) {
49065
49217
  process.stdin.setRawMode(false);
49066
49218
  }
49219
+ const resolvePendingEnter = pendingEnterResolver;
49220
+ pendingEnterResolver = undefined;
49221
+ resolvePendingEnter === null || resolvePendingEnter === void 0 ? void 0 : resolvePendingEnter();
49067
49222
  isCapturing = false;
49068
49223
  console.info = originalConsoleInfo;
49069
49224
  console.warn = originalConsoleWarn;
49070
49225
  console.error = originalConsoleError;
49071
49226
  console.log = originalConsoleLog;
49072
- // Render one final frame so the user sees the last state.
49073
49227
  render();
49074
49228
  process.stdout.write('\n');
49075
49229
  }
@@ -49081,6 +49235,19 @@ function renderCoderRunUi(startTime) {
49081
49235
  stopCapturingAgentOutput() {
49082
49236
  isCapturing = false;
49083
49237
  },
49238
+ waitForEnter(actionLabel) {
49239
+ if (pendingEnterResolver) {
49240
+ throw new Error('Coder run UI is already waiting for Enter.');
49241
+ }
49242
+ state.setPendingEnterLabel(actionLabel);
49243
+ scheduleRender();
49244
+ return new Promise((resolve) => {
49245
+ pendingEnterResolver = () => {
49246
+ scheduleRender();
49247
+ resolve();
49248
+ };
49249
+ });
49250
+ },
49084
49251
  cleanup,
49085
49252
  };
49086
49253
  }
@@ -49148,11 +49315,11 @@ async function runCodexPrompts(providedOptions) {
49148
49315
  `));
49149
49316
  }
49150
49317
  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;
49318
+ const isRichUiEnabled = !options.dryRun && !options.noUi && Boolean(process.stdout.isTTY);
49319
+ const progressDisplay = options.dryRun || options.noUi || isRichUiEnabled ? undefined : new CliProgressDisplay(runStartDate, options.priority);
49320
+ const uiHandle = isRichUiEnabled ? renderCoderRunUi(runStartDate) : undefined;
49154
49321
  // When the Ink UI is active it handles keyboard input itself, so skip the raw stdin listener.
49155
- if (!isUiMode) {
49322
+ if (!isRichUiEnabled) {
49156
49323
  listenForPause();
49157
49324
  }
49158
49325
  try {
@@ -49268,17 +49435,19 @@ async function runCodexPrompts(providedOptions) {
49268
49435
  let hasWaitedForStart = false;
49269
49436
  while (just(true)) {
49270
49437
  await checkPause({
49271
- silent: isUiMode,
49438
+ silent: isRichUiEnabled,
49272
49439
  onPaused: () => {
49440
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
49273
49441
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49274
49442
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('paused');
49275
49443
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Paused');
49276
49444
  },
49277
49445
  onResumed: () => {
49446
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
49278
49447
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49279
49448
  },
49280
49449
  });
49281
- if (isUiMode) {
49450
+ if (isRichUiEnabled) {
49282
49451
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
49283
49452
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Loading prompts...');
49284
49453
  }
@@ -49286,17 +49455,17 @@ async function runCodexPrompts(providedOptions) {
49286
49455
  const stats = summarizePrompts(promptFiles, options.priority);
49287
49456
  progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.update(stats);
49288
49457
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.updateProgress(stats);
49289
- if (!isUiMode) {
49458
+ if (!isRichUiEnabled) {
49290
49459
  printStats(stats, options.priority);
49291
49460
  }
49292
49461
  const nextPrompt = findNextTodoPrompt(promptFiles, options.priority);
49293
49462
  if (!hasShownUpcomingTasks) {
49294
- if (stats.toBeWritten > 0 && !isUiMode) {
49463
+ if (stats.toBeWritten > 0 && !isRichUiEnabled) {
49295
49464
  console.info(colors.yellow('Following prompts need to be written:'));
49296
49465
  printPromptsToBeWritten(promptFiles, options.priority);
49297
49466
  console.info('');
49298
49467
  }
49299
- if (!isUiMode) {
49468
+ if (!isRichUiEnabled) {
49300
49469
  printUpcomingTasks(listUpcomingTasks(promptFiles, options.priority));
49301
49470
  }
49302
49471
  hasShownUpcomingTasks = true;
@@ -49306,7 +49475,7 @@ async function runCodexPrompts(providedOptions) {
49306
49475
  const message = 'No prompts ready for agent.';
49307
49476
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
49308
49477
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
49309
- if (!isUiMode) {
49478
+ if (!isRichUiEnabled) {
49310
49479
  console.info(colors.yellow(message));
49311
49480
  }
49312
49481
  }
@@ -49314,16 +49483,28 @@ async function runCodexPrompts(providedOptions) {
49314
49483
  const message = 'All prompts are done.';
49315
49484
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
49316
49485
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
49317
- if (!isUiMode) {
49486
+ if (!isRichUiEnabled) {
49318
49487
  console.info(colors.green(message));
49319
49488
  }
49320
49489
  }
49321
49490
  return;
49322
49491
  }
49492
+ const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
49323
49493
  if (options.waitForUser) {
49494
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
49324
49495
  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);
49496
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
49497
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
49498
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(hasWaitedForStart ? 'Waiting for confirmation to continue' : 'Waiting for confirmation to start');
49499
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines([buildPromptSummary(nextPrompt.file, nextPrompt.section)]);
49500
+ if (isRichUiEnabled) {
49501
+ await (uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.waitForEnter(hasWaitedForStart ? 'Continue' : 'Start'));
49502
+ }
49503
+ else {
49504
+ await waitForPromptStart(nextPrompt.file, nextPrompt.section, !hasWaitedForStart);
49505
+ }
49506
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines([]);
49507
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
49327
49508
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49328
49509
  hasWaitedForStart = true;
49329
49510
  }
@@ -49333,9 +49514,7 @@ async function runCodexPrompts(providedOptions) {
49333
49514
  const commitMessage = buildCommitMessage(nextPrompt.file, nextPrompt.section);
49334
49515
  const codexPrompt = appendCoderContext(buildCodexPrompt(nextPrompt.file, nextPrompt.section), resolvedCoderContext);
49335
49516
  const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
49336
- const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
49337
- const promptRoundArtifacts = createCoderRunPromptRoundArtifacts(options.preserveLogs);
49338
- if (isUiMode) {
49517
+ if (isRichUiEnabled) {
49339
49518
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
49340
49519
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('running');
49341
49520
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Running');
@@ -49345,69 +49524,74 @@ async function runCodexPrompts(providedOptions) {
49345
49524
  }
49346
49525
  const promptExecutionStartedDate = moment();
49347
49526
  let attemptCount = 1;
49348
- let promptRoundOutcome = 'failure';
49349
49527
  const roundChangedFilesSnapshot = options.normalizeLineEndings
49350
49528
  ? await captureChangedFilesSnapshot(process.cwd())
49351
49529
  : 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');
49530
+ await withPromptRuntimeLog(scriptPath, async (logPath) => {
49531
+ try {
49532
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
49533
+ const result = await runPromptWithTestFeedback({
49534
+ runner,
49535
+ prompt: codexPrompt,
49536
+ scriptPath,
49537
+ projectPath: process.cwd(),
49538
+ promptLabel,
49539
+ testCommand: options.testCommand,
49540
+ preserveArtifactsOnSuccess: options.preserveLogs,
49541
+ logPath,
49542
+ onAttemptStarted: (nextAttemptCount) => {
49543
+ attemptCount = nextAttemptCount;
49544
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(nextAttemptCount);
49545
+ if (nextAttemptCount > 1) {
49546
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Retrying (attempt ${nextAttemptCount})`);
49547
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('verifying');
49548
+ }
49549
+ },
49550
+ });
49551
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49552
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Committing changes');
49553
+ markPromptDone(nextPrompt.file, nextPrompt.section, result.usage, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, result.attemptCount);
49554
+ await writePromptFile(nextPrompt.file);
49555
+ await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49556
+ if (options.waitForUser) {
49557
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
49558
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49559
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
49560
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Review the commit preview and confirm to continue');
49561
+ if (isRichUiEnabled) {
49562
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines(formatCommitMessageForDisplay(commitMessage)
49563
+ .split(/\r?\n/)
49564
+ .map((line) => line.trim()));
49565
+ await (uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.waitForEnter('Commit'));
49566
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines([]);
49567
+ }
49568
+ else {
49382
49569
  printCommitMessage(commitMessage);
49383
49570
  await waitForEnter(colors.bgWhite('Press Enter to commit and continue...'));
49384
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49385
49571
  }
49386
- await commitChanges(commitMessage, { autoPush: options.autoPush });
49387
- await runPostPromptAutoMigrationIfEnabled(options);
49388
- promptRoundOutcome = 'success';
49572
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
49573
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49389
49574
  }
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;
49405
- }
49406
- }, promptRoundArtifacts);
49407
- }
49408
- finally {
49409
- await promptRoundArtifacts.cleanup(promptRoundOutcome);
49410
- }
49575
+ await commitChanges(commitMessage, { autoPush: options.autoPush });
49576
+ await runPostPromptAutoMigrationIfEnabled(options);
49577
+ }
49578
+ catch (error) {
49579
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49580
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
49581
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
49582
+ markPromptFailed(nextPrompt.file, nextPrompt.section, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, attemptCount);
49583
+ await writePromptFile(nextPrompt.file);
49584
+ await writePromptErrorLog({
49585
+ file: nextPrompt.file,
49586
+ section: nextPrompt.section,
49587
+ runnerName: runnerMetadata.runnerName,
49588
+ modelName: runnerMetadata.modelName,
49589
+ error,
49590
+ });
49591
+ await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49592
+ throw error;
49593
+ }
49594
+ }, { preserveArtifactsOnSuccess: options.preserveLogs });
49411
49595
  }
49412
49596
  }
49413
49597
  finally {