@promptbook/cli 0.112.0-43 → 0.112.0-45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/esm/index.es.js +1013 -459
  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/buildScriptLogPath.d.ts +4 -0
  11. package/esm/scripts/run-codex-prompts/common/runGoScript/runBashScriptWithOutput.d.ts +5 -0
  12. package/esm/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +28 -0
  13. package/esm/scripts/run-codex-prompts/common/runGoScript/shouldDeleteTemporaryArtifact.d.ts +7 -0
  14. package/esm/scripts/run-codex-prompts/common/runGoScript/withPromptRuntimeLog.d.ts +6 -0
  15. package/esm/scripts/run-codex-prompts/common/runGoScript/withTempScript.d.ts +1 -1
  16. package/esm/scripts/run-codex-prompts/testing/runPromptTestCommand.d.ts +2 -0
  17. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +15 -20
  18. package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +28 -0
  19. package/esm/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +2 -0
  20. package/esm/src/avatars/Avatar.d.ts +7 -0
  21. package/esm/src/avatars/avatarRenderingUtils.d.ts +117 -0
  22. package/esm/src/avatars/index.d.ts +6 -0
  23. package/esm/src/avatars/renderAvatarVisual.d.ts +9 -0
  24. package/esm/src/avatars/types/AvatarDefinition.d.ts +20 -0
  25. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +96 -0
  26. package/esm/src/avatars/visuals/avatarVisualRegistry.d.ts +16 -0
  27. package/esm/src/avatars/visuals/minecraftAvatarVisual.d.ts +7 -0
  28. package/esm/src/avatars/visuals/octopusAvatarVisual.d.ts +7 -0
  29. package/esm/src/avatars/visuals/pixelArtAvatarVisual.d.ts +7 -0
  30. package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -1
  31. package/esm/src/book-2.0/agent-source/TeammateProfileResolver.d.ts +2 -1
  32. package/esm/src/commitments/PERSONA/PERSONA.d.ts +7 -0
  33. package/esm/src/commitments/STYLE/STYLE.d.ts +9 -2
  34. package/esm/src/version.d.ts +1 -1
  35. package/package.json +1 -1
  36. package/umd/index.umd.js +1012 -458
  37. package/umd/index.umd.js.map +1 -1
  38. package/umd/scripts/run-codex-prompts/common/CoderRunTimer.d.ts +31 -0
  39. package/umd/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +23 -0
  40. package/umd/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +13 -4
  41. package/umd/scripts/run-codex-prompts/common/progressFormatting.d.ts +16 -0
  42. package/umd/scripts/run-codex-prompts/common/runGoScript/$runGoScript.d.ts +1 -1
  43. package/umd/scripts/run-codex-prompts/common/runGoScript/$runGoScriptUntilMarkerIdle.d.ts +1 -1
  44. package/umd/scripts/run-codex-prompts/common/runGoScript/$runGoScriptWithOutput.d.ts +1 -1
  45. package/umd/scripts/run-codex-prompts/common/runGoScript/buildScriptLogPath.d.ts +4 -0
  46. package/umd/scripts/run-codex-prompts/common/runGoScript/runBashScriptWithOutput.d.ts +5 -0
  47. package/umd/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +28 -0
  48. package/umd/scripts/run-codex-prompts/common/runGoScript/shouldDeleteTemporaryArtifact.d.ts +7 -0
  49. package/umd/scripts/run-codex-prompts/common/runGoScript/withPromptRuntimeLog.d.ts +6 -0
  50. package/umd/scripts/run-codex-prompts/common/runGoScript/withTempScript.d.ts +1 -1
  51. package/umd/scripts/run-codex-prompts/testing/runPromptTestCommand.d.ts +2 -0
  52. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +15 -20
  53. package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +28 -0
  54. package/umd/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +2 -0
  55. package/umd/src/avatars/Avatar.d.ts +7 -0
  56. package/umd/src/avatars/avatarRenderingUtils.d.ts +117 -0
  57. package/umd/src/avatars/index.d.ts +6 -0
  58. package/umd/src/avatars/renderAvatarVisual.d.ts +9 -0
  59. package/umd/src/avatars/types/AvatarDefinition.d.ts +20 -0
  60. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +96 -0
  61. package/umd/src/avatars/visuals/avatarVisualRegistry.d.ts +16 -0
  62. package/umd/src/avatars/visuals/minecraftAvatarVisual.d.ts +7 -0
  63. package/umd/src/avatars/visuals/octopusAvatarVisual.d.ts +7 -0
  64. package/umd/src/avatars/visuals/pixelArtAvatarVisual.d.ts +7 -0
  65. package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -1
  66. package/umd/src/book-2.0/agent-source/TeammateProfileResolver.d.ts +2 -1
  67. package/umd/src/commitments/PERSONA/PERSONA.d.ts +7 -0
  68. package/umd/src/commitments/STYLE/STYLE.d.ts +9 -2
  69. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -4,7 +4,7 @@ import _spaceTrim, { spaceTrim as spaceTrim$1 } from 'spacetrim';
4
4
  import * as fs from 'fs';
5
5
  import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs';
6
6
  import { join, basename, dirname, isAbsolute, relative, extname, resolve } from 'path';
7
- import { readFile, writeFile, stat, mkdir, access, constants, readdir, watch, unlink, rm, rename, rmdir } from 'fs/promises';
7
+ import { readFile, writeFile, stat, mkdir, access, constants, readdir, watch, unlink, rm, rename, rmdir, appendFile } from 'fs/promises';
8
8
  import { forTime, forEver } from 'waitasecond';
9
9
  import prompts from 'prompts';
10
10
  import * as dotenv from 'dotenv';
@@ -58,7 +58,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
58
58
  * @generated
59
59
  * @see https://github.com/webgptorg/promptbook
60
60
  */
61
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-43';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-45';
62
62
  /**
63
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
64
64
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -3005,6 +3005,8 @@ function $initializeCoderRunCommand(program) {
3005
3005
  Features:
3006
3006
  - Automatically stages and commits changes with agent identity
3007
3007
  - Optional post-commit git push with explicit --auto-push opt-in
3008
+ - Optional --preserve-logs keeps temp prompt/log artifacts after successful rounds
3009
+ - Optional --no-ui keeps plain streaming console output for logging and debugging
3008
3010
  - Supports GPG signing of commits
3009
3011
  - Optional post-prompt verification with test-feedback retries
3010
3012
  - Progress tracking and interactive controls
@@ -3020,6 +3022,8 @@ function $initializeCoderRunCommand(program) {
3020
3022
  `));
3021
3023
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
3022
3024
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
3025
+ command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
3026
+ command.option('--no-ui', 'Disable the rich terminal UI and keep plain streaming console output for logging and debugging');
3023
3027
  command.addOption(new Option('--thinking-level <thinking-level>', `Set reasoning effort for supported runners (${THINKING_LEVEL_VALUES.join(', ')})`).choices([...THINKING_LEVEL_VALUES]));
3024
3028
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
3025
3029
  command.option('--no-wait', 'Skip user prompts between processing');
@@ -3030,8 +3034,9 @@ function $initializeCoderRunCommand(program) {
3030
3034
  command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
3031
3035
  command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
3032
3036
  command.action(handleActionErrors(async (cliOptions) => {
3033
- const { dryRun, agent, model, context, test, thinkingLevel, priority, wait, ignoreGitChanges, allowCredits, normalizeLineEndings, autoMigrate, allowDestructiveAutoMigrate, autoPush, } = cliOptions;
3037
+ const { dryRun, agent, model, context, test, preserveLogs, ui, thinkingLevel, priority, wait, ignoreGitChanges, allowCredits, normalizeLineEndings, autoMigrate, allowDestructiveAutoMigrate, autoPush, } = cliOptions;
3034
3038
  const testCommand = normalizeCommandOptionValue(test);
3039
+ const noUi = !ui;
3035
3040
  // Validate agent
3036
3041
  let agentName = undefined;
3037
3042
  if (agent) {
@@ -3061,6 +3066,8 @@ function $initializeCoderRunCommand(program) {
3061
3066
  model,
3062
3067
  context,
3063
3068
  testCommand,
3069
+ preserveLogs,
3070
+ noUi,
3064
3071
  thinkingLevel,
3065
3072
  priority,
3066
3073
  normalizeLineEndings,
@@ -18384,7 +18391,7 @@ class DeleteCommitmentDefinition extends BaseCommitmentDefinition {
18384
18391
  DELETE Casual conversational style
18385
18392
  REMOVE All emoji usage
18386
18393
  GOAL Provide professional business communications
18387
- STYLE Use formal language and proper business etiquette
18394
+ WRITING RULES Use formal language and proper business etiquette
18388
18395
  \`\`\`
18389
18396
 
18390
18397
  \`\`\`book
@@ -18395,7 +18402,7 @@ class DeleteCommitmentDefinition extends BaseCommitmentDefinition {
18395
18402
  DISCARD Technical jargon explanations
18396
18403
  CANCEL Advanced troubleshooting procedures
18397
18404
  GOAL Help users with simple, easy-to-follow solutions
18398
- STYLE Use plain language that anyone can understand
18405
+ WRITING RULES Use plain language that anyone can understand
18399
18406
  \`\`\`
18400
18407
 
18401
18408
  \`\`\`book
@@ -18412,11 +18419,11 @@ class DeleteCommitmentDefinition extends BaseCommitmentDefinition {
18412
18419
  Concise Information Provider
18413
18420
 
18414
18421
  PERSONA You are a helpful assistant who provides detailed explanations
18415
- STYLE Include examples, analogies, and comprehensive context
18422
+ WRITING RULES Include examples, analogies, and comprehensive context
18416
18423
  CANCEL Detailed explanation style
18417
18424
  DISCARD Examples and analogies
18418
18425
  GOAL Provide brief, direct answers without unnecessary elaboration
18419
- STYLE Be concise and to the point
18426
+ WRITING RULES Be concise and to the point
18420
18427
  \`\`\`
18421
18428
  `);
18422
18429
  }
@@ -18600,7 +18607,7 @@ class FormatCommitmentDefinition extends BaseCommitmentDefinition {
18600
18607
  PERSONA You are a data analysis expert
18601
18608
  FORMAT Present results in structured tables
18602
18609
  FORMAT Include confidence scores for all predictions
18603
- STYLE Be concise and precise in explanations
18610
+ WRITING RULES Be concise and precise in explanations
18604
18611
  \`\`\`
18605
18612
  `);
18606
18613
  }
@@ -18949,7 +18956,7 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
18949
18956
  * Short one-line description of GOAL.
18950
18957
  */
18951
18958
  get description() {
18952
- return 'Define main **goals** the AI assistant should achieve, with later goals having higher priority.';
18959
+ return 'Define the effective agent **goal**; when multiple goals exist, only the last one stays effective.';
18953
18960
  }
18954
18961
  /**
18955
18962
  * Icon for this commitment.
@@ -18964,12 +18971,14 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
18964
18971
  return spaceTrim$1(`
18965
18972
  # ${this.type}
18966
18973
 
18967
- Defines the main goal which should be achieved by the AI assistant. There can be multiple goals, and later goals are more important than earlier goals.
18974
+ Defines the main goal which should be achieved by the AI assistant.
18975
+ There can be multiple goals in source, but after inheritance/source rewriting only the last \`GOAL\` /\`GOALS\` remains effective.
18968
18976
 
18969
18977
  ## Key aspects
18970
18978
 
18971
18979
  - Both terms work identically and can be used interchangeably.
18972
- - Later goals have higher priority and can override earlier goals.
18980
+ - Later goals overwrite earlier goals.
18981
+ - The public agent profile text is derived from the last goal.
18973
18982
  - Goals provide clear direction and purpose for the agent's responses.
18974
18983
  - Goals influence decision-making and response prioritization.
18975
18984
 
@@ -18982,9 +18991,7 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
18982
18991
  \`\`\`book
18983
18992
  Customer Support Agent
18984
18993
 
18985
- PERSONA You are a helpful customer support representative
18986
18994
  GOAL Resolve customer issues quickly and efficiently
18987
- GOAL Maintain high customer satisfaction scores
18988
18995
  GOAL Always follow company policies and procedures
18989
18996
  RULE Be polite and professional at all times
18990
18997
  \`\`\`
@@ -18992,19 +18999,15 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
18992
18999
  \`\`\`book
18993
19000
  Educational Assistant
18994
19001
 
18995
- PERSONA You are an educational assistant specializing in mathematics
18996
19002
  GOAL Help students understand mathematical concepts clearly
18997
- GOAL Encourage critical thinking and problem-solving skills
18998
19003
  GOAL Ensure all explanations are age-appropriate and accessible
18999
- STYLE Use simple language and provide step-by-step explanations
19004
+ WRITING RULES Use simple language and provide step-by-step explanations
19000
19005
  \`\`\`
19001
19006
 
19002
19007
  \`\`\`book
19003
19008
  Safety-First Assistant
19004
19009
 
19005
- PERSONA You are a general-purpose AI assistant
19006
19010
  GOAL Be helpful and informative in all interactions
19007
- GOAL Provide accurate and reliable information
19008
19011
  GOAL Always prioritize user safety and ethical guidelines
19009
19012
  RULE Never provide harmful or dangerous advice
19010
19013
  \`\`\`
@@ -19412,7 +19415,7 @@ class KnowledgeCommitmentDefinition extends BaseCommitmentDefinition {
19412
19415
  KNOWLEDGE Academic research requires careful citation and verification
19413
19416
  KNOWLEDGE https://example.com/research-guidelines.pdf
19414
19417
  ACTION Can help with literature reviews and data analysis
19415
- STYLE Present information in clear, academic format
19418
+ WRITING RULES Present information in clear, academic format
19416
19419
  \`\`\`
19417
19420
  `);
19418
19421
  }
@@ -21163,7 +21166,7 @@ class MetaImageCommitmentDefinition extends BaseCommitmentDefinition {
21163
21166
 
21164
21167
  META IMAGE https://example.com/professional-avatar.jpg
21165
21168
  PERSONA You are a professional business assistant
21166
- STYLE Maintain a formal and courteous tone
21169
+ WRITING RULES Maintain a formal and courteous tone
21167
21170
  \`\`\`
21168
21171
 
21169
21172
  \`\`\`book
@@ -21171,7 +21174,7 @@ class MetaImageCommitmentDefinition extends BaseCommitmentDefinition {
21171
21174
 
21172
21175
  META IMAGE /assets/creative-bot-avatar.png
21173
21176
  PERSONA You are a creative and inspiring assistant
21174
- STYLE Be enthusiastic and encouraging
21177
+ WRITING RULES Be enthusiastic and encouraging
21175
21178
  ACTION Can help with brainstorming and ideation
21176
21179
  \`\`\`
21177
21180
  `);
@@ -21330,7 +21333,7 @@ class MetaLinkCommitmentDefinition extends BaseCommitmentDefinition {
21330
21333
  META LINK https://twitter.com/devhandle
21331
21334
  PERSONA You are an experienced open source developer
21332
21335
  ACTION Can help with code reviews and architecture decisions
21333
- STYLE Be direct and technical in explanations
21336
+ WRITING RULES Be direct and technical in explanations
21334
21337
  \`\`\`
21335
21338
  `);
21336
21339
  }
@@ -21536,7 +21539,7 @@ class ModelCommitmentDefinition extends BaseCommitmentDefinition {
21536
21539
  MODEL TEMPERATURE 0.8
21537
21540
  MODEL TOP_P 0.9
21538
21541
  MODEL MAX_TOKENS 2048
21539
- STYLE Be imaginative and expressive
21542
+ WRITING RULES Be imaginative and expressive
21540
21543
  ACTION Can help with storytelling and character development
21541
21544
  \`\`\`
21542
21545
 
@@ -21748,7 +21751,7 @@ class NoteCommitmentDefinition extends BaseCommitmentDefinition {
21748
21751
  NOTE Uses RAG for accessing latest research papers
21749
21752
  PERSONA You are a knowledgeable research assistant
21750
21753
  ACTION Can help with literature reviews and citations
21751
- STYLE Present information in academic format
21754
+ WRITING RULES Present information in academic format
21752
21755
  \`\`\`
21753
21756
  `);
21754
21757
  }
@@ -21863,7 +21866,16 @@ class PersonaCommitmentDefinition extends BaseCommitmentDefinition {
21863
21866
  * Short one-line description of PERSONA.
21864
21867
  */
