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