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