21865
21868
  get description() {
21866
- return 'Define who the agent is: background, expertise, and personality.';
21869
+ return 'Deprecated legacy profile commitment. Prefer `GOAL` for agent profile text and inheritance-safe rewrites.';
21870
+ }
21871
+ /**
21872
+ * Optional UI/docs-only deprecation metadata.
21873
+ */
21874
+ get deprecation() {
21875
+ return {
21876
+ message: 'Use `GOAL` for agent profile text and inheritance-safe rewrites.',
21877
+ replacedBy: ['GOAL'],
21878
+ };
21867
21879
  }
21868
21880
  /**
21869
21881
  * Icon for this commitment.
@@ -21878,16 +21890,24 @@ class PersonaCommitmentDefinition extends BaseCommitmentDefinition {
21878
21890
  return spaceTrim$1(`
21879
21891
  # ${this.type}
21880
21892
 
21881
- Defines who the agent is, their background, expertise, and personality traits.
21893
+ Deprecated legacy commitment that defines who the agent is, their background, expertise, and personality traits.
21882
21894
 
21883
- ## Key aspects
21895
+ ## Migration
21884
21896
 
21885
- - Multiple \`PERSONA\` and \`PERSONAE\` commitments are merged together.
21886
- - Both terms work identically and can be used interchangeably.
21887
- - If they are in conflict, the last one takes precedence.
21888
- - You can write persona content in multiple lines.
21897
+ - Existing \`${this.type}\` books still parse and compile.
21898
+ - New books should prefer \`GOAL\`.
21899
+ - Agent profile rendering now prefers the last \`GOAL\` and only falls back to \`${this.type}\` when no goal exists.
21900
+ - Runtime compilation keeps the legacy multi-\`PERSONA\` merge behavior for backward compatibility.
21889
21901
 
21890
- ## Examples
21902
+ ## Preferred replacement
21903
+
21904
+ \`\`\`book
21905
+ Programming Assistant
21906
+
21907
+ GOAL Help the user solve programming problems with practical TypeScript and React guidance.
21908
+ \`\`\`
21909
+
21910
+ ## Legacy compatibility example
21891
21911
 
21892
21912
  \`\`\`book
21893
21913
  Programming Assistant
@@ -22025,7 +22045,7 @@ class RuleCommitmentDefinition extends BaseCommitmentDefinition {
22025
22045
  RULE Always ask for clarification if the user's request is ambiguous
22026
22046
  RULE Be polite and professional in all interactions
22027
22047
  RULES Never provide medical or legal advice
22028
- STYLE Maintain a friendly and helpful tone
22048
+ WRITING RULES Maintain a friendly and helpful tone
22029
22049
  \`\`\`
22030
22050
 
22031
22051
  \`\`\`book
@@ -22294,8 +22314,8 @@ class ScenarioCommitmentDefinition extends BaseCommitmentDefinition {
22294
22314
  /**
22295
22315
  * STYLE commitment definition
22296
22316
  *
22297
- * The STYLE commitment defines how the agent should format and present its responses.
22298
- * This includes tone, writing style, formatting preferences, and communication patterns.
22317
+ * Deprecated legacy writing-style commitment kept for backward compatibility.
22318
+ * New books should prefer `WRITING RULES` for writing-only constraints.
22299
22319
  *
22300
22320
  * Example usage in agent source:
22301
22321
  *
@@ -22314,7 +22334,16 @@ class StyleCommitmentDefinition extends BaseCommitmentDefinition {
22314
22334
  * Short one-line description of STYLE.
22315
22335
  */
22316
22336
  get description() {
22317
- return 'Control the tone and writing style of responses.';
22337
+ return 'Deprecated legacy writing-style commitment. Prefer `WRITING RULES` for new books.';
22338
+ }
22339
+ /**
22340
+ * Optional UI/docs-only deprecation metadata.
22341
+ */
22342
+ get deprecation() {
22343
+ return {
22344
+ message: 'Use `WRITING RULES` for writing-only constraints such as tone, length, formatting, or emoji usage.',
22345
+ replacedBy: ['WRITING RULES'],
22346
+ };
22318
22347
  }
22319
22348
  /**
22320
22349
  * Icon for this commitment.
@@ -22329,15 +22358,34 @@ class StyleCommitmentDefinition extends BaseCommitmentDefinition {
22329
22358
  return spaceTrim$1(`
22330
22359
  # ${this.type}
22331
22360
 
22332
- Defines how the agent should format and present its responses (tone, writing style, formatting).
22361
+ Deprecated legacy commitment for writing and presentation instructions.
22362
+
22363
+ ## Migration
22364
+
22365
+ - Existing \`${this.type}\` books still parse and compile.
22366
+ - New books should prefer \`WRITING RULES\`.
22367
+ - Use \`WRITING SAMPLE\` when you want to anchor voice by example instead of stating constraints directly.
22368
+ - The plural alias \`STYLES\` is the same legacy commitment family.
22333
22369
 
22334
22370
  ## Key aspects
22335
22371
 
22336
- - Both terms work identically and can be used interchangeably.
22372
+ - \`${this.type}\` remains functional for backward compatibility only.
22337
22373
  - Later style instructions can override earlier ones.
22338
22374
  - Style affects both tone and presentation format.
22339
22375
 
22340
- ## Examples
22376
+ ## Preferred replacement
22377
+
22378
+ \`\`\`book
22379
+ Technical Writer
22380
+
22381
+ GOAL Help the user understand technical topics with practical, accurate guidance.
22382
+ WRITING RULES Write in a professional but friendly tone.
22383
+ WRITING RULES Use bullet points for lists.
22384
+ WRITING RULES Always provide code examples when explaining programming concepts.
22385
+ FORMAT Use markdown formatting with clear headings
22386
+ \`\`\`
22387
+
22388
+ ## Legacy compatibility examples
22341
22389
 
22342
22390
  \`\`\`book
22343
22391
  Technical Writer
@@ -22652,7 +22700,7 @@ class TeamCommitmentDefinition extends BaseCommitmentDefinition {
22652
22700
  \`\`\`book
22653
22701
  Legal Assistant
22654
22702
 
22655
- PERSONA An expert software developer
22703
+ GOAL Get expert software-development advice from the teammate when legal discussion needs technical context.
22656
22704
  TEAM You can talk with http://localhost:4440/agents/GMw67JN8TXxN7y to discuss the legal aspects.
22657
22705
  \`\`\`
22658
22706
  `);
@@ -23078,7 +23126,7 @@ class TemplateCommitmentDefinition extends BaseCommitmentDefinition {
23078
23126
 
23079
23127
  PERSONA You are a helpful customer support representative
23080
23128
  TEMPLATE Always structure your response with: 1) Acknowledgment, 2) Solution, 3) Follow-up question
23081
- STYLE Be professional and empathetic
23129
+ WRITING RULES Be professional and empathetic
23082
23130
  \`\`\`
23083
23131
 
23084
23132
  \`\`\`book
@@ -23522,7 +23570,7 @@ class UseBrowserCommitmentDefinition extends BaseCommitmentDefinition {
23522
23570
 
23523
23571
  PERSONA You are a news analyst who stays up-to-date with current events
23524
23572
  USE BROWSER
23525
- STYLE Present news in a balanced and objective manner
23573
+ WRITING RULES Present news in a balanced and objective manner
23526
23574
  ACTION Can search for and summarize news articles
23527
23575
  \`\`\`
23528
23576
 
@@ -41398,6 +41446,15 @@ const COMMITMENTS_WITH_AGENT_REFERENCES = new Set(['FROM', 'IMPORT', 'IMPORTS',
41398
41446
  * @private internal constant of `createAgentModelRequirementsWithCommitments`
41399
41447
  */
41400
41448
  const DELETE_COMMITMENT_TYPES = new Set(['DELETE', 'CANCEL', 'DISCARD', 'REMOVE']);
41449
+ /**
41450
+ * Commitments whose earlier occurrences are overwritten by the last occurrence in source order.
41451
+ *
41452
+ * @private internal constant of `createAgentModelRequirementsWithCommitments`
41453
+ */
41454
+ const OVERWRITTEN_COMMITMENT_GROUP_BY_TYPE = new Map([
41455
+ ['GOAL', 'GOAL'],
41456
+ ['GOALS', 'GOAL'],
41457
+ ]);
41401
41458
  /**
41402
41459
  * Regex pattern matching markdown horizontal lines that should not be copied into the final system message.
41403
41460
  *
@@ -41448,7 +41505,7 @@ function getSafeReferenceCommitmentFallback(commitmentType, originalContent) {
41448
41505
  */
41449
41506
  async function createAgentModelRequirementsWithCommitments(agentSource, modelName, options) {
41450
41507
  const parseResult = parseAgentSourceWithCommitments(agentSource);
41451
- const filteredCommitments = filterDeletedCommitments(parseResult.commitments);
41508
+ const filteredCommitments = filterOverwrittenCommitments(filterDeletedCommitments(parseResult.commitments));
41452
41509
  let requirements = createInitialAgentModelRequirements(parseResult.agentName, modelName);
41453
41510
  requirements = await applyCommitmentsToRequirements(requirements, filteredCommitments, options);
41454
41511
  requirements = aggregateUseCommitmentSystemMessages(requirements, filteredCommitments);
@@ -41459,6 +41516,35 @@ async function createAgentModelRequirementsWithCommitments(agentSource, modelNam
41459
41516
  requirements = await applyPendingInlineKnowledgeSources(requirements, options === null || options === void 0 ? void 0 : options.inlineKnowledgeSourceUploader);
41460
41517
  return finalizeRequirements(requirements);
41461
41518
  }
41519
+ /**
41520
+ * Removes earlier commitments that are overwritten by later commitments of the same semantic group.
41521
+ *
41522
+ * This currently keeps only the last `GOAL` / `GOALS` commitment so inheritance rewrites
41523
+ * and multi-goal sources expose one effective goal to the runtime.
41524
+ *
41525
+ * @param commitments - Parsed commitments after DELETE-like filtering.
41526
+ * @returns Commitments with overwritten entries removed while preserving source order.
41527
+ *
41528
+ * @private internal utility of `createAgentModelRequirementsWithCommitments`
41529
+ */
41530
+ function filterOverwrittenCommitments(commitments) {
41531
+ const seenOverwriteGroups = new Set();
41532
+ const keptCommitments = [];
41533
+ for (let index = commitments.length - 1; index >= 0; index--) {
41534
+ const commitment = commitments[index];
41535
+ const overwriteGroup = OVERWRITTEN_COMMITMENT_GROUP_BY_TYPE.get(commitment.type);
41536
+ if (!overwriteGroup) {
41537
+ keptCommitments.push(commitment);
41538
+ continue;
41539
+ }
41540
+ if (seenOverwriteGroups.has(overwriteGroup)) {
41541
+ continue;
41542
+ }
41543
+ seenOverwriteGroups.add(overwriteGroup);
41544
+ keptCommitments.push(commitment);
41545
+ }
41546
+ return keptCommitments.reverse();
41547
+ }
41462
41548
  /**
41463
41549
  * Creates the initial requirements object with the parsed agent name stored in metadata and an optional model override.
41464
41550
  *
@@ -42101,7 +42187,7 @@ function createDefaultAgentName(agentSource) {
42101
42187
  function parseAgentSource(agentSource) {
42102
42188
  const parseResult = parseAgentSourceWithCommitments(agentSource);
42103
42189
  const resolvedAgentName = parseResult.agentName || createDefaultAgentName(agentSource);
42104
- const personaDescription = extractPersonaDescription(parseResult.commitments);
42190
+ const personaDescription = extractAgentProfileText(parseResult.commitments);
42105
42191
  const initialMessage = extractInitialMessage(parseResult.commitments);
42106
42192
  const parsedProfile = extractParsedAgentProfile(parseResult.commitments);
42107
42193
  ensureMetaFullname(parsedProfile.meta, resolvedAgentName);
@@ -42205,25 +42291,33 @@ const META_COMMITMENT_APPLIERS = {
42205
42291
  */
42206
42292
  const LOCAL_AGENT_REFERENCE_PREFIXES = ['./', '../', '/'];
42207
42293
  /**
42208
- * Builds the combined persona description from PERSONA commitments.
42294
+ * Resolves the public agent profile text from the last GOAL/GOALS commitment,
42295
+ * falling back to the deprecated PERSONA/PERSONAE commitments when no goal exists.
42209
42296
  *
42210
42297
  * @private internal utility of `parseAgentSource`
42211
42298
  */
42212
- function extractPersonaDescription(commitments) {
42213
- let personaDescription = null;
42299
+ function extractAgentProfileText(commitments) {
42300
+ let goalDescription = '';
42301
+ let hasGoalDescription = false;
42302
+ let personaDescription = '';
42303
+ let hasPersonaDescription = false;
42214
42304
  for (const commitment of commitments) {
42215
- if (commitment.type !== 'PERSONA') {
42216
- continue;
42305
+ if (commitment.type === 'GOAL' || commitment.type === 'GOALS') {
42306
+ goalDescription = commitment.content;
42307
+ hasGoalDescription = true;
42217
42308
  }
42218
- if (personaDescription === null) {
42219
- personaDescription = '';
42309
+ if (commitment.type === 'PERSONA' || commitment.type === 'PERSONAE') {
42310
+ personaDescription = commitment.content;
42311
+ hasPersonaDescription = true;
42220
42312
  }
42221
- else {
42222
- personaDescription += `\n\n${personaDescription}`;
42223
- }
42224
- personaDescription += commitment.content;
42225
42313
  }
42226
- return personaDescription;
42314
+ if (hasGoalDescription) {
42315
+ return goalDescription;
42316
+ }
42317
+ if (hasPersonaDescription) {
42318
+ return personaDescription;
42319
+ }
42320
+ return null;
42227
42321
  }
42228
42322
  /**
42229
42323
  * Resolves the last INITIAL MESSAGE commitment, which is the public initial-message value.
@@ -42710,7 +42804,7 @@ async function createAgentModelRequirements(agentSource, modelName, availableMod
42710
42804
  * Selects the best model using the preparePersona function
42711
42805
  * This directly uses preparePersona to ensure DRY principle
42712
42806
  *
42713
- * @param agentSource The agent source to derive persona description from
42807
+ * @param agentSource The agent source to derive effective profile text from
42714
42808
  * @param llmTools LLM tools for preparing persona
42715
42809
  * @returns The name of the best selected model
42716
42810
  *
@@ -42718,9 +42812,9 @@ async function createAgentModelRequirements(agentSource, modelName, availableMod
42718
42812
  */
42719
42813
  async function selectBestModelUsingPersona(agentSource, llmTools) {
42720
42814
  var _a;
42721
- // Parse agent source to get persona description
42815
+ // Parse agent source to get the effective profile description
42722
42816
  const { agentName, personaDescription } = parseAgentSource(agentSource);
42723
- // Use agent name as fallback if no persona description is available
42817
+ // Use agent name as fallback if no profile description is available
42724
42818
  const description = personaDescription || agentName || 'AI Agent';
42725
42819
  try {
42726
42820
  // Use preparePersona directly
@@ -44182,7 +44276,7 @@ var findRefactorCandidates$1 = /*#__PURE__*/Object.freeze({
44182
44276
  /**
44183
44277
  * CLI usage text for this script.
44184
44278
  */
44185
- 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] [--auto-migrate] [--allow-destructive-auto-migrate] [--no-wait] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push]';
44279
+ const USAGE = 'Usage: run-codex-prompts [--dry-run] [--agent <agent-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--preserve-logs] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--no-wait] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push]';
44186
44280
  /**
44187
44281
  * Top-level flags supported by this command.
44188
44282
  */
@@ -44192,6 +44286,8 @@ const KNOWN_OPTION_FLAGS = new Set([
44192
44286
  '--model',
44193
44287
  '--context',
44194
44288
  '--test',
44289
+ '--preserve-logs',
44290
+ '--no-ui',
44195
44291
  '--thinking-level',
44196
44292
  '--priority',
44197
44293
  '--allow-credits',
@@ -44224,6 +44320,8 @@ function parseRunOptions(args) {
44224
44320
  const context = readOptionValue(args, '--context');
44225
44321
  const hasTestCommandFlag = args.includes('--test');
44226
44322
  const testCommand = readVariadicOptionValue(args, '--test');
44323
+ const preserveLogs = args.includes('--preserve-logs');
44324
+ const noUi = args.includes('--no-ui');
44227
44325
  const hasThinkingLevelFlag = args.includes('--thinking-level');
44228
44326
  const thinkingLevelValue = readOptionValue(args, '--thinking-level');
44229
44327
  const hasPriorityFlag = args.includes('--priority');
@@ -44259,6 +44357,8 @@ function parseRunOptions(args) {
44259
44357
  autoMigrate,
44260
44358
  allowDestructiveAutoMigrate,
44261
44359
  autoPush,
44360
+ preserveLogs,
44361
+ noUi,
44262
44362
  agentName,
44263
44363
  model,
44264
44364
  context,
@@ -44339,18 +44439,10 @@ function appendCoderContext(prompt, context) {
44339
44439
  return `${normalizedPrompt}\n\n${normalizedContext}`;
44340
44440
  }
44341
44441
 
44342
- /**
44343
- * Refresh interval for the progress header in milliseconds.
44344
- */
44345
- const PROGRESS_REFRESH_INTERVAL_MS = 1000;
44346
- /**
44347
- * Number of terminal lines reserved for the sticky progress header.
44348
- */
44349
- const PROGRESS_HEADER_RESERVED_LINES = 1;
44350
44442
  /**
44351
44443
  * Calendar formats used when displaying the estimated completion time.
44352
44444
  */
44353
- const ESTIMATED_DONE_CALENDAR_FORMATS$1 = {
44445
+ const ESTIMATED_DONE_CALENDAR_FORMATS = {
44354
44446
  sameDay: '[Today] h:mm',
44355
44447
  nextDay: '[Tomorrow] h:mm',
44356
44448
  nextWeek: 'dddd h:mm',
@@ -44358,6 +44450,121 @@ const ESTIMATED_DONE_CALENDAR_FORMATS$1 = {
44358
44450
  lastWeek: 'dddd h:mm',
44359
44451
  sameElse: 'MMM D h:mm',
44360
44452
  };
44453
+ /**
44454
+ * Formats a duration into a compact string such as "3h 12m" or "45s".
44455
+ */
44456
+ function formatDurationBrief(duration) {
44457
+ const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
44458
+ const hours = Math.floor(totalSeconds / 3600);
44459
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
44460
+ const seconds = totalSeconds % 60;
44461
+ const parts = [];
44462
+ if (hours > 0) {
44463
+ parts.push(`${hours}h`);
44464
+ }
44465
+ if (minutes > 0) {
44466
+ parts.push(`${minutes}m`);
44467
+ }
44468
+ if (!parts.length && seconds > 0) {
44469
+ parts.push(`${seconds}s`);
44470
+ }
44471
+ if (!parts.length) {
44472
+ parts.push('0s');
44473
+ }
44474
+ return parts.join(' ');
44475
+ }
44476
+
44477
+ /**
44478
+ * Builds a session-scoped progress snapshot from prompt stats and elapsed active time.
44479
+ */
44480
+ function buildCoderRunProgressSnapshot(stats, elapsedDuration, initialDone) {
44481
+ const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
44482
+ const sessionDone = Math.max(0, stats.done - initialDone);
44483
+ const sessionRemaining = stats.forAgent;
44484
+ const sessionTotal = sessionDone + sessionRemaining;
44485
+ const currentPromptIndex = sessionTotal > 0 ? Math.min(sessionDone + 1, sessionTotal) : 0;
44486
+ const percentage = sessionTotal > 0 ? Math.round((sessionDone / sessionTotal) * 100) : 0;
44487
+ const elapsedText = formatDurationBrief(elapsedDuration);
44488
+ let estimatedTotalText = 'estimating...';
44489
+ let estimatedLabel = 'after first completion';
44490
+ let isEstimatedTotalKnown = false;
44491
+ if (sessionTotal > 0 && sessionDone > 0) {
44492
+ const estimatedTotalMs = (elapsedDuration.asMilliseconds() * sessionTotal) / sessionDone;
44493
+ const estimatedRemainingMs = Math.max(0, estimatedTotalMs - elapsedDuration.asMilliseconds());
44494
+ const estimatedTotalDuration = moment.duration(estimatedTotalMs);
44495
+ const estimatedCompletion = moment().add(estimatedRemainingMs, 'milliseconds');
44496
+ estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
44497
+ estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
44498
+ isEstimatedTotalKnown = true;
44499
+ }
44500
+ return {
44501
+ totalPrompts,
44502
+ sessionDone,
44503
+ sessionRemaining,
44504
+ sessionTotal,
44505
+ currentPromptIndex,
44506
+ skippedPrompts: stats.belowMinimumPriority,
44507
+ toBeWrittenPrompts: stats.toBeWritten,
44508
+ percentage,
44509
+ elapsedText,
44510
+ estimatedTotalText,
44511
+ estimatedLabel,
44512
+ isEstimatedTotalKnown,
44513
+ };
44514
+ }
44515
+
44516
+ /**
44517
+ * Tracks active coder-run time while excluding pauses and user-confirmation waits.
44518
+ */
44519
+ class CoderRunTimer {
44520
+ /**
44521
+ * Creates a timer anchored at the provided start time.
44522
+ */
44523
+ constructor(startTime, isPausedInitially = false) {
44524
+ this.startTime = startTime;
44525
+ /**
44526
+ * Total milliseconds spent in paused state across the run.
44527
+ */
44528
+ this.pausedMs = 0;
44529
+ if (isPausedInitially) {
44530
+ this.pausedSince = startTime.clone();
44531
+ }
44532
+ }
44533
+ /**
44534
+ * Pauses active-time tracking until `resume()` is called.
44535
+ */
44536
+ pause() {
44537
+ if (this.pausedSince === undefined) {
44538
+ this.pausedSince = moment();
44539
+ }
44540
+ }
44541
+ /**
44542
+ * Resumes active-time tracking after a pause.
44543
+ */
44544
+ resume() {
44545
+ if (this.pausedSince !== undefined) {
44546
+ this.pausedMs += moment().diff(this.pausedSince);
44547
+ this.pausedSince = undefined;
44548
+ }
44549
+ }
44550
+ /**
44551
+ * Returns the currently accumulated active duration.
44552
+ */
44553
+ getElapsedDuration() {
44554
+ const wallMs = moment().diff(this.startTime);
44555
+ const currentPauseMs = this.pausedSince !== undefined ? moment().diff(this.pausedSince) : 0;
44556
+ return moment.duration(Math.max(0, wallMs - this.pausedMs - currentPauseMs));
44557
+ }
44558
+ }
44559
+
44560
+ /**
44561
+ * Refresh interval for the progress header in milliseconds.
44562
+ */
44563
+ const PROGRESS_REFRESH_INTERVAL_MS = 1000;
44564
+ /**
44565
+ * Number of terminal lines reserved for the sticky progress header.
44566
+ */
44567
+ const PROGRESS_HEADER_RESERVED_LINES = 3;
44361
44568
  /**
44362
44569
  * Compact CLI progress display that stays pinned at the top of the terminal.
44363
44570
  */
@@ -44365,11 +44572,12 @@ class CliProgressDisplay {
44365
44572
  /**
44366
44573
  * Creates a new display that uses the provided start time when computing estimates.
44367
44574
  */
44368
- constructor(startTime) {
44369
- this.startTime = startTime;
44575
+ constructor(startTime, minimumPriority) {
44576
+ this.minimumPriority = minimumPriority;
44370
44577
  this.stats = { done: 0, forAgent: 0, belowMinimumPriority: 0, toBeWritten: 0 };
44371
44578
  this.isHeaderReserved = false;
44372
44579
  this.isInteractive = Boolean(process.stdout.isTTY);
44580
+ this.timer = new CoderRunTimer(startTime);
44373
44581
  if (!this.isInteractive) {
44374
44582
  return;
44375
44583
  }
@@ -44388,6 +44596,20 @@ class CliProgressDisplay {
44388
44596
  this.stats = stats;
44389
44597
  this.render();
44390
44598
  }
44599
+ /**
44600
+ * Pauses the active timer while the runner is waiting for user input.
44601
+ */
44602
+ pauseTimer() {
44603
+ this.timer.pause();
44604
+ this.render();
44605
+ }
44606
+ /**
44607
+ * Resumes the active timer after a pause.
44608
+ */
44609
+ resumeTimer() {
44610
+ this.timer.resume();
44611
+ this.render();
44612
+ }
44391
44613
  /**
44392
44614
  * Stops the automatic refresh cycle and renders the final header once more.
44393
44615
  */
@@ -44405,14 +44627,17 @@ class CliProgressDisplay {
44405
44627
  * Repaint the header without disturbing the current cursor position.
44406
44628
  */
44407
44629
  render() {
44630
+ var _a;
44408
44631
  if (!this.isInteractive) {
44409
44632
  return;
44410
44633
  }
44411
- const line = this.buildProgressLine();
44634
+ const lines = this.buildProgressLines();
44412
44635
  process.stdout.write('\x1b[s');
44413
- cursorTo(process.stdout, 0, 0);
44414
- clearLine(process.stdout, 0);
44415
- process.stdout.write(line);
44636
+ for (let lineIndex = 0; lineIndex < PROGRESS_HEADER_RESERVED_LINES; lineIndex++) {
44637
+ cursorTo(process.stdout, 0, lineIndex);
44638
+ clearLine(process.stdout, 0);
44639
+ process.stdout.write((_a = lines[lineIndex]) !== null && _a !== void 0 ? _a : '');
44640
+ }
44416
44641
  process.stdout.write('\x1b[u');
44417
44642
  }
44418
44643
  /**
@@ -44426,72 +44651,74 @@ class CliProgressDisplay {
44426
44651
  this.isHeaderReserved = true;
44427
44652
  }
44428
44653
  /**
44429
- * Builds the coloured progress text padded to the terminal width.
44654
+ * Builds the colored progress text padded to the terminal width.
44430
44655
  */
44431
- buildProgressLine() {
44656
+ buildProgressLines() {
44432
44657
  var _a, _b;
44433
- const snapshot = buildProgressSnapshot(this.stats, this.startTime, (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done);
44434
- const sessionLabel = `${snapshot.sessionDone}/${snapshot.sessionTotal} Prompts`;
44435
- const totalLabel = `(${snapshot.totalPrompts} total)`;
44436
- const baseLine = `${sessionLabel} ${totalLabel} | ${snapshot.percentage}% | ${snapshot.elapsedText}/${snapshot.estimatedTotalText} | Estimated done ${snapshot.estimatedLabel}`;
44437
- const columns = (_b = process.stdout.columns) !== null && _b !== void 0 ? _b : baseLine.length;
44438
- const padded = baseLine.padEnd(columns > baseLine.length ? columns : baseLine.length);
44439
- return colors.bgWhite(colors.black(padded));
44658
+ const snapshot = buildCoderRunProgressSnapshot(this.stats, this.timer.getElapsedDuration(), (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done);
44659
+ const columns = Math.max(40, (_b = process.stdout.columns) !== null && _b !== void 0 ? _b : 80);
44660
+ const workingLine = snapshot.sessionTotal > 0
44661
+ ? [
44662
+ `Working on ${snapshot.currentPromptIndex}/${snapshot.sessionTotal} prompts`,
44663
+ `Priority >=${this.minimumPriority}`,
44664
+ `Repo total ${snapshot.totalPrompts}`,
44665
+ ].join(' | ')
44666
+ : [`No runnable prompts`, `Priority >=${this.minimumPriority}`, `Repo total ${snapshot.totalPrompts}`].join(' | ');
44667
+ const detailParts = [
44668
+ `Done ${snapshot.sessionDone}/${snapshot.sessionTotal} this run`,
44669
+ `Elapsed ${snapshot.elapsedText} / ${snapshot.estimatedTotalText}`,
44670
+ `Est. done ${snapshot.estimatedLabel}`,
44671
+ ];
44672
+ if (snapshot.skippedPrompts > 0) {
44673
+ detailParts.splice(1, 0, `Skipping ${snapshot.skippedPrompts} prompts with Priority <${this.minimumPriority}`);
44674
+ }
44675
+ if (snapshot.toBeWrittenPrompts > 0) {
44676
+ detailParts.splice(detailParts.length - 2, 0, `Write first ${formatPromptCount$1(snapshot.toBeWrittenPrompts)}`);
44677
+ }
44678
+ const progressLabel = `${snapshot.percentage}% complete (${snapshot.sessionDone}/${snapshot.sessionTotal} done)`;
44679
+ const progressBar = buildProgressBar$1(snapshot.percentage, progressLabel, columns);
44680
+ return [
44681
+ colors.bgCyan.black(padPlainText(workingLine, columns)),
44682
+ colors.bgBlack.white(padPlainText(detailParts.join(' | '), columns)),
44683
+ colors.bgBlack(progressBar),
44684
+ ];
44440
44685
  }
44441
44686
  }
44442
44687
  /**
44443
- * Calculates progress metrics shown in the sticky header.
44688
+ * Builds a colored progress bar with an explicit completion label.
44444
44689
  */
44445
- function buildProgressSnapshot(stats, startTime, initialDone) {
44446
- const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
44447
- const completedPrompts = stats.done;
44448
- const sessionDone = Math.max(0, completedPrompts - initialDone);
44449
- const sessionTotal = sessionDone + stats.forAgent;
44450
- const percentage = totalPrompts > 0 ? Math.round((completedPrompts / totalPrompts) * 100) : 0;
44451
- const elapsedDuration = moment.duration(moment().diff(startTime));
44452
- const elapsedText = formatDurationBrief$1(elapsedDuration);
44453
- let estimatedTotalText = '—';
44454
- let estimatedLabel = 'unknown';
44455
- if (totalPrompts > 0 && completedPrompts > 0) {
44456
- const estimatedTotalMs = (elapsedDuration.asMilliseconds() * totalPrompts) / completedPrompts;
44457
- const estimatedTotalDuration = moment.duration(estimatedTotalMs);
44458
- const estimatedCompletion = startTime.clone().add(estimatedTotalDuration);
44459
- estimatedTotalText = formatDurationBrief$1(estimatedTotalDuration);
44460
- estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS$1);
44461
- }
44462
- return {
44463
- totalPrompts,
44464
- completedPrompts,
44465
- sessionDone,
44466
- sessionTotal,
44467
- percentage,
44468
- elapsedText,
44469
- estimatedTotalText,
44470
- estimatedLabel,
44471
- };
44690
+ function buildProgressBar$1(percentage, label, width) {
44691
+ const safeLabel = ` ${label}`;
44692
+ const barWidth = Math.max(10, width - safeLabel.length);
44693
+ const filledWidth = Math.round((percentage / 100) * barWidth);
44694
+ const emptyWidth = Math.max(0, barWidth - filledWidth);
44695
+ return `${colors.green('█'.repeat(filledWidth))}${colors.gray('░'.repeat(emptyWidth))}${safeLabel}`;
44472
44696
  }
44473
44697
  /**
44474
- * Formats a duration into a compact string such as "3h 12m" or "45s".
44698
+ * Pads or truncates one plain-text header line to the terminal width.
44475
44699
  */
44476
- function formatDurationBrief$1(duration) {
44477
- const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
44478
- const hours = Math.floor(totalSeconds / 3600);
44479
- const minutes = Math.floor((totalSeconds % 3600) / 60);
44480
- const seconds = totalSeconds % 60;
44481
- const parts = [];
44482
- if (hours > 0) {
44483
- parts.push(`${hours}h`);
44484
- }
44485
- if (minutes > 0) {
44486
- parts.push(`${minutes}m`);
44487
- }
44488
- if (!parts.length && seconds > 0) {
44489
- parts.push(`${seconds}s`);
44490
- }
44491
- if (!parts.length) {
44492
- parts.push('0s');
44700
+ function padPlainText(text, width) {
44701
+ if (text.length > width) {
44702
+ if (width <= 3) {
44703
+ return '.'.repeat(width);
44704
+ }
44705
+ return `${text.slice(0, width - 3)}...`;
44493
44706
  }
44494
- return parts.join(' ');
44707
+ return text.padEnd(width);
44708
+ }
44709
+ /**
44710
+ * Formats a prompt count with the correct singular/plural noun.
44711
+ */
44712
+ function formatPromptCount$1(count) {
44713
+ return `${count} prompt${count === 1 ? '' : 's'}`;
44714
+ }
44715
+
44716
+ /**
44717
+ * Formats commit message lines for console display.
44718
+ */
44719
+ function formatCommitMessageForDisplay(message) {
44720
+ const lines = message.split(/\r?\n/);
44721
+ return lines.map((line) => colors.bgBlue.white(` ${line} `)).join('\n');
44495
44722
  }
44496
44723
 
44497
44724
  /**
@@ -44705,14 +44932,6 @@ function normalizeCrLfToLf(content) {
44705
44932
  return normalized.subarray(0, writeIndex);
44706
44933
  }
44707
44934
 
44708
- /**
44709
- * Formats commit message lines for console display.
44710
- */
44711
- function formatCommitMessageForDisplay(message) {
44712
- const lines = message.split(/\r?\n/);
44713
- return lines.map((line) => colors.bgBlue.white(` ${line} `)).join('\n');
44714
- }
44715
-
44716
44935
  /**
44717
44936
  * Prints the formatted commit message preview.
44718
44937
  */
@@ -44749,6 +44968,45 @@ async function resolveCoderContext(contextReference, currentWorkingDirectory) {
44749
44968
  return readFile(contextPath, 'utf-8');
44750
44969
  }
44751
44970
 
44971
+ /**
44972
+ * Builds the temporary live runtime log path for one prompt script and its sibling test script.
44973
+ */
44974
+ function buildScriptLogPath(scriptPath) {
44975
+ const basePath = scriptPath.replace(/\.test\.sh$/iu, '').replace(/\.sh$/iu, '');
44976
+ return `${basePath}.log.txt`;
44977
+ }
44978
+
44979
+ /**
44980
+ * Decides whether one temporary prompt artifact should be deleted after a round finishes.
44981
+ */
44982
+ function shouldDeleteTemporaryArtifact({ preserveArtifactsOnSuccess, hasFailed, }) {
44983
+ return !preserveArtifactsOnSuccess && !hasFailed;
44984
+ }
44985
+
44986
+ /**
44987
+ * Runs one prompt-processing round with a dedicated temporary runtime log file that is cleaned up only after successful non-preserved runs.
44988
+ */
44989
+ async function withPromptRuntimeLog(scriptPath, handler, options) {
44990
+ const logPath = buildScriptLogPath(scriptPath);
44991
+ let hasFailed = false;
44992
+ await unlink(logPath).catch(() => undefined);
44993
+ try {
44994
+ return await handler(logPath);
44995
+ }
44996
+ catch (error) {
44997
+ hasFailed = true;
44998
+ throw error;
44999
+ }
45000
+ finally {
45001
+ if (shouldDeleteTemporaryArtifact({
45002
+ preserveArtifactsOnSuccess: options === null || options === void 0 ? void 0 : options.preserveArtifactsOnSuccess,
45003
+ hasFailed,
45004
+ })) {
45005
+ await unlink(logPath).catch(() => undefined);
45006
+ }
45007
+ }
45008
+ }
45009
+
44752
45010
  /**
44753
45011
  * Waits for the user to press Enter before continuing.
44754
45012
  */
@@ -46588,6 +46846,15 @@ function buildPromptLabelForDisplay(file, section) {
46588
46846
  return `${relative(process.cwd(), file.path).replace(/\\/g, '/')}#${section.startLine + 1}`;
46589
46847
  }
46590
46848
 
46849
+ /**
46850
+ * Extracts a short summary line from a prompt section.
46851
+ */
46852
+ function buildPromptSummary(file, section) {
46853
+ const lines = buildCodexPrompt(file, section).split(/\r?\n/);
46854
+ const firstLine = lines.find((line) => line.trim() !== '');
46855
+ return (firstLine === null || firstLine === void 0 ? void 0 : firstLine.trim()) || '(empty prompt)';
46856
+ }
46857
+
46591
46858
  /**
46592
46859
  * Builds the script path for a prompt section.
46593
46860
  */
@@ -46647,15 +46914,6 @@ function findNextTodoPrompt(files, minimumPriority = 0) {
46647
46914
  return nextPrompt;
46648
46915
  }
46649
46916
 
46650
- /**
46651
- * Extracts a short summary line from a prompt section.
46652
- */
46653
- function buildPromptSummary(file, section) {
46654
- const lines = buildCodexPrompt(file, section).split(/\r?\n/);
46655
- const firstLine = lines.find((line) => line.trim() !== '');
46656
- return (firstLine === null || firstLine === void 0 ? void 0 : firstLine.trim()) || '(empty prompt)';
46657
- }
46658
-
46659
46917
  /**
46660
46918
  * Lists upcoming tasks that are ready to run (no authoring placeholders).
46661
46919
  */
@@ -47052,25 +47310,190 @@ function toPosixPath(filePath) {
47052
47310
  }
47053
47311
 
47054
47312
  /**
47055
- * Creates a temporary script file, runs a handler, and ensures cleanup.
47313
+ * Environment variable read by the shell wrapper to tee live output into the temporary runtime log file.
47314
+ */
47315
+ const PTBK_CODER_LOG_FILE_ENV_NAME = 'PTBK_CODER_LOG_FILE';
47316
+ /**
47317
+ * Small bash wrapper that preserves stdout/stderr streams while teeing both into the runtime log file.
47318
+ */
47319
+ const LOGGED_BASH_WRAPPER_COMMAND = `
47320
+ if [ -n "\${${PTBK_CODER_LOG_FILE_ENV_NAME}:-}" ]; then
47321
+ exec > >(tee -a "\$${PTBK_CODER_LOG_FILE_ENV_NAME}") 2> >(tee -a "\$${PTBK_CODER_LOG_FILE_ENV_NAME}" >&2)
47322
+ fi
47323
+ bash "$1"
47324
+ `.trim();
47325
+ /**
47326
+ * Shapes one bash invocation that optionally mirrors live script output into a temporary log file.
47327
+ */
47328
+ function buildLoggedBashExecution(scriptPath, logPath) {
47329
+ return {
47330
+ args: ['-lc', LOGGED_BASH_WRAPPER_COMMAND, 'ptbk-coder-temp-script', toPosixPath(scriptPath)],
47331
+ env: logPath ? { [PTBK_CODER_LOG_FILE_ENV_NAME]: toPosixPath(logPath) } : undefined,
47332
+ };
47333
+ }
47334
+ /**
47335
+ * Appends one execution-start section with the raw script input before the shell begins producing output.
47336
+ */
47337
+ async function appendScriptExecutionLogStart({ scriptPath, scriptContent, logPath, }) {
47338
+ if (!logPath) {
47339
+ return;
47340
+ }
47341
+ await mkdir(dirname(logPath), { recursive: true });
47342
+ const scriptKind = describeTempScriptKind(scriptPath);
47343
+ const normalizedInput = scriptContent.replace(/\r\n/g, '\n').trimEnd();
47344
+ const logSection = [
47345
+ `=== ${scriptKind} started at ${new Date().toISOString()} ===`,
47346
+ `Script path: ${toPosixPath(scriptPath)}`,
47347
+ '',
47348
+ '--- raw input ---',
47349
+ normalizedInput,
47350
+ '',
47351
+ '--- raw output ---',
47352
+ '',
47353
+ ].join('\n');
47354
+ await appendFile(logPath, `${logSection}\n`, 'utf-8');
47355
+ }
47356
+ /**
47357
+ * Appends one execution-finish section after the shell settles.
47358
+ */
47359
+ async function appendScriptExecutionLogFinish({ scriptPath, logPath, status, details, }) {
47360
+ if (!logPath) {
47361
+ return;
47362
+ }
47363
+ const scriptKind = describeTempScriptKind(scriptPath);
47364
+ const logLines = [
47365
+ '',
47366
+ `=== ${scriptKind} finished at ${new Date().toISOString()} ===`,
47367
+ `Status: ${status}`,
47368
+ ];
47369
+ if (details !== undefined) {
47370
+ logLines.push('');
47371
+ logLines.push('--- details ---');
47372
+ logLines.push(formatUnknownErrorDetails(details));
47373
+ }
47374
+ logLines.push('');
47375
+ await appendFile(logPath, `${logLines.join('\n')}\n`, 'utf-8');
47376
+ }
47377
+ /**
47378
+ * Distinguishes prompt-runner and verification temp shells in the shared runtime log.
47379
+ */
47380
+ function describeTempScriptKind(scriptPath) {
47381
+ return scriptPath.toLowerCase().endsWith('.test.sh') ? 'test shell' : 'runner shell';
47382
+ }
47383
+
47384
+ /**
47385
+ * Runs one temporary bash script, optionally mirroring its raw input/output into a live runtime log file.
47386
+ */
47387
+ async function runBashScriptWithOutput(options) {
47388
+ await appendScriptExecutionLogStart(options);
47389
+ const bashExecution = buildLoggedBashExecution(options.scriptPath, options.logPath);
47390
+ const scriptPathPosix = toPosixPath(options.scriptPath);
47391
+ return await new Promise((resolve, reject) => {
47392
+ const commandProcess = spawn('bash', bashExecution.args, {
47393
+ env: bashExecution.env ? { ...process.env, ...bashExecution.env } : process.env,
47394
+ });
47395
+ let output = '';
47396
+ let settled = false;
47397
+ let isSettling = false;
47398
+ /**
47399
+ * Appends the final log footer before settling.
47400
+ */
47401
+ const finishLog = async (status, details) => {
47402
+ await appendScriptExecutionLogFinish({
47403
+ scriptPath: options.scriptPath,
47404
+ logPath: options.logPath,
47405
+ status,
47406
+ details,
47407
+ });
47408
+ };
47409
+ /**
47410
+ * Ensures the promise settles only once.
47411
+ */
47412
+ const settleOnce = (handler) => {
47413
+ if (settled) {
47414
+ return;
47415
+ }
47416
+ settled = true;
47417
+ handler();
47418
+ };
47419
+ /**
47420
+ * Appends the final log footer and settles the promise exactly once.
47421
+ */
47422
+ const settleWithLog = (status, handler, details) => {
47423
+ if (isSettling || settled) {
47424
+ return;
47425
+ }
47426
+ isSettling = true;
47427
+ void finishLog(status, details).finally(() => {
47428
+ settleOnce(handler);
47429
+ });
47430
+ };
47431
+ commandProcess.stdout.on('data', (stdout) => {
47432
+ const chunk = stdout.toString();
47433
+ output += chunk;
47434
+ console.info(chunk);
47435
+ });
47436
+ commandProcess.stderr.on('data', (stderr) => {
47437
+ const chunk = stderr.toString();
47438
+ output += chunk;
47439
+ if (chunk.trim()) {
47440
+ console.warn(chunk);
47441
+ }
47442
+ });
47443
+ /**
47444
+ * Handles process exit and resolves or rejects accordingly.
47445
+ */
47446
+ const handleExit = (code) => {
47447
+ if (code === 0) {
47448
+ settleWithLog('succeeded', () => resolve(spaceTrim$1(output)));
47449
+ return;
47450
+ }
47451
+ const failure = new Error(spaceTrim$1(output) || `Command "bash ${scriptPathPosix}" exited with code ${code}`);
47452
+ settleWithLog(`failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}`, () => reject(failure), failure);
47453
+ };
47454
+ commandProcess.on('close', handleExit);
47455
+ commandProcess.on('exit', handleExit);
47456
+ commandProcess.on('disconnect', () => {
47457
+ const failure = new Error(`Command "bash ${scriptPathPosix}" disconnected`);
47458
+ settleWithLog('failed after disconnect', () => reject(failure), failure);
47459
+ });
47460
+ commandProcess.on('error', (error) => {
47461
+ const failure = new Error(`Command "bash ${scriptPathPosix}" failed: ${error.message}`);
47462
+ settleWithLog('failed before completion', () => reject(failure), failure);
47463
+ });
47464
+ });
47465
+ }
47466
+
47467
+ /**
47468
+ * Creates a temporary script file, runs a handler, and cleans it up unless preservation is requested or the run fails.
47056
47469
  */
47057
47470
  async function withTempScript(options, handler) {
47058
47471
  const { scriptPath, scriptContent } = options;
47472
+ let hasFailed = false;
47059
47473
  await mkdir(dirname$1(scriptPath), { recursive: true });
47060
47474
  await writeFile(scriptPath, scriptContent, 'utf-8');
47061
- const result = await handler(scriptPath);
47062
- await unlink(scriptPath).catch(() => undefined);
47063
- return result;
47475
+ try {
47476
+ return await handler(scriptPath);
47477
+ }
47478
+ catch (error) {
47479
+ hasFailed = true;
47480
+ throw error;
47481
+ }
47482
+ finally {
47483
+ if (shouldDeleteTemporaryArtifact({ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess, hasFailed })) {
47484
+ await unlink(scriptPath).catch(() => undefined);
47485
+ }
47486
+ }
47064
47487
  }
47065
47488
 
47066
47489
  /**
47067
- * Creates a temporary script file, runs it, captures output, and then deletes it.
47490
+ * Creates a temporary script file, runs it, captures output, and cleans it up unless preservation is requested or the run fails.
47068
47491
  */
47069
47492
  async function $runGoScriptWithOutput(options) {
47070
47493
  return await withTempScript(options, async (scriptPath) => {
47071
- return await $execCommand({
47072
- command: `bash "${toPosixPath(scriptPath)}"`,
47073
- isVerbose: true, // <- Note: Proxy the raw command output to the console
47494
+ return await runBashScriptWithOutput({
47495
+ ...options,
47496
+ scriptPath,
47074
47497
  });
47075
47498
  });
47076
47499
  }
@@ -47166,6 +47589,8 @@ class ClaudeCodeRunner {
47166
47589
  const output = await $runGoScriptWithOutput({
47167
47590
  scriptPath: options.scriptPath,
47168
47591
  scriptContent,
47592
+ logPath: options.logPath,
47593
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47169
47594
  });
47170
47595
  const usage = parseClaudeCodeJsonOutput(output);
47171
47596
  return { usage };
@@ -47173,13 +47598,13 @@ class ClaudeCodeRunner {
47173
47598
  }
47174
47599
 
47175
47600
  /**
47176
- * Creates a temporary script file, runs it, and then deletes it.
47601
+ * Creates a temporary script file, runs it, and cleans it up unless preservation is requested or the run fails.
47177
47602
  */
47178
47603
  async function $runGoScript(options) {
47179
47604
  await withTempScript(options, async (scriptPath) => {
47180
- await $execCommand({
47181
- command: `bash "${toPosixPath(scriptPath)}"`,
47182
- isVerbose: true, // <- Note: Proxy the raw command output to the console
47605
+ await runBashScriptWithOutput({
47606
+ ...options,
47607
+ scriptPath,
47183
47608
  });
47184
47609
  });
47185
47610
  }
@@ -47228,6 +47653,8 @@ class ClineRunner {
47228
47653
  await $runGoScript({
47229
47654
  scriptPath: options.scriptPath,
47230
47655
  scriptContent,
47656
+ logPath: options.logPath,
47657
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47231
47658
  });
47232
47659
  return { usage: UNCERTAIN_USAGE };
47233
47660
  }
@@ -47351,6 +47778,8 @@ class GeminiRunner {
47351
47778
  const output = await $runGoScriptWithOutput({
47352
47779
  scriptPath: options.scriptPath,
47353
47780
  scriptContent,
47781
+ logPath: options.logPath,
47782
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47354
47783
  });
47355
47784
  const usage = parseGeminiUsageFromOutput(output, options.prompt, this.options.model);
47356
47785
  return { usage };
@@ -47406,6 +47835,8 @@ class GitHubCopilotRunner {
47406
47835
  await $runGoScript({
47407
47836
  scriptPath: options.scriptPath,
47408
47837
  scriptContent,
47838
+ logPath: options.logPath,
47839
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47409
47840
  });
47410
47841
  return { usage: UNCERTAIN_USAGE };
47411
47842
  }
@@ -47442,14 +47873,30 @@ function buildCommandFailureMessage(scriptPathPosix, code, fullOutput) {
47442
47873
  async function runScriptUntilMarkerIdle(options) {
47443
47874
  const { scriptPath, completionLineMatcher, idleTimeoutMs } = options;
47444
47875
  const scriptPathPosix = toPosixPath(scriptPath);
47876
+ await appendScriptExecutionLogStart(options);
47877
+ const bashExecution = buildLoggedBashExecution(scriptPath, options.logPath);
47445
47878
  return await new Promise((resolve, reject) => {
47446
- const commandProcess = spawn('bash', [scriptPathPosix], { env: process.env });
47879
+ const commandProcess = spawn('bash', bashExecution.args, {
47880
+ env: bashExecution.env ? { ...process.env, ...bashExecution.env } : process.env,
47881
+ });
47447
47882
  let stdoutBuffer = '';
47448
47883
  let stderrBuffer = '';
47449
47884
  let fullOutput = '';
47450
47885
  let markerSeen = false;
47451
47886
  let idleTimer;
47452
47887
  let settled = false;
47888
+ let isSettling = false;
47889
+ /**
47890
+ * Appends the final log footer before settling.
47891
+ */
47892
+ const finishLog = async (status, details) => {
47893
+ await appendScriptExecutionLogFinish({
47894
+ scriptPath,
47895
+ logPath: options.logPath,
47896
+ status,
47897
+ details,
47898
+ });
47899
+ };
47453
47900
  /**
47454
47901
  * Ensures the promise settles only once.
47455
47902
  */
@@ -47464,6 +47911,18 @@ async function runScriptUntilMarkerIdle(options) {
47464
47911
  }
47465
47912
  handler();
47466
47913
  };
47914
+ /**
47915
+ * Appends the final log footer and settles the promise exactly once.
47916
+ */
47917
+ const settleWithLog = (status, handler, details) => {
47918
+ if (isSettling || settled) {
47919
+ return;
47920
+ }
47921
+ isSettling = true;
47922
+ void finishLog(status, details).finally(() => {
47923
+ settleOnce(handler);
47924
+ });
47925
+ };
47467
47926
  /**
47468
47927
  * Resets the idle timer that triggers termination after inactivity.
47469
47928
  */
@@ -47473,7 +47932,7 @@ async function runScriptUntilMarkerIdle(options) {
47473
47932
  }
47474
47933
  idleTimer = setTimeout(() => {
47475
47934
  commandProcess.kill();
47476
- settleOnce(() => resolve(fullOutput));
47935
+ settleWithLog('completed after idle timeout', () => resolve(fullOutput));
47477
47936
  }, idleTimeoutMs);
47478
47937
  };
47479
47938
  /**
@@ -47527,41 +47986,43 @@ async function runScriptUntilMarkerIdle(options) {
47527
47986
  * Handles process exit and resolves or rejects accordingly.
47528
47987
  */
47529
47988
  const handleExit = (code) => {
47530
- settleOnce(() => {
47531
- if (code === 0 || markerSeen) {
47989
+ const failure = code === 0 || markerSeen ? undefined : new Error(buildCommandFailureMessage(scriptPathPosix, code, fullOutput));
47990
+ const status = failure ? `failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}` : 'succeeded';
47991
+ settleWithLog(status, () => {
47992
+ if (!failure) {
47532
47993
  resolve(fullOutput);
47533
47994
  return;
47534
47995
  }
47535
- reject(new Error(buildCommandFailureMessage(scriptPathPosix, code, fullOutput)));
47996
+ reject(failure);
47536
47997
  });
47537
47998
  };
47538
47999
  commandProcess.on('close', handleExit);
47539
48000
  commandProcess.on('exit', handleExit);
47540
48001
  commandProcess.on('disconnect', () => {
47541
- settleOnce(() => {
47542
- reject(new Error(buildCommandFailureMessage(scriptPathPosix, null, fullOutput)));
47543
- });
48002
+ const failure = new Error(buildCommandFailureMessage(scriptPathPosix, null, fullOutput));
48003
+ settleWithLog('failed after disconnect', () => reject(failure), failure);
47544
48004
  });
47545
48005
  commandProcess.on('error', (error) => {
47546
- settleOnce(() => {
47547
- const outputSnippet = createOutputSnippet(fullOutput);
47548
- const details = outputSnippet ? `\n\n${outputSnippet}` : '';
47549
- reject(new Error(`Command "bash ${scriptPathPosix}" failed: ${error.message}${details}`));
47550
- });
48006
+ const outputSnippet = createOutputSnippet(fullOutput);
48007
+ const details = outputSnippet ? `\n\n${outputSnippet}` : '';
48008
+ const failure = new Error(`Command "bash ${scriptPathPosix}" failed: ${error.message}${details}`);
48009
+ settleWithLog('failed before completion', () => reject(failure), failure);
47551
48010
  });
47552
48011
  });
47553
48012
  }
47554
48013
 
47555
48014
  /**
47556
- * Creates a temporary script file, runs it, waits for a completion marker and idle time, and then deletes it.
48015
+ * Creates a temporary script file, runs it, waits for a completion marker and idle time, and cleans it up unless preservation is requested or the run fails.
47557
48016
  * Returns the captured output for post-processing.
47558
48017
  */
47559
48018
  async function $runGoScriptUntilMarkerIdle(options) {
47560
48019
  return await withTempScript(options, async (scriptPath) => {
47561
48020
  return await runScriptUntilMarkerIdle({
47562
48021
  scriptPath,
48022
+ scriptContent: options.scriptContent,
47563
48023
  completionLineMatcher: options.completionLineMatcher,
47564
48024
  idleTimeoutMs: options.idleTimeoutMs,
48025
+ logPath: options.logPath,
47565
48026
  });
47566
48027
  });
47567
48028
  }
@@ -47939,6 +48400,8 @@ class OpenAiCodexRunner {
47939
48400
  scriptContent,
47940
48401
  completionLineMatcher: CODEX_COMPLETION_LINE,
47941
48402
  idleTimeoutMs: CODEX_COMPLETION_IDLE_MS,
48403
+ logPath: options.logPath,
48404
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
47942
48405
  });
47943
48406
  this.rateLimitBackoff.reset();
47944
48407
  return { usage: buildCodexUsageFromOutput(output, this.options.model) };
@@ -48072,6 +48535,8 @@ class OpencodeRunner {
48072
48535
  output = await $runGoScriptWithOutput({
48073
48536
  scriptPath: options.scriptPath,
48074
48537
  scriptContent,
48538
+ logPath: options.logPath,
48539
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48075
48540
  });
48076
48541
  }
48077
48542
  catch (error) {
@@ -48098,6 +48563,8 @@ async function runPromptTestCommand(options) {
48098
48563
  cd "${projectPath}"
48099
48564
  ${options.command}
48100
48565
  `),
48566
+ logPath: options.logPath,
48567
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48101
48568
  });
48102
48569
  }
48103
48570
 
@@ -48121,6 +48588,8 @@ async function runPromptWithTestFeedback(options) {
48121
48588
  prompt: options.prompt,
48122
48589
  scriptPath: options.scriptPath,
48123
48590
  projectPath: options.projectPath,
48591
+ logPath: options.logPath,
48592
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48124
48593
  });
48125
48594
  return { ...result, attemptCount: 1 };
48126
48595
  }
@@ -48132,6 +48601,8 @@ async function runPromptWithTestFeedback(options) {
48132
48601
  prompt: promptForCurrentAttempt,
48133
48602
  scriptPath: options.scriptPath,
48134
48603
  projectPath: options.projectPath,
48604
+ logPath: options.logPath,
48605
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48135
48606
  });
48136
48607
  console.info(colors.gray(`Running verification command after attempt #${attemptCount}: ${normalizedTestCommand}`));
48137
48608
  try {
@@ -48139,6 +48610,8 @@ async function runPromptWithTestFeedback(options) {
48139
48610
  command: normalizedTestCommand,
48140
48611
  projectPath: options.projectPath,
48141
48612
  scriptPath: buildPromptTestScriptPath(options.scriptPath),
48613
+ logPath: options.logPath,
48614
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
48142
48615
  });
48143
48616
  return { ...result, attemptCount };
48144
48617
  }
@@ -48220,24 +48693,203 @@ function buildPromptTestScriptPath(scriptPath) {
48220
48693
  }
48221
48694
 
48222
48695
  /**
48223
- * Maximum number of agent output lines kept in the scrolling output area.
48224
- *
48225
- * @private internal constant of coder run UI
48696
+ * Maximum number of output lines reserved for agent output in the UI.
48226
48697
  */
48227
- const MAX_AGENT_OUTPUT_LINES = 12;
48698
+ const MAX_VISIBLE_OUTPUT_LINES = 8;
48228
48699
  /**
48229
- * Calendar formats used when displaying the estimated completion time.
48700
+ * Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
48701
+ */
48702
+ function buildCoderRunUiFrame(options) {
48703
+ const totalWidth = Math.max(56, Math.min(options.terminalWidth, 96));
48704
+ const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
48705
+ const promptStatusPrefix = isPromptActive ? `${colors.yellow(`${options.spinner} `)}` : '';
48706
+ const sessionScopeLine = options.progress.sessionTotal > 0
48707
+ ? `Working on ${options.progress.currentPromptIndex}/${options.progress.sessionTotal} prompts with Priority ≥${options.config.priority}`
48708
+ : `No runnable prompts with Priority ≥${options.config.priority}`;
48709
+ const sessionCountLine = `Done ${options.progress.sessionDone}/${options.progress.sessionTotal} this run · Repo total ${options.progress.totalPrompts}`;
48710
+ const sessionQueueParts = [];
48711
+ if (options.progress.skippedPrompts > 0) {
48712
+ sessionQueueParts.push(`Skipping ${formatPromptCount(options.progress.skippedPrompts)} with Priority <${options.config.priority}`);
48713
+ }
48714
+ if (options.progress.toBeWrittenPrompts > 0) {
48715
+ sessionQueueParts.push(`Write first ${formatPromptCount(options.progress.toBeWrittenPrompts)}`);
48716
+ }
48717
+ const sessionLines = [
48718
+ `${buildPhaseBadge(options.phase, options.pauseState)} ${fitPlainText(options.statusMessage, totalWidth - 18)}`,
48719
+ sessionScopeLine,
48720
+ sessionCountLine,
48721
+ ...(sessionQueueParts.length > 0 ? [sessionQueueParts.join(' · ')] : []),
48722
+ `Elapsed ${options.progress.elapsedText} · Est. total ${options.progress.estimatedTotalText} · Est. done ${options.progress.estimatedLabel}`,
48723
+ buildProgressBar(options.progress.percentage, totalWidth - 6, `${options.progress.percentage}% complete (${options.progress.sessionDone}/${options.progress.sessionTotal} done)`),
48724
+ ];
48725
+ const metadataParts = [options.config.agentName || 'No agent selected'];
48726
+ if (options.config.modelName) {
48727
+ metadataParts.push(options.config.modelName);
48728
+ }
48729
+ if (options.config.thinkingLevel) {
48730
+ metadataParts.push(`thinking ${options.config.thinkingLevel}`);
48731
+ }
48732
+ const runnerDetails = [
48733
+ [`${colors.bgCyan.black(' PTBK ')}`, colors.bgBlue.white(' CODER '), colors.bold.white(' Promptbook Coder')]
48734
+ .join(''),
48735
+ metadataParts.join(' · '),
48736
+ buildConfigSummaryLine(options.config),
48737
+ ];
48738
+ const currentTaskLines = options.currentPromptLabel
48739
+ ? [
48740
+ `${promptStatusPrefix}${colors.bold.white(fitPlainText(options.currentPromptLabel, totalWidth - 8))}`,
48741
+ `Attempt ${options.currentAttempt}/${options.maxAttempts} · ${options.statusMessage}`,
48742
+ ...options.detailLines.map((detailLine) => `• ${detailLine}`),
48743
+ ]
48744
+ : [options.statusMessage, ...options.detailLines.map((detailLine) => `• ${detailLine}`)];
48745
+ const visibleOutputLines = options.agentOutputLines.length > 0
48746
+ ? options.agentOutputLines.slice(-MAX_VISIBLE_OUTPUT_LINES).map((line) => `› ${stripAnsi(line)}`)
48747
+ : ['No live agent output yet.'];
48748
+ const controls = buildControlPills(options.pauseState, options.pendingEnterLabel).join(' ');
48749
+ const frame = [
48750
+ ...renderBox('Brand', runnerDetails, totalWidth, colors.cyan.bold),
48751
+ ...renderBox('Session', sessionLines, totalWidth, colors.yellow.bold),
48752
+ ...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors.magenta.bold),
48753
+ ...renderBox('Live output', visibleOutputLines, totalWidth, colors.green.bold),
48754
+ ];
48755
+ if (options.errors.length > 0) {
48756
+ frame.push(...renderBox('Errors', options.errors.map((errorLine) => `${colors.red('✗')} ${errorLine}`), totalWidth, colors.red.bold));
48757
+ }
48758
+ frame.push(...renderBox('Controls', [controls], totalWidth, colors.white.bold));
48759
+ return frame;
48760
+ }
48761
+ /**
48762
+ * Renders a framed box with a colored title and padded body lines.
48763
+ */
48764
+ function renderBox(title, lines, totalWidth, colorizeTitle) {
48765
+ const bodyWidth = Math.max(10, totalWidth - 4);
48766
+ const titleText = ` ${title} `;
48767
+ const topBorder = colors.gray('┌') +
48768
+ colorizeTitle(titleText) +
48769
+ colors.gray('─'.repeat(Math.max(0, totalWidth - 2 - titleText.length)) + '┐');
48770
+ const body = lines.map((line) => {
48771
+ const paddedLine = padAnsiText(line, bodyWidth);
48772
+ return colors.gray('│ ') + paddedLine + colors.gray(' │');
48773
+ });
48774
+ const bottomBorder = colors.gray(`└${'─'.repeat(totalWidth - 2)}┘`);
48775
+ return [topBorder, ...body, bottomBorder];
48776
+ }
48777
+ /**
48778
+ * Builds the compact config summary line shown in the branding box.
48779
+ */
48780
+ function buildConfigSummaryLine(config) {
48781
+ const parts = [`Priority ≥${config.priority}`];
48782
+ if (config.context) {
48783
+ parts.unshift(`Context ${config.context}`);
48784
+ }
48785
+ if (config.testCommand) {
48786
+ parts.push(`Test ${config.testCommand}`);
48787
+ }
48788
+ return parts.join(' · ');
48789
+ }
48790
+ /**
48791
+ * Builds the colored phase badge shown in the session box.
48792
+ */
48793
+ function buildPhaseBadge(phase, pauseState) {
48794
+ if (pauseState !== 'RUNNING' || phase === 'paused') {
48795
+ return colors.bgYellow.black(' PAUSED ');
48796
+ }
48797
+ switch (phase) {
48798
+ case 'loading':
48799
+ case 'initializing':
48800
+ return colors.bgCyan.black(' LOADING ');
48801
+ case 'running':
48802
+ return colors.bgGreen.black(' RUNNING ');
48803
+ case 'verifying':
48804
+ return colors.bgMagenta.white(' VERIFYING ');
48805
+ case 'waiting':
48806
+ return colors.bgBlue.white(' WAITING ');
48807
+ case 'done':
48808
+ return colors.bgGreen.black(' DONE ');
48809
+ case 'error':
48810
+ return colors.bgRed.white(' ERROR ');
48811
+ default:
48812
+ return colors.bgWhite.black(' READY ');
48813
+ }
48814
+ }
48815
+ /**
48816
+ * Builds the progress bar shown in the session box.
48817
+ */
48818
+ function buildProgressBar(percentage, availableWidth, label) {
48819
+ const percentageLabel = label;
48820
+ const barWidth = Math.max(10, availableWidth - percentageLabel.length - 1);
48821
+ const filledWidth = Math.round((percentage / 100) * barWidth);
48822
+ const emptyWidth = Math.max(0, barWidth - filledWidth);
48823
+ return `${colors.green('█'.repeat(filledWidth))}${colors.gray('░'.repeat(emptyWidth))} ${percentageLabel}`;
48824
+ }
48825
+ /**
48826
+ * Formats a prompt count with singular/plural wording.
48827
+ */
48828
+ function formatPromptCount(count) {
48829
+ return `${count} prompt${count === 1 ? '' : 's'}`;
48830
+ }
48831
+ /**
48832
+ * Builds the control pills shown in the footer box.
48833
+ */
48834
+ function buildControlPills(pauseState, pendingEnterLabel) {
48835
+ const pills = [];
48836
+ if (pendingEnterLabel) {
48837
+ pills.push(colors.bgWhite.black(' ENTER ') + colors.white(` ${pendingEnterLabel}`));
48838
+ }
48839
+ pills.push(pauseState === 'RUNNING'
48840
+ ? colors.bgYellow.black(' P ') + colors.white(' Pause')
48841
+ : colors.bgYellow.black(' P ') + colors.white(' Resume'));
48842
+ pills.push(colors.bgRed.white(' CTRL+C ') + colors.white(' Exit'));
48843
+ return pills;
48844
+ }
48845
+ /**
48846
+ * Pads or truncates a possibly ANSI-colored line to the target visible width.
48847
+ */
48848
+ function padAnsiText(text, width) {
48849
+ const fittedText = fitAnsiText(text, width);
48850
+ return fittedText + ' '.repeat(Math.max(0, width - visibleLength(fittedText)));
48851
+ }
48852
+ /**
48853
+ * Truncates a possibly ANSI-colored line to the target visible width.
48854
+ */
48855
+ function fitAnsiText(text, width) {
48856
+ if (visibleLength(text) <= width) {
48857
+ return text;
48858
+ }
48859
+ return fitPlainText(stripAnsi(text), width);
48860
+ }
48861
+ /**
48862
+ * Truncates a plain-text line to the target width with an ellipsis.
48863
+ */
48864
+ function fitPlainText(text, width) {
48865
+ if (text.length <= width) {
48866
+ return text;
48867
+ }
48868
+ if (width <= 3) {
48869
+ return '.'.repeat(width);
48870
+ }
48871
+ return `${text.slice(0, width - 3)}...`;
48872
+ }
48873
+ /**
48874
+ * Measures visible string width by stripping ANSI escape codes.
48875
+ */
48876
+ function visibleLength(text) {
48877
+ return stripAnsi(text).length;
48878
+ }
48879
+ /**
48880
+ * Strips ANSI escape codes from a string.
48881
+ */
48882
+ function stripAnsi(text) {
48883
+ // eslint-disable-next-line no-control-regex
48884
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
48885
+ }
48886
+
48887
+ /**
48888
+ * Maximum number of agent output lines kept in the scrolling output area.
48230
48889
  *
48231
48890
  * @private internal constant of coder run UI
48232
48891
  */
48233
- const ESTIMATED_DONE_CALENDAR_FORMATS = {
48234
- sameDay: '[Today] h:mm',
48235
- nextDay: '[Tomorrow] h:mm',
48236
- nextWeek: 'dddd h:mm',
48237
- lastDay: '[Yesterday] h:mm',
48238
- lastWeek: 'dddd h:mm',
48239
- sameElse: 'MMM D h:mm',
48240
- };
48892
+ const MAX_AGENT_OUTPUT_LINES = 12;
48241
48893
  /**
48242
48894
  * Reactive state manager for the coder run terminal UI.
48243
48895
  *
@@ -48253,35 +48905,27 @@ class CoderRunUiState extends EventEmitter {
48253
48905
  this.currentPromptLabel = '';
48254
48906
  this.currentAttempt = 1;
48255
48907
  this.maxAttempts = 3;
48908
+ this.detailLines = [];
48256
48909
  this.agentOutputLines = [];
48257
48910
  this.phase = 'initializing';
48258
48911
  this.statusMessage = 'Initializing...';
48259
48912
  this.errors = [];
48260
48913
  this.stats = { done: 0, forAgent: 0, belowMinimumPriority: 0, toBeWritten: 0 };
48261
- /**
48262
- * Total milliseconds the timer was paused/waiting (excluded from elapsed display).
48263
- */
48264
- this.pausedMs = 0;
48265
- this.startTime = startTime;
48266
- // Timer starts paused — callers call `resumeTimer()` when actual work begins.
48267
- this.pausedSince = startTime.clone();
48914
+ this.timer = new CoderRunTimer(startTime, true);
48268
48915
  }
48269
48916
  /**
48270
48917
  * Pauses the elapsed timer (e.g. while waiting for user input or paused state).
48271
48918
  */
48272
48919
  pauseTimer() {
48273
- if (this.pausedSince === undefined) {
48274
- this.pausedSince = moment();
48275
- }
48920
+ this.timer.pause();
48921
+ this.emitChange();
48276
48922
  }
48277
48923
  /**
48278
48924
  * Resumes the elapsed timer after a pause.
48279
48925
  */
48280
48926
  resumeTimer() {
48281
- if (this.pausedSince !== undefined) {
48282
- this.pausedMs += moment().diff(this.pausedSince);
48283
- this.pausedSince = undefined;
48284
- }
48927
+ this.timer.resume();
48928
+ this.emitChange();
48285
48929
  }
48286
48930
  /**
48287
48931
  * Replaces the configuration shown in the UI header.
@@ -48305,41 +48949,15 @@ class CoderRunUiState extends EventEmitter {
48305
48949
  */
48306
48950
  getProgress() {
48307
48951
  var _a;
48308
- const stats = this.stats;
48309
- const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
48310
- const sessionDone = Math.max(0, stats.done - ((_a = this.initialDone) !== null && _a !== void 0 ? _a : stats.done));
48311
- const sessionTotal = sessionDone + stats.forAgent;
48312
- const percentage = totalPrompts > 0 ? Math.round((stats.done / totalPrompts) * 100) : 0;
48313
- const wallMs = moment().diff(this.startTime);
48314
- const currentPauseMs = this.pausedSince !== undefined ? moment().diff(this.pausedSince) : 0;
48315
- const activeMs = Math.max(0, wallMs - this.pausedMs - currentPauseMs);
48316
- const elapsedDuration = moment.duration(activeMs);
48317
- const elapsedText = formatDurationBrief(elapsedDuration);
48318
- let estimatedTotalText = '\u2014';
48319
- let estimatedLabel = 'unknown';
48320
- if (totalPrompts > 0 && stats.done > 0) {
48321
- const estimatedTotalMs = (elapsedDuration.asMilliseconds() * totalPrompts) / stats.done;
48322
- const estimatedRemainingMs = estimatedTotalMs - elapsedDuration.asMilliseconds();
48323
- const estimatedTotalDuration = moment.duration(estimatedTotalMs);
48324
- const estimatedCompletion = moment().add(estimatedRemainingMs, 'milliseconds');
48325
- estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
48326
- estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
48327
- }
48328
- return {
48329
- totalPrompts,
48330
- sessionDone,
48331
- sessionTotal,
48332
- percentage,
48333
- elapsedText,
48334
- estimatedTotalText,
48335
- estimatedLabel,
48336
- };
48952
+ return buildCoderRunProgressSnapshot(this.stats, this.timer.getElapsedDuration(), (_a = this.initialDone) !== null && _a !== void 0 ? _a : this.stats.done);
48337
48953
  }
48338
48954
  /**
48339
48955
  * Sets the label of the prompt currently being processed and resets per-prompt state.
48340
48956
  */
48341
48957
  setCurrentPrompt(label) {
48342
48958
  this.currentPromptLabel = label;
48959
+ this.detailLines = [];
48960
+ this.pendingEnterLabel = undefined;
48343
48961
  this.agentOutputLines = [];
48344
48962
  this.currentAttempt = 1;
48345
48963
  this.emitChange();
@@ -48379,6 +48997,20 @@ class CoderRunUiState extends EventEmitter {
48379
48997
  this.statusMessage = message;
48380
48998
  this.emitChange();
48381
48999
  }
49000
+ /**
49001
+ * Replaces the contextual detail lines shown beneath the current prompt status.
49002
+ */
49003
+ setDetailLines(detailLines) {
49004
+ this.detailLines = detailLines.filter((detailLine) => detailLine.trim() !== '');
49005
+ this.emitChange();
49006
+ }
49007
+ /**
49008
+ * Sets or clears the Enter-key action label shown in the controls panel.
49009
+ */
49010
+ setPendingEnterLabel(pendingEnterLabel) {
49011
+ this.pendingEnterLabel = pendingEnterLabel;
49012
+ this.emitChange();
49013
+ }
48382
49014
  /**
48383
49015
  * Appends an error message to the error list shown in the UI.
48384
49016
  */
@@ -48390,31 +49022,6 @@ class CoderRunUiState extends EventEmitter {
48390
49022
  this.emit('change');
48391
49023
  }
48392
49024
  }
48393
- /**
48394
- * Formats a duration into a compact string such as "3h 12m" or "45s".
48395
- *
48396
- * @private internal utility of coder run UI
48397
- */
48398
- function formatDurationBrief(duration) {
48399
- const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
48400
- const hours = Math.floor(totalSeconds / 3600);
48401
- const minutes = Math.floor((totalSeconds % 3600) / 60);
48402
- const seconds = totalSeconds % 60;
48403
- const parts = [];
48404
- if (hours > 0) {
48405
- parts.push(`${hours}h`);
48406
- }
48407
- if (minutes > 0) {
48408
- parts.push(`${minutes}m`);
48409
- }
48410
- if (!parts.length && seconds > 0) {
48411
- parts.push(`${seconds}s`);
48412
- }
48413
- if (!parts.length) {
48414
- parts.push('0s');
48415
- }
48416
- return parts.join(' ');
48417
- }
48418
49025
 
48419
49026
  /**
48420
49027
  * Refresh interval for the terminal UI in milliseconds.
@@ -48422,18 +49029,6 @@ function formatDurationBrief(duration) {
48422
49029
  * @private internal constant of coder run UI
48423
49030
  */
48424
49031
  const UI_REFRESH_INTERVAL_MS = 200;
48425
- /**
48426
- * Character width used for the text progress bar.
48427
- *
48428
- * @private internal constant of coder run UI
48429
- */
48430
- const PROGRESS_BAR_WIDTH = 40;
48431
- /**
48432
- * Maximum number of output lines reserved for agent output in the UI.
48433
- *
48434
- * @private internal constant of coder run UI
48435
- */
48436
- const MAX_VISIBLE_OUTPUT_LINES = 8;
48437
49032
  /**
48438
49033
  * Spinner animation frames.
48439
49034
  *
@@ -48452,31 +49047,12 @@ const SPINNER_FRAMES = [
48452
49047
  '\u280F',
48453
49048
  ];
48454
49049
  /**
48455
- * Strips ANSI escape codes from a string.
48456
- *
48457
- * @private internal utility of coder run UI
48458
- */
48459
- function stripAnsi(text) {
48460
- // eslint-disable-next-line no-control-regex
48461
- return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
48462
- }
48463
- /**
48464
- * Returns the usable terminal width, capped at 80.
49050
+ * Returns the usable terminal width, capped at 96.
48465
49051
  *
48466
49052
  * @private internal utility of coder run UI
48467
49053
  */
48468
49054
  function getTerminalWidth() {
48469
- return Math.min(process.stdout.columns || 80, 80);
48470
- }
48471
- /**
48472
- * Builds a text progress bar string from a percentage.
48473
- *
48474
- * @private internal utility of coder run UI
48475
- */
48476
- function buildProgressBar(percentage) {
48477
- const filled = Math.round((percentage / 100) * PROGRESS_BAR_WIDTH);
48478
- const empty = PROGRESS_BAR_WIDTH - filled;
48479
- return colors.green('\u2588'.repeat(filled)) + colors.gray('\u2591'.repeat(empty)) + ` ${percentage}%`;
49055
+ return Math.min(process.stdout.columns || 80, 96);
48480
49056
  }
48481
49057
  /**
48482
49058
  * Boots the ANSI terminal UI for `ptbk coder run`.
@@ -48497,21 +49073,20 @@ function renderCoderRunUi(startTime) {
48497
49073
  state,
48498
49074
  startCapturingAgentOutput: () => { },
48499
49075
  stopCapturingAgentOutput: () => { },
49076
+ waitForEnter: async () => { },
48500
49077
  cleanup: () => { },
48501
49078
  };
48502
49079
  }
48503
- // --- Console interception ---
48504
49080
  const originalConsoleInfo = console.info;
48505
49081
  const originalConsoleWarn = console.warn;
48506
49082
  const originalConsoleError = console.error;
48507
49083
  const originalConsoleLog = console.log;
48508
49084
  let isCapturing = false;
49085
+ let pendingEnterResolver;
48509
49086
  console.info = (...args) => {
48510
49087
  if (isCapturing) {
48511
49088
  state.addAgentOutput(args.map(String).join(' '));
48512
49089
  }
48513
- // In UI mode, non-captured output is intentionally suppressed
48514
- // so it does not interfere with the repainted frame.
48515
49090
  };
48516
49091
  console.warn = (...args) => {
48517
49092
  if (isCapturing) {
@@ -48528,29 +49103,11 @@ function renderCoderRunUi(startTime) {
48528
49103
  state.addAgentOutput(args.map(String).join(' '));
48529
49104
  }
48530
49105
  };
48531
- // --- Keyboard input (pause) ---
48532
49106
  const readline = require('readline');
48533
49107
  readline.emitKeypressEvents(process.stdin);
48534
49108
  if (process.stdin.isTTY) {
48535
49109
  process.stdin.setRawMode(true);
48536
49110
  }
48537
- const keypressHandler = (_str, key) => {
48538
- if (key.ctrl && key.name === 'c') {
48539
- cleanup();
48540
- process.exit(0);
48541
- }
48542
- if (key.name === 'p') {
48543
- const current = getPauseState();
48544
- if (current === 'RUNNING') {
48545
- requestPause();
48546
- }
48547
- else {
48548
- requestResume();
48549
- }
48550
- }
48551
- };
48552
- process.stdin.on('keypress', keypressHandler);
48553
- // --- Rendering ---
48554
49111
  let spinnerFrame = 0;
48555
49112
  let previousFrameLineCount = 0;
48556
49113
  let isRendering = false;
@@ -48578,8 +49135,22 @@ function renderCoderRunUi(startTime) {
48578
49135
  }
48579
49136
  isRendering = true;
48580
49137
  try {
48581
- const lines = buildFrame();
48582
- // Move cursor up to clear the previous frame.
49138
+ const lines = buildCoderRunUiFrame({
49139
+ terminalWidth: getTerminalWidth(),
49140
+ spinner: SPINNER_FRAMES[spinnerFrame],
49141
+ pauseState: getPauseState(),
49142
+ config: state.config,
49143
+ phase: state.phase,
49144
+ currentPromptLabel: state.currentPromptLabel,
49145
+ currentAttempt: state.currentAttempt,
49146
+ maxAttempts: state.maxAttempts,
49147
+ statusMessage: state.statusMessage,
49148
+ detailLines: state.detailLines,
49149
+ pendingEnterLabel: state.pendingEnterLabel,
49150
+ agentOutputLines: state.agentOutputLines,
49151
+ errors: state.errors,
49152
+ progress: state.getProgress(),
49153
+ });
48583
49154
  if (previousFrameLineCount > 0) {
48584
49155
  process.stdout.write(`\x1b[${previousFrameLineCount}A`);
48585
49156
  }
@@ -48591,14 +49162,12 @@ function renderCoderRunUi(startTime) {
48591
49162
  process.stdout.write('\n');
48592
49163
  }
48593
49164
  }
48594
- // Clear any leftover lines from a previous longer frame.
48595
49165
  if (lines.length < previousFrameLineCount) {
48596
49166
  for (let i = lines.length; i < previousFrameLineCount; i++) {
48597
49167
  process.stdout.write('\n');
48598
49168
  clearLine(process.stdout, 0);
48599
49169
  cursorTo(process.stdout, 0);
48600
49170
  }
48601
- // Move back up to the end of the current frame.
48602
49171
  const overshoot = previousFrameLineCount - lines.length;
48603
49172
  if (overshoot > 0) {
48604
49173
  process.stdout.write(`\x1b[${overshoot}A`);
@@ -48611,94 +49180,35 @@ function renderCoderRunUi(startTime) {
48611
49180
  isRendering = false;
48612
49181
  }
48613
49182
  }
48614
- /**
48615
- * Builds the complete frame as an array of terminal lines.
48616
- */
48617
- function buildFrame() {
48618
- const w = getTerminalWidth();
48619
- const sep = colors.gray('\u2500'.repeat(w - 2));
48620
- const spinner = SPINNER_FRAMES[spinnerFrame];
48621
- const { config, phase, currentPromptLabel, currentAttempt, maxAttempts, statusMessage, agentOutputLines, errors, } = state;
48622
- const progress = state.getProgress();
48623
- const isPaused = getPauseState() !== 'RUNNING';
48624
- const isActive = phase === 'running' || phase === 'verifying' || phase === 'loading';
48625
- const lines = [];
48626
- // --- Branding ---
48627
- lines.push(colors.bold.cyan('\u2728 Promptbook Coder'));
48628
- // --- Config ---
48629
- let configLine1 = `Agent: ${colors.bold.green(config.agentName)}`;
48630
- if (config.modelName) {
48631
- configLine1 += ` \u2502 Model: ${colors.bold(config.modelName)}`;
48632
- }
48633
- if (config.thinkingLevel) {
48634
- configLine1 += ` \u2502 Thinking: ${colors.bold(config.thinkingLevel)}`;
48635
- }
48636
- lines.push(configLine1);
48637
- let configLine2 = '';
48638
- if (config.context) {
48639
- configLine2 += `Context: ${colors.yellow(config.context)} \u2502 `;
48640
- }
48641
- configLine2 += `Priority: \u2265${config.priority}`;
48642
- if (config.testCommand) {
48643
- configLine2 += ` \u2502 Test: ${colors.gray(config.testCommand)}`;
48644
- }
48645
- lines.push(configLine2);
48646
- // --- Separator ---
48647
- lines.push(sep);
48648
- // --- Progress ---
48649
- const progressSummary = [
48650
- `${progress.sessionDone}/${progress.sessionTotal} Prompts (${progress.totalPrompts} total)`,
48651
- `${progress.elapsedText}/${progress.estimatedTotalText}`,
48652
- `Est. done ${progress.estimatedLabel}`,
48653
- ].join(' \u2502 ');
48654
- lines.push(progressSummary);
48655
- lines.push(buildProgressBar(progress.percentage));
48656
- // --- Separator ---
48657
- lines.push(sep);
48658
- // --- Current prompt ---
48659
- if (currentPromptLabel) {
48660
- const spinnerPrefix = isActive ? colors.yellow(`${spinner} `) : ' ';
48661
- lines.push(spinnerPrefix + colors.bold(currentPromptLabel));
48662
- lines.push(colors.gray(`Attempt ${currentAttempt}/${maxAttempts} \u2502 ${statusMessage}`));
49183
+ const keypressHandler = (_str, key) => {
49184
+ if (key.ctrl && key.name === 'c') {
49185
+ cleanup();
49186
+ process.exit(0);
48663
49187
  }
48664
- else {
48665
- lines.push(colors.gray(statusMessage));
48666
- }
48667
- // --- Agent output ---
48668
- if (agentOutputLines.length > 0) {
48669
- lines.push('');
48670
- lines.push(colors.gray.bold('Agent output:'));
48671
- const visibleLines = agentOutputLines.slice(-MAX_VISIBLE_OUTPUT_LINES);
48672
- for (const line of visibleLines) {
48673
- const cleanLine = stripAnsi(line);
48674
- // Truncate to terminal width.
48675
- const truncated = cleanLine.length > w - 2 ? cleanLine.slice(0, w - 5) + '...' : cleanLine;
48676
- lines.push(colors.gray(truncated));
48677
- }
48678
- }
48679
- // --- Errors ---
48680
- if (errors.length > 0) {
48681
- lines.push('');
48682
- for (const err of errors) {
48683
- lines.push(colors.red(`\u2717 ${err}`));
48684
- }
48685
- }
48686
- // --- Separator ---
48687
- lines.push(sep);
48688
- // --- Controls ---
48689
- const pauseLabel = isPaused
48690
- ? colors.bgYellow.black(' PAUSED ') + colors.gray(' [P] Resume \u2502 Ctrl+C Exit')
48691
- : colors.gray('[P] Pause \u2502 Ctrl+C Exit');
48692
- lines.push(pauseLabel);
48693
- return lines;
48694
- }
48695
- // Initial render.
49188
+ if (key.name === 'p') {
49189
+ if (getPauseState() === 'RUNNING') {
49190
+ requestPause();
49191
+ }
49192
+ else {
49193
+ requestResume();
49194
+ }
49195
+ return;
49196
+ }
49197
+ if ((key.name === 'return' || key.name === 'enter') && pendingEnterResolver) {
49198
+ const resolvePendingEnter = pendingEnterResolver;
49199
+ pendingEnterResolver = undefined;
49200
+ state.setPendingEnterLabel(undefined);
49201
+ resolvePendingEnter();
49202
+ }
49203
+ };
49204
+ process.stdin.on('keypress', keypressHandler);
48696
49205
  process.stdout.write('\n');
48697
49206
  render();
48698
49207
  const interval = setInterval(scheduleRender, UI_REFRESH_INTERVAL_MS);
48699
- // Listen for state changes and schedule a re-render (debounced).
48700
49208
  state.on('change', scheduleRender);
48701
- // --- Cleanup ---
49209
+ /**
49210
+ * Tears down the terminal UI and restores console / stdin state.
49211
+ */
48702
49212
  function cleanup() {
48703
49213
  clearInterval(interval);
48704
49214
  state.off('change', scheduleRender);
@@ -48706,12 +49216,14 @@ function renderCoderRunUi(startTime) {
48706
49216
  if (process.stdin.isTTY) {
48707
49217
  process.stdin.setRawMode(false);
48708
49218
  }
49219
+ const resolvePendingEnter = pendingEnterResolver;
49220
+ pendingEnterResolver = undefined;
49221
+ resolvePendingEnter === null || resolvePendingEnter === void 0 ? void 0 : resolvePendingEnter();
48709
49222
  isCapturing = false;
48710
49223
  console.info = originalConsoleInfo;
48711
49224
  console.warn = originalConsoleWarn;
48712
49225
  console.error = originalConsoleError;
48713
49226
  console.log = originalConsoleLog;
48714
- // Render one final frame so the user sees the last state.
48715
49227
  render();
48716
49228
  process.stdout.write('\n');
48717
49229
  }
@@ -48723,6 +49235,19 @@ function renderCoderRunUi(startTime) {
48723
49235
  stopCapturingAgentOutput() {
48724
49236
  isCapturing = false;
48725
49237
  },
49238
+ waitForEnter(actionLabel) {
49239
+ if (pendingEnterResolver) {
49240
+ throw new Error('Coder run UI is already waiting for Enter.');
49241
+ }
49242
+ state.setPendingEnterLabel(actionLabel);
49243
+ scheduleRender();
49244
+ return new Promise((resolve) => {
49245
+ pendingEnterResolver = () => {
49246
+ scheduleRender();
49247
+ resolve();
49248
+ };
49249
+ });
49250
+ },
48726
49251
  cleanup,
48727
49252
  };
48728
49253
  }
@@ -48790,11 +49315,11 @@ async function runCodexPrompts(providedOptions) {
48790
49315
  `));
48791
49316
  }
48792
49317
  const runStartDate = moment();
48793
- const isUiMode = !options.dryRun && Boolean(process.stdout.isTTY);
48794
- const progressDisplay = options.dryRun || isUiMode ? undefined : new CliProgressDisplay(runStartDate);
48795
- const uiHandle = isUiMode ? renderCoderRunUi(runStartDate) : undefined;
49318
+ const isRichUiEnabled = !options.dryRun && !options.noUi && Boolean(process.stdout.isTTY);
49319
+ const progressDisplay = options.dryRun || options.noUi || isRichUiEnabled ? undefined : new CliProgressDisplay(runStartDate, options.priority);
49320
+ const uiHandle = isRichUiEnabled ? renderCoderRunUi(runStartDate) : undefined;
48796
49321
  // When the Ink UI is active it handles keyboard input itself, so skip the raw stdin listener.
48797
- if (!isUiMode) {
49322
+ if (!isRichUiEnabled) {
48798
49323
  listenForPause();
48799
49324
  }
48800
49325
  try {
@@ -48910,17 +49435,19 @@ async function runCodexPrompts(providedOptions) {
48910
49435
  let hasWaitedForStart = false;
48911
49436
  while (just(true)) {
48912
49437
  await checkPause({
48913
- silent: isUiMode,
49438
+ silent: isRichUiEnabled,
48914
49439
  onPaused: () => {
49440
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
48915
49441
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
48916
49442
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('paused');
48917
49443
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Paused');
48918
49444
  },
48919
49445
  onResumed: () => {
49446
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
48920
49447
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
48921
49448
  },
48922
49449
  });
48923
- if (isUiMode) {
49450
+ if (isRichUiEnabled) {
48924
49451
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
48925
49452
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Loading prompts...');
48926
49453
  }
@@ -48928,17 +49455,17 @@ async function runCodexPrompts(providedOptions) {
48928
49455
  const stats = summarizePrompts(promptFiles, options.priority);
48929
49456
  progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.update(stats);
48930
49457
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.updateProgress(stats);
48931
- if (!isUiMode) {
49458
+ if (!isRichUiEnabled) {
48932
49459
  printStats(stats, options.priority);
48933
49460
  }
48934
49461
  const nextPrompt = findNextTodoPrompt(promptFiles, options.priority);
48935
49462
  if (!hasShownUpcomingTasks) {
48936
- if (stats.toBeWritten > 0 && !isUiMode) {
49463
+ if (stats.toBeWritten > 0 && !isRichUiEnabled) {
48937
49464
  console.info(colors.yellow('Following prompts need to be written:'));
48938
49465
  printPromptsToBeWritten(promptFiles, options.priority);
48939
49466
  console.info('');
48940
49467
  }
48941
- if (!isUiMode) {
49468
+ if (!isRichUiEnabled) {
48942
49469
  printUpcomingTasks(listUpcomingTasks(promptFiles, options.priority));
48943
49470
  }
48944
49471
  hasShownUpcomingTasks = true;
@@ -48948,7 +49475,7 @@ async function runCodexPrompts(providedOptions) {
48948
49475
  const message = 'No prompts ready for agent.';
48949
49476
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
48950
49477
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
48951
- if (!isUiMode) {
49478
+ if (!isRichUiEnabled) {
48952
49479
  console.info(colors.yellow(message));
48953
49480
  }
48954
49481
  }
@@ -48956,16 +49483,28 @@ async function runCodexPrompts(providedOptions) {
48956
49483
  const message = 'All prompts are done.';
48957
49484
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
48958
49485
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
48959
- if (!isUiMode) {
49486
+ if (!isRichUiEnabled) {
48960
49487
  console.info(colors.green(message));
48961
49488
  }
48962
49489
  }
48963
49490
  return;
48964
49491
  }
49492
+ const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
48965
49493
  if (options.waitForUser) {
49494
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
48966
49495
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
48967
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(hasWaitedForStart ? 'Waiting... Press Enter to continue' : 'Waiting... Press Enter to start');
48968
- await waitForPromptStart(nextPrompt.file, nextPrompt.section, !hasWaitedForStart);
49496
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
49497
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
49498
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(hasWaitedForStart ? 'Waiting for confirmation to continue' : 'Waiting for confirmation to start');
49499
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines([buildPromptSummary(nextPrompt.file, nextPrompt.section)]);
49500
+ if (isRichUiEnabled) {
49501
+ await (uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.waitForEnter(hasWaitedForStart ? 'Continue' : 'Start'));
49502
+ }
49503
+ else {
49504
+ await waitForPromptStart(nextPrompt.file, nextPrompt.section, !hasWaitedForStart);
49505
+ }
49506
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines([]);
49507
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
48969
49508
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
48970
49509
  hasWaitedForStart = true;
48971
49510
  }
@@ -48975,8 +49514,7 @@ async function runCodexPrompts(providedOptions) {
48975
49514
  const commitMessage = buildCommitMessage(nextPrompt.file, nextPrompt.section);
48976
49515
  const codexPrompt = appendCoderContext(buildCodexPrompt(nextPrompt.file, nextPrompt.section), resolvedCoderContext);
48977
49516
  const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
48978
- const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
48979
- if (isUiMode) {
49517
+ if (isRichUiEnabled) {
48980
49518
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
48981
49519
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('running');
48982
49520
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Running');
@@ -48989,55 +49527,71 @@ async function runCodexPrompts(providedOptions) {
48989
49527
  const roundChangedFilesSnapshot = options.normalizeLineEndings
48990
49528
  ? await captureChangedFilesSnapshot(process.cwd())
48991
49529
  : undefined;
48992
- try {
48993
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
48994
- const result = await runPromptWithTestFeedback({
48995
- runner,
48996
- prompt: codexPrompt,
48997
- scriptPath,
48998
- projectPath: process.cwd(),
48999
- promptLabel,
49000
- testCommand: options.testCommand,
49001
- onAttemptStarted: (nextAttemptCount) => {
49002
- attemptCount = nextAttemptCount;
49003
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(nextAttemptCount);
49004
- if (nextAttemptCount > 1) {
49005
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Retrying (attempt ${nextAttemptCount})`);
49006
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('verifying');
49530
+ await withPromptRuntimeLog(scriptPath, async (logPath) => {
49531
+ try {
49532
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
49533
+ const result = await runPromptWithTestFeedback({
49534
+ runner,
49535
+ prompt: codexPrompt,
49536
+ scriptPath,
49537
+ projectPath: process.cwd(),
49538
+ promptLabel,
49539
+ testCommand: options.testCommand,
49540
+ preserveArtifactsOnSuccess: options.preserveLogs,
49541
+ logPath,
49542
+ onAttemptStarted: (nextAttemptCount) => {
49543
+ attemptCount = nextAttemptCount;
49544
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(nextAttemptCount);
49545
+ if (nextAttemptCount > 1) {
49546
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Retrying (attempt ${nextAttemptCount})`);
49547
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('verifying');
49548
+ }
49549
+ },
49550
+ });
49551
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49552
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Committing changes');
49553
+ markPromptDone(nextPrompt.file, nextPrompt.section, result.usage, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, result.attemptCount);
49554
+ await writePromptFile(nextPrompt.file);
49555
+ await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49556
+ if (options.waitForUser) {
49557
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.pauseTimer();
49558
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49559
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
49560
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Review the commit preview and confirm to continue');
49561
+ if (isRichUiEnabled) {
49562
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines(formatCommitMessageForDisplay(commitMessage)
49563
+ .split(/\r?\n/)
49564
+ .map((line) => line.trim()));
49565
+ await (uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.waitForEnter('Commit'));
49566
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setDetailLines([]);
49007
49567
  }
49008
- },
49009
- });
49010
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49011
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Committing changes');
49012
- markPromptDone(nextPrompt.file, nextPrompt.section, result.usage, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, result.attemptCount);
49013
- await writePromptFile(nextPrompt.file);
49014
- await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49015
- if (options.waitForUser) {
49016
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49017
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Waiting... Press Enter to commit');
49018
- printCommitMessage(commitMessage);
49019
- await waitForEnter(colors.bgWhite('Press Enter to commit and continue...'));
49020
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49568
+ else {
49569
+ printCommitMessage(commitMessage);
49570
+ await waitForEnter(colors.bgWhite('Press Enter to commit and continue...'));
49571
+ }
49572
+ progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.resumeTimer();
49573
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
49574
+ }
49575
+ await commitChanges(commitMessage, { autoPush: options.autoPush });
49576
+ await runPostPromptAutoMigrationIfEnabled(options);
49021
49577
  }
49022
- await commitChanges(commitMessage, { autoPush: options.autoPush });
49023
- await runPostPromptAutoMigrationIfEnabled(options);
49024
- }
49025
- catch (error) {
49026
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49027
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
49028
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
49029
- markPromptFailed(nextPrompt.file, nextPrompt.section, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, attemptCount);
49030
- await writePromptFile(nextPrompt.file);
49031
- await writePromptErrorLog({
49032
- file: nextPrompt.file,
49033
- section: nextPrompt.section,
49034
- runnerName: runnerMetadata.runnerName,
49035
- modelName: runnerMetadata.modelName,
49036
- error,
49037
- });
49038
- await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49039
- throw error;
49040
- }
49578
+ catch (error) {
49579
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49580
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
49581
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
49582
+ markPromptFailed(nextPrompt.file, nextPrompt.section, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, attemptCount);
49583
+ await writePromptFile(nextPrompt.file);
49584
+ await writePromptErrorLog({
49585
+ file: nextPrompt.file,
49586
+ section: nextPrompt.section,
49587
+ runnerName: runnerMetadata.runnerName,
49588
+ modelName: runnerMetadata.modelName,
49589
+ error,
49590
+ });
49591
+ await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
49592
+ throw error;
49593
+ }
49594
+ }, { preserveArtifactsOnSuccess: options.preserveLogs });
49041
49595
  }
49042
49596
  }
49043
49597
  finally {