@promptbook/cli 0.112.0-41 → 0.112.0-43

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 (58) hide show
  1. package/README.md +10 -3
  2. package/esm/apps/agents-server/src/database/acquireMigrationExecutionLock.d.ts +12 -1
  3. package/esm/apps/agents-server/src/database/runDatabaseMigrations.d.ts +9 -0
  4. package/esm/index.es.js +1218 -114
  5. package/esm/index.es.js.map +1 -1
  6. package/esm/scripts/run-codex-prompts/common/formatUnknownErrorDetails.d.ts +4 -0
  7. package/esm/scripts/run-codex-prompts/common/waitForPause.d.ts +21 -1
  8. package/esm/scripts/run-codex-prompts/git/commitChanges.d.ts +2 -2
  9. package/esm/scripts/run-codex-prompts/prompts/formatPromptAttemptMetadata.d.ts +4 -0
  10. package/esm/scripts/run-codex-prompts/prompts/markPromptDone.d.ts +1 -1
  11. package/esm/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +1 -1
  12. package/esm/scripts/run-codex-prompts/testing/runPromptTestCommand.d.ts +13 -0
  13. package/esm/scripts/run-codex-prompts/testing/runPromptWithTestFeedback.d.ts +25 -0
  14. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +112 -0
  15. package/esm/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +30 -0
  16. package/esm/scripts/verify-prompts/verify-prompts.d.ts +23 -2
  17. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +1 -1
  18. package/esm/src/cli/cli-commands/coder/getTypescriptModule.d.ts +19 -0
  19. package/esm/src/cli/cli-commands/coder/getTypescriptModule.test.d.ts +1 -0
  20. package/esm/src/cli/cli-commands/coder/mergeStringRecordJsonFile.test.d.ts +1 -0
  21. package/esm/src/cli/cli-commands/coder/verify.test.d.ts +1 -0
  22. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +2 -14
  23. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/createAgentPersistenceRecords.d.ts +40 -0
  24. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/createAgentPersistenceRecords.test.d.ts +1 -0
  25. package/esm/src/llm-providers/agent/Agent.test.d.ts +1 -0
  26. package/esm/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +4 -0
  27. package/esm/src/llm-providers/agent/AgentOptions.d.ts +8 -0
  28. package/esm/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +9 -0
  29. package/esm/src/version.d.ts +1 -1
  30. package/package.json +3 -2
  31. package/umd/apps/agents-server/src/database/acquireMigrationExecutionLock.d.ts +12 -1
  32. package/umd/apps/agents-server/src/database/runDatabaseMigrations.d.ts +9 -0
  33. package/umd/index.umd.js +1221 -118
  34. package/umd/index.umd.js.map +1 -1
  35. package/umd/scripts/run-codex-prompts/common/formatUnknownErrorDetails.d.ts +4 -0
  36. package/umd/scripts/run-codex-prompts/common/waitForPause.d.ts +21 -1
  37. package/umd/scripts/run-codex-prompts/git/commitChanges.d.ts +2 -2
  38. package/umd/scripts/run-codex-prompts/prompts/formatPromptAttemptMetadata.d.ts +4 -0
  39. package/umd/scripts/run-codex-prompts/prompts/markPromptDone.d.ts +1 -1
  40. package/umd/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +1 -1
  41. package/umd/scripts/run-codex-prompts/testing/runPromptTestCommand.d.ts +13 -0
  42. package/umd/scripts/run-codex-prompts/testing/runPromptWithTestFeedback.d.ts +25 -0
  43. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +112 -0
  44. package/umd/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +30 -0
  45. package/umd/scripts/verify-prompts/verify-prompts.d.ts +23 -2
  46. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +1 -1
  47. package/umd/src/cli/cli-commands/coder/getTypescriptModule.d.ts +19 -0
  48. package/umd/src/cli/cli-commands/coder/getTypescriptModule.test.d.ts +1 -0
  49. package/umd/src/cli/cli-commands/coder/mergeStringRecordJsonFile.test.d.ts +1 -0
  50. package/umd/src/cli/cli-commands/coder/verify.test.d.ts +1 -0
  51. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +2 -14
  52. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/createAgentPersistenceRecords.d.ts +40 -0
  53. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/createAgentPersistenceRecords.test.d.ts +1 -0
  54. package/umd/src/llm-providers/agent/Agent.test.d.ts +1 -0
  55. package/umd/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +4 -0
  56. package/umd/src/llm-providers/agent/AgentOptions.d.ts +8 -0
  57. package/umd/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +9 -0
  58. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -37,6 +37,7 @@ import { spawn } from 'child_process';
37
37
  import { Client } from 'pg';
38
38
  import '@supabase/supabase-js';
39
39
  import { dirname as dirname$1 } from 'path/posix';
40
+ import { EventEmitter } from 'events';
40
41
  import { Subject, BehaviorSubject } from 'rxjs';
41
42
  import { lookup, extension } from 'mime-types';
42
43
  import { parse, unparse } from 'papaparse';
@@ -57,7 +58,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
57
58
  * @generated
58
59
  * @see https://github.com/webgptorg/promptbook
59
60
  */
60
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-41';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-43';
61
62
  /**
62
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
63
64
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2320,7 +2321,7 @@ function getDefaultCoderAgentCodingFileContent({ packageJsonScripts, }) {
2320
2321
  '',
2321
2322
  '## Customizing the workflow',
2322
2323
  '- Edit `package.json` if you want `npm run coder:run` to use another coding agent, model, thinking level, context file, or wait mode.',
2323
- '- Use direct CLI commands when you need one-off flags such as `--priority`, `--ignore-git-changes`, `--dry-run`, `--allow-credits`, or `--auto-migrate`.',
2324
+ '- Use direct CLI commands when you need one-off flags such as `--priority`, `--ignore-git-changes`, `--dry-run`, `--test`, `--allow-credits`, or `--auto-migrate`.',
2324
2325
  '- Use `npx ptbk coder --help` and `npx ptbk coder <command> --help` for the full CLI reference.',
2325
2326
  ].join('\n');
2326
2327
  }
@@ -2580,6 +2581,26 @@ class ParseError extends Error {
2580
2581
  }
2581
2582
  // TODO: Maybe split `ParseError` and `ApplyError`
2582
2583
 
2584
+ /**
2585
+ * Loads the TypeScript runtime used for parsing JSONC-style project files.
2586
+ *
2587
+ * @private internal utility of `coder init`
2588
+ */
2589
+ async function getTypescriptModule() {
2590
+ return normalizeImportedTypescriptModule((await import('typescript')));
2591
+ }
2592
+ /**
2593
+ * Normalizes CommonJS-via-`default` and direct namespace imports of TypeScript.
2594
+ *
2595
+ * @private internal utility of `getTypescriptModule`
2596
+ */
2597
+ function normalizeImportedTypescriptModule(importedTypescriptModule) {
2598
+ return 'parseConfigFileTextToJson' in importedTypescriptModule
2599
+ ? importedTypescriptModule
2600
+ : importedTypescriptModule.default;
2601
+ }
2602
+ // Note: [🟡] Code for coder init TypeScript loading [getTypescriptModule](src/cli/cli-commands/coder/getTypescriptModule.ts) should never be published outside of `@promptbook/cli`
2603
+
2583
2604
  /**
2584
2605
  * Default indentation used when creating new JSON configuration files.
2585
2606
  */
@@ -2625,7 +2646,7 @@ async function parseJsonObjectFile(relativeFilePath, fileContent) {
2625
2646
  if (fileContent.trim() === '') {
2626
2647
  return {};
2627
2648
  }
2628
- const typescript = await import('typescript');
2649
+ const typescript = await getTypescriptModule();
2629
2650
  const parsedFile = typescript.parseConfigFileTextToJson(relativeFilePath, fileContent);
2630
2651
  if (parsedFile.error) {
2631
2652
  throw new ParseError(spaceTrim$1(`
@@ -2983,7 +3004,9 @@ function $initializeCoderRunCommand(program) {
2983
3004
 
2984
3005
  Features:
2985
3006
  - Automatically stages and commits changes with agent identity
3007
+ - Optional post-commit git push with explicit --auto-push opt-in
2986
3008
  - Supports GPG signing of commits
3009
+ - Optional post-prompt verification with test-feedback retries
2987
3010
  - Progress tracking and interactive controls
2988
3011
  - Dry-run mode to preview prompts
2989
3012
  `));
@@ -2996,17 +3019,19 @@ function $initializeCoderRunCommand(program) {
2996
3019
  Gemini examples: gemini-3-flash-preview, default
2997
3020
  `));
2998
3021
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
3022
+ command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
2999
3023
  command.addOption(new Option('--thinking-level <thinking-level>', `Set reasoning effort for supported runners (${THINKING_LEVEL_VALUES.join(', ')})`).choices([...THINKING_LEVEL_VALUES]));
3000
3024
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
3001
3025
  command.option('--no-wait', 'Skip user prompts between processing');
3002
3026
  command.option('--ignore-git-changes', 'Skip clean working tree check before running prompts', false);
3003
3027
  command.option('--allow-credits', 'Allow OpenAI Codex runner to spend credits when rate limits are exhausted', false);
3004
3028
  command.option('--no-normalize-line-endings', 'Disable automatic LF normalization for files changed in each coding round');
3005
- command.option('--no-push', 'Disable automatic git push after each commit');
3029
+ command.option('--auto-push', 'Automatically git push after each commit', false);
3006
3030
  command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
3007
3031
  command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
3008
3032
  command.action(handleActionErrors(async (cliOptions) => {
3009
- const { dryRun, agent, model, context, thinkingLevel, priority, wait, ignoreGitChanges, allowCredits, normalizeLineEndings, autoMigrate, allowDestructiveAutoMigrate, push, } = cliOptions;
3033
+ const { dryRun, agent, model, context, test, thinkingLevel, priority, wait, ignoreGitChanges, allowCredits, normalizeLineEndings, autoMigrate, allowDestructiveAutoMigrate, autoPush, } = cliOptions;
3034
+ const testCommand = normalizeCommandOptionValue(test);
3010
3035
  // Validate agent
3011
3036
  let agentName = undefined;
3012
3037
  if (agent) {
@@ -3035,13 +3060,14 @@ function $initializeCoderRunCommand(program) {
3035
3060
  agentName,
3036
3061
  model,
3037
3062
  context,
3063
+ testCommand,
3038
3064
  thinkingLevel,
3039
3065
  priority,
3040
3066
  normalizeLineEndings,
3041
3067
  allowCredits,
3042
3068
  autoMigrate,
3043
3069
  allowDestructiveAutoMigrate,
3044
- noPush: !push,
3070
+ autoPush,
3045
3071
  };
3046
3072
  // Note: Import the function dynamically to avoid loading heavy dependencies until needed
3047
3073
  const { runCodexPrompts } = await Promise.resolve().then(function () { return runCodexPrompts$1; });
@@ -3070,6 +3096,19 @@ function parseIntOption(value) {
3070
3096
  }
3071
3097
  return parsed;
3072
3098
  }
3099
+ /**
3100
+ * Joins one Commander option that may be parsed either as a single string or a variadic token array.
3101
+ *
3102
+ * @private internal utility of `coder run` command
3103
+ */
3104
+ function normalizeCommandOptionValue(value) {
3105
+ if (value === undefined) {
3106
+ return undefined;
3107
+ }
3108
+ const parts = Array.isArray(value) ? value : [value];
3109
+ const normalizedValue = parts.map((part) => part.trim()).filter(Boolean).join(' ').trim();
3110
+ return normalizedValue === '' ? undefined : normalizedValue;
3111
+ }
3073
3112
  // Note: [🟡] Code for CLI command [run](src/cli/cli-commands/coder/run.ts) should never be published outside of `@promptbook/cli`
3074
3113
  // Note: [💞] Ignore a discrepancy between file name and entity name
3075
3114
 
@@ -3091,14 +3130,16 @@ function $initializeCoderVerifyCommand(program) {
3091
3130
  - Archives verified prompt files to prompts/done/ directory
3092
3131
  - Auto-appends repair prompts for incomplete work
3093
3132
  - Processes files with all-done prompts first
3133
+ - Supports ignoring matching prompt candidates for one verification run
3094
3134
  `));
3095
3135
  command.option('--reverse', 'Process prompt files in reverse order', false);
3136
+ command.option('--ignore <candidate-text>', 'Ignore prompt files whose filename or first prompt line contains the given text (repeatable)', collectStringOption, []);
3096
3137
  command.action(handleActionErrors(async (cliOptions) => {
3097
- const { reverse } = cliOptions;
3138
+ const { reverse, ignore } = cliOptions;
3098
3139
  // Note: Import the main function dynamically to avoid loading heavy dependencies until needed
3099
3140
  const { verifyPrompts } = await Promise.resolve().then(function () { return verifyPrompts$1; });
3100
3141
  try {
3101
- await verifyPrompts(reverse);
3142
+ await verifyPrompts({ reverse, ignore });
3102
3143
  }
3103
3144
  catch (error) {
3104
3145
  console.error(colors.bgRed('Prompt verification failed:'), error);
@@ -3107,6 +3148,14 @@ function $initializeCoderVerifyCommand(program) {
3107
3148
  return process.exit(0);
3108
3149
  }));
3109
3150
  }
3151
+ /**
3152
+ * Collects repeatable string options from Commander.
3153
+ *
3154
+ * @private internal utility of `coder verify` command
3155
+ */
3156
+ function collectStringOption(value, previousValues) {
3157
+ return [...previousValues, value];
3158
+ }
3110
3159
  // Note: [🟡] Code for CLI command [verify](src/cli/cli-commands/coder/verify.ts) should never be published outside of `@promptbook/cli`
3111
3160
  // Note: [💞] Ignore a discrepancy between file name and entity name
3112
3161
 
@@ -22546,6 +22595,15 @@ const teamToolFunctions = {};
22546
22595
  * Map of team tool titles.
22547
22596
  */
22548
22597
  const teamToolTitles = {};
22598
+ /**
22599
+ * Shared TEAM usage rules appended ahead of teammate listings.
22600
+ *
22601
+ * @private
22602
+ */
22603
+ const TEAM_SYSTEM_MESSAGE_GUIDANCE_LINES = [
22604
+ '- If a teammate is relevant to the request, consult that teammate using the matching tool.',
22605
+ '- Do not ask the user for information that a listed teammate can provide directly.',
22606
+ ];
22549
22607
  /**
22550
22608
  * Constant for remote agents by Url.
22551
22609
  */
@@ -22635,12 +22693,9 @@ class TeamCommitmentDefinition extends BaseCommitmentDefinition {
22635
22693
  if (updatedTools.some((tool) => tool.name === entry.toolName)) {
22636
22694
  continue;
22637
22695
  }
22638
- const toolDescription = entry.description
22639
- ? `Consult teammate ${entry.teammate.label}\n${entry.description}`
22640
- : `Consult teammate ${entry.teammate.label}`;
22641
22696
  updatedTools.push({
22642
22697
  name: entry.toolName,
22643
- description: toolDescription,
22698
+ description: buildTeamToolDescription(entry),
22644
22699
  parameters: {
22645
22700
  type: 'object',
22646
22701
  properties: {
@@ -22709,22 +22764,72 @@ function resolveTeamTeammateLabels(teamContent, teammates) {
22709
22764
  /**
22710
22765
  * Builds the textual TEAM section body for the final system message.
22711
22766
  *
22712
- * Each teammate is listed with its tool name and, when available, a one-line description.
22713
- * Uses `spaceTrim` to ensure consistent whitespace and indentation.
22767
+ * Each teammate is listed with its tool name, TEAM instructions, and optional profile hints.
22714
22768
  */
22715
22769
  function buildTeamSystemMessageBody(teamEntries) {
22716
- const lines = teamEntries.map((entry, index) => {
22717
- const toolLine = `${index + 1}) ${entry.teammate.label} tool \`${entry.toolName}\``;
22718
- if (!entry.description) {
22719
- return toolLine;
22720
- }
22721
- return spaceTrim$1(`
22722
- ${toolLine}
22723
- ${entry.description}
22724
- `);
22725
- });
22770
+ const lines = [
22771
+ ...TEAM_SYSTEM_MESSAGE_GUIDANCE_LINES,
22772
+ '',
22773
+ ...teamEntries.map((entry, index) => {
22774
+ const toolLine = `${index + 1}) ${entry.teammate.label} tool \`${entry.toolName}\``;
22775
+ const detailLines = collectTeamEntryDetails(entry).map(formatTeamEntryDetailLine);
22776
+ return [toolLine, ...detailLines].join('\n');
22777
+ }),
22778
+ ];
22726
22779
  return lines.join('\n');
22727
22780
  }
22781
+ /**
22782
+ * Builds the model-visible description for one teammate tool.
22783
+ *
22784
+ * @private
22785
+ */
22786
+ function buildTeamToolDescription(entry) {
22787
+ const detailLines = collectTeamEntryDetails(entry).map(({ label, content }) => `${label}: ${content}`);
22788
+ return [`Consult teammate ${entry.teammate.label}`, ...detailLines].join('\n');
22789
+ }
22790
+ /**
22791
+ * Collects structured teammate details that should stay visible to the model.
22792
+ *
22793
+ * @private
22794
+ */
22795
+ function collectTeamEntryDetails(entry) {
22796
+ var _a;
22797
+ const details = [];
22798
+ const instructions = entry.teammate.instructions.trim();
22799
+ const description = ((_a = entry.description) === null || _a === void 0 ? void 0 : _a.trim()) || '';
22800
+ if (instructions) {
22801
+ details.push({
22802
+ label: 'TEAM instructions',
22803
+ content: instructions,
22804
+ });
22805
+ }
22806
+ if (description) {
22807
+ details.push({
22808
+ label: 'Profile',
22809
+ content: description,
22810
+ });
22811
+ }
22812
+ return details;
22813
+ }
22814
+ /**
22815
+ * Formats one teammate detail line for the TEAM system-message section.
22816
+ *
22817
+ * @private
22818
+ */
22819
+ function formatTeamEntryDetailLine(detail) {
22820
+ return indentMultilineText(`${detail.label}: ${detail.content}`, ' ');
22821
+ }
22822
+ /**
22823
+ * Indents all lines of one potentially multi-line text block.
22824
+ *
22825
+ * @private
22826
+ */
22827
+ function indentMultilineText(text, prefix) {
22828
+ return text
22829
+ .split('\n')
22830
+ .map((line) => `${prefix}${line}`)
22831
+ .join('\n');
22832
+ }
22728
22833
  /**
22729
22834
  * Registers tool function and title for a teammate tool.
22730
22835
  */
@@ -40897,7 +41002,7 @@ function createEmptyAgentModelRequirements() {
40897
41002
  systemMessage: '',
40898
41003
  promptSuffix: '',
40899
41004
  // modelName: 'gpt-5',
40900
- modelName: 'gemini-2.5-flash-lite',
41005
+ modelName: 'gpt-5.4-mini',
40901
41006
  temperature: 0.7,
40902
41007
  topP: 0.9,
40903
41008
  topK: 50,
@@ -44077,7 +44182,26 @@ var findRefactorCandidates$1 = /*#__PURE__*/Object.freeze({
44077
44182
  /**
44078
44183
  * CLI usage text for this script.
44079
44184
  */
44080
- const USAGE = 'Usage: run-codex-prompts [--dry-run] [--agent <agent-name>] [--model <model>] [--context <context-or-file>] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--no-wait] [--ignore-git-changes] [--no-normalize-line-endings] [--no-push]';
44185
+ const USAGE = 'Usage: run-codex-prompts [--dry-run] [--agent <agent-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--no-wait] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push]';
44186
+ /**
44187
+ * Top-level flags supported by this command.
44188
+ */
44189
+ const KNOWN_OPTION_FLAGS = new Set([
44190
+ '--dry-run',
44191
+ '--agent',
44192
+ '--model',
44193
+ '--context',
44194
+ '--test',
44195
+ '--thinking-level',
44196
+ '--priority',
44197
+ '--allow-credits',
44198
+ '--auto-migrate',
44199
+ '--allow-destructive-auto-migrate',
44200
+ '--no-wait',
44201
+ '--ignore-git-changes',
44202
+ '--no-normalize-line-endings',
44203
+ '--auto-push',
44204
+ ]);
44081
44205
  /**
44082
44206
  * Parses CLI arguments into runner options.
44083
44207
  */
@@ -44098,6 +44222,8 @@ function parseRunOptions(args) {
44098
44222
  }
44099
44223
  const model = readOptionValue(args, '--model');
44100
44224
  const context = readOptionValue(args, '--context');
44225
+ const hasTestCommandFlag = args.includes('--test');
44226
+ const testCommand = readVariadicOptionValue(args, '--test');
44101
44227
  const hasThinkingLevelFlag = args.includes('--thinking-level');
44102
44228
  const thinkingLevelValue = readOptionValue(args, '--thinking-level');
44103
44229
  const hasPriorityFlag = args.includes('--priority');
@@ -44107,8 +44233,11 @@ function parseRunOptions(args) {
44107
44233
  const allowCredits = args.includes('--allow-credits');
44108
44234
  const autoMigrate = args.includes('--auto-migrate');
44109
44235
  const allowDestructiveAutoMigrate = args.includes('--allow-destructive-auto-migrate');
44110
- const noPush = args.includes('--no-push');
44236
+ const autoPush = args.includes('--auto-push');
44111
44237
  let thinkingLevel;
44238
+ if (hasTestCommandFlag && testCommand === undefined) {
44239
+ exitWithUsageError('Missing value for --test. Use a shell command such as `npm run test` and quote it when it contains top-level CLI flags.');
44240
+ }
44112
44241
  if (hasThinkingLevelFlag && thinkingLevelValue === undefined) {
44113
44242
  exitWithUsageError(`Missing value for --thinking-level. Use one of: ${THINKING_LEVEL_VALUES.join(', ')}.`);
44114
44243
  }
@@ -44129,10 +44258,11 @@ function parseRunOptions(args) {
44129
44258
  allowCredits,
44130
44259
  autoMigrate,
44131
44260
  allowDestructiveAutoMigrate,
44132
- noPush,
44261
+ autoPush,
44133
44262
  agentName,
44134
44263
  model,
44135
44264
  context,
44265
+ testCommand,
44136
44266
  thinkingLevel,
44137
44267
  priority,
44138
44268
  };
@@ -44147,6 +44277,28 @@ function readOptionValue(args, flag) {
44147
44277
  const index = args.indexOf(flag);
44148
44278
  return args[index + 1];
44149
44279
  }
44280
+ /**
44281
+ * Reads a multi-token shell command value that follows a given flag.
44282
+ */
44283
+ function readVariadicOptionValue(args, flag) {
44284
+ if (!args.includes(flag)) {
44285
+ return undefined;
44286
+ }
44287
+ const index = args.indexOf(flag);
44288
+ const valueParts = [];
44289
+ for (let i = index + 1; i < args.length; i++) {
44290
+ const valuePart = args[i];
44291
+ if (valuePart === undefined) {
44292
+ continue;
44293
+ }
44294
+ if (KNOWN_OPTION_FLAGS.has(valuePart)) {
44295
+ break;
44296
+ }
44297
+ valueParts.push(valuePart);
44298
+ }
44299
+ const normalizedValue = valueParts.join(' ').trim();
44300
+ return normalizedValue === '' ? undefined : normalizedValue;
44301
+ }
44150
44302
  /**
44151
44303
  * Parses and validates the minimum prompt priority.
44152
44304
  */
@@ -44172,6 +44324,21 @@ function exitWithUsageError(message) {
44172
44324
  process.exit(1);
44173
44325
  }
44174
44326
 
44327
+ /**
44328
+ * Appends optional coding context to a runner prompt.
44329
+ */
44330
+ function appendCoderContext(prompt, context) {
44331
+ const normalizedContext = context === null || context === void 0 ? void 0 : context.trim();
44332
+ if (!normalizedContext) {
44333
+ return prompt;
44334
+ }
44335
+ const normalizedPrompt = prompt.trimEnd();
44336
+ if (normalizedPrompt === '') {
44337
+ return normalizedContext;
44338
+ }
44339
+ return `${normalizedPrompt}\n\n${normalizedContext}`;
44340
+ }
44341
+
44175
44342
  /**
44176
44343
  * Refresh interval for the progress header in milliseconds.
44177
44344
  */
@@ -44183,7 +44350,7 @@ const PROGRESS_HEADER_RESERVED_LINES = 1;
44183
44350
  /**
44184
44351
  * Calendar formats used when displaying the estimated completion time.
44185
44352
  */
44186
- const ESTIMATED_DONE_CALENDAR_FORMATS = {
44353
+ const ESTIMATED_DONE_CALENDAR_FORMATS$1 = {
44187
44354
  sameDay: '[Today] h:mm',
44188
44355
  nextDay: '[Tomorrow] h:mm',
44189
44356
  nextWeek: 'dddd h:mm',
@@ -44282,15 +44449,15 @@ function buildProgressSnapshot(stats, startTime, initialDone) {
44282
44449
  const sessionTotal = sessionDone + stats.forAgent;
44283
44450
  const percentage = totalPrompts > 0 ? Math.round((completedPrompts / totalPrompts) * 100) : 0;
44284
44451
  const elapsedDuration = moment.duration(moment().diff(startTime));
44285
- const elapsedText = formatDurationBrief(elapsedDuration);
44452
+ const elapsedText = formatDurationBrief$1(elapsedDuration);
44286
44453
  let estimatedTotalText = '—';
44287
44454
  let estimatedLabel = 'unknown';
44288
44455
  if (totalPrompts > 0 && completedPrompts > 0) {
44289
44456
  const estimatedTotalMs = (elapsedDuration.asMilliseconds() * totalPrompts) / completedPrompts;
44290
44457
  const estimatedTotalDuration = moment.duration(estimatedTotalMs);
44291
44458
  const estimatedCompletion = startTime.clone().add(estimatedTotalDuration);
44292
- estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
44293
- estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
44459
+ estimatedTotalText = formatDurationBrief$1(estimatedTotalDuration);
44460
+ estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS$1);
44294
44461
  }
44295
44462
  return {
44296
44463
  totalPrompts,
@@ -44306,7 +44473,7 @@ function buildProgressSnapshot(stats, startTime, initialDone) {
44306
44473
  /**
44307
44474
  * Formats a duration into a compact string such as "3h 12m" or "45s".
44308
44475
  */
44309
- function formatDurationBrief(duration) {
44476
+ function formatDurationBrief$1(duration) {
44310
44477
  const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
44311
44478
  const hours = Math.floor(totalSeconds / 3600);
44312
44479
  const minutes = Math.floor((totalSeconds % 3600) / 60);
@@ -44327,21 +44494,6 @@ function formatDurationBrief(duration) {
44327
44494
  return parts.join(' ');
44328
44495
  }
44329
44496
 
44330
- /**
44331
- * Appends optional coding context to a runner prompt.
44332
- */
44333
- function appendCoderContext(prompt, context) {
44334
- const normalizedContext = context === null || context === void 0 ? void 0 : context.trim();
44335
- if (!normalizedContext) {
44336
- return prompt;
44337
- }
44338
- const normalizedPrompt = prompt.trimEnd();
44339
- if (normalizedPrompt === '') {
44340
- return normalizedContext;
44341
- }
44342
- return `${normalizedPrompt}\n\n${normalizedContext}`;
44343
- }
44344
-
44345
44497
  /**
44346
44498
  * Git commands used to list changed and untracked files in the working tree.
44347
44499
  */
@@ -44611,7 +44763,7 @@ async function waitForEnter(prompt) {
44611
44763
  }
44612
44764
 
44613
44765
  /**
44614
- * Constant for pause state.
44766
+ * Current pause state.
44615
44767
  */
44616
44768
  let pauseState = 'RUNNING';
44617
44769
  /**
@@ -44648,17 +44800,48 @@ function listenForPause() {
44648
44800
  }
44649
44801
  /**
44650
44802
  * If the execution is paused, it will wait until it is resumed.
44803
+ *
44804
+ * @param options.silent - When `true`, suppresses console output (used when the terminal UI handles display).
44805
+ * @param options.onPaused - Callback invoked when entering the PAUSED state.
44806
+ * @param options.onResumed - Callback invoked when leaving the PAUSED state.
44651
44807
  */
44652
- async function checkPause() {
44808
+ async function checkPause(options) {
44809
+ var _a, _b;
44653
44810
  if (pauseState === 'PAUSING') {
44654
44811
  pauseState = 'PAUSED';
44655
- console.log(colors.bgWhite.black('Paused') + colors.gray(' (Press "p" to resume)'));
44812
+ if (!(options === null || options === void 0 ? void 0 : options.silent)) {
44813
+ console.log(colors.bgWhite.black('Paused') + colors.gray(' (Press "p" to resume)'));
44814
+ }
44815
+ (_a = options === null || options === void 0 ? void 0 : options.onPaused) === null || _a === void 0 ? void 0 : _a.call(options);
44656
44816
  while (pauseState === 'PAUSED') {
44657
44817
  await new Promise((resolve) => setTimeout(resolve, 100));
44658
44818
  }
44659
- console.log(colors.green('Resuming...'));
44819
+ (_b = options === null || options === void 0 ? void 0 : options.onResumed) === null || _b === void 0 ? void 0 : _b.call(options);
44820
+ if (!(options === null || options === void 0 ? void 0 : options.silent)) {
44821
+ console.log(colors.green('Resuming...'));
44822
+ }
44660
44823
  }
44661
44824
  }
44825
+ /**
44826
+ * Returns the current pause state for external consumers such as the terminal UI.
44827
+ */
44828
+ function getPauseState() {
44829
+ return pauseState;
44830
+ }
44831
+ /**
44832
+ * Requests a pause from an external controller (e.g. the Ink UI).
44833
+ */
44834
+ function requestPause() {
44835
+ if (pauseState === 'RUNNING') {
44836
+ pauseState = 'PAUSING';
44837
+ }
44838
+ }
44839
+ /**
44840
+ * Resumes execution from an external controller after a pause.
44841
+ */
44842
+ function requestResume() {
44843
+ pauseState = 'RUNNING';
44844
+ }
44662
44845
 
44663
44846
  /**
44664
44847
  * Environment variable that configures the name used for agent commits.
@@ -45003,7 +45186,7 @@ function stringifyUnknownError$1(error) {
45003
45186
 
45004
45187
  /**
45005
45188
  * Commits staged changes with the provided message using the dedicated coding-agent identity when configured,
45006
- * otherwise falls back to the default Git configuration.
45189
+ * otherwise falls back to the default Git configuration. Remote pushing is opt-in via `options.autoPush`.
45007
45190
  */
45008
45191
  async function commitChanges(message, options) {
45009
45192
  const projectPath = process.cwd();
@@ -45023,7 +45206,7 @@ async function commitChanges(message, options) {
45023
45206
  cwd: projectPath,
45024
45207
  env: agentEnv,
45025
45208
  });
45026
- if (!(options === null || options === void 0 ? void 0 : options.noPush)) {
45209
+ if (options === null || options === void 0 ? void 0 : options.autoPush) {
45027
45210
  await pushCommittedChanges(projectPath, agentEnv);
45028
45211
  }
45029
45212
  }
@@ -45389,6 +45572,7 @@ function normalizePathForLogs(value) {
45389
45572
  return value.split('\\').join('/');
45390
45573
  }
45391
45574
 
45575
+ // cspell:ignore hashtext
45392
45576
  /**
45393
45577
  * Cross-process advisory lock key guarding migration execution.
45394
45578
  *
@@ -45400,13 +45584,27 @@ const DATABASE_MIGRATION_LOCK_KEY = 'promptbook_agents_server_migrations';
45400
45584
  *
45401
45585
  * @param client Connected PostgreSQL client.
45402
45586
  * @param logger Logger used for progress output.
45587
+ * @param mode Whether the caller should block for the lock or skip when it is already held.
45588
+ * @returns `true` when the lock was acquired and migrations may proceed.
45403
45589
  *
45404
45590
  * @private function of runDatabaseMigrations
45405
45591
  */
45406
- async function acquireMigrationExecutionLock(client, logger) {
45592
+ async function acquireMigrationExecutionLock(client, logger, mode = 'wait') {
45593
+ var _a;
45594
+ if (mode === 'skip') {
45595
+ logger.info('🔒 Checking migration lock without waiting');
45596
+ const { rows } = await client.query('SELECT pg_try_advisory_lock(hashtext($1)) AS "isMigrationLockAcquired";', [DATABASE_MIGRATION_LOCK_KEY]);
45597
+ if (!((_a = rows[0]) === null || _a === void 0 ? void 0 : _a.isMigrationLockAcquired)) {
45598
+ logger.info('⏭️ Migration lock is already held by another process. Skipping this migration attempt.');
45599
+ return false;
45600
+ }
45601
+ logger.info('🔒 Migration lock acquired');
45602
+ return true;
45603
+ }
45407
45604
  logger.info('🔒 Waiting for migration lock');
45408
45605
  await client.query('SELECT pg_advisory_lock(hashtext($1));', [DATABASE_MIGRATION_LOCK_KEY]);
45409
45606
  logger.info('🔒 Migration lock acquired');
45607
+ return true;
45410
45608
  }
45411
45609
  /**
45412
45610
  * Releases advisory lock used for migration execution.
@@ -45930,23 +46128,33 @@ async function resolveDatabaseMigrationRuntimeConfiguration(logger = console) {
45930
46128
  * @returns Aggregated migration summary.
45931
46129
  */
45932
46130
  async function runDatabaseMigrations(options) {
45933
- var _a, _b, _c, _d;
46131
+ var _a, _b, _c, _d, _e;
45934
46132
  const logger = (_a = options.logger) !== null && _a !== void 0 ? _a : console;
45935
46133
  const selectedPrefixes = selectPrefixesForMigration(options.prefixes, (_b = options.registeredServers) !== null && _b !== void 0 ? _b : [], options.onlyTargets);
45936
46134
  const migrationsDirectory = (_c = options.migrationsDirectory) !== null && _c !== void 0 ? _c : (await resolveMigrationsDirectory());
45937
46135
  const migrationFiles = await readMigrationFiles(migrationsDirectory);
46136
+ const totalMigrationFiles = migrationFiles.length;
45938
46137
  const perPrefix = [];
45939
- logger.info(`📂 Found ${migrationFiles.length} migration files`);
46138
+ logger.info(`📂 Found ${totalMigrationFiles} migration files`);
45940
46139
  logger.info(`📋 Found ${selectedPrefixes.length} prefixes to migrate: ${selectedPrefixes
45941
46140
  .map((prefix) => prefix || '<default>')
45942
46141
  .join(', ')}`);
45943
46142
  const client = await createPostgresClient(options.connectionString);
45944
46143
  let hasExecutionLock = false;
46144
+ let isSkippedDueToActiveMigrationLock = false;
45945
46145
  try {
45946
46146
  await client.connect();
45947
46147
  logger.info('🔌 Connected to database');
45948
- await acquireMigrationExecutionLock(client, logger);
45949
- hasExecutionLock = true;
46148
+ hasExecutionLock = await acquireMigrationExecutionLock(client, logger, (_d = options.executionLockMode) !== null && _d !== void 0 ? _d : 'wait');
46149
+ if (!hasExecutionLock) {
46150
+ isSkippedDueToActiveMigrationLock = true;
46151
+ return {
46152
+ processedPrefixes: [],
46153
+ totalMigrationFiles,
46154
+ perPrefix,
46155
+ isSkippedDueToActiveMigrationLock,
46156
+ };
46157
+ }
45950
46158
  for (const prefix of selectedPrefixes) {
45951
46159
  logger.info(`\n🏗️ Migrating prefix: "${prefix}"`);
45952
46160
  const appliedCount = await migratePrefix({
@@ -45957,7 +46165,7 @@ async function runDatabaseMigrations(options) {
45957
46165
  logger,
45958
46166
  migrationFiles,
45959
46167
  migrationsDirectory,
45960
- logSqlStatements: (_d = options.logSqlStatements) !== null && _d !== void 0 ? _d : false,
46168
+ logSqlStatements: (_e = options.logSqlStatements) !== null && _e !== void 0 ? _e : false,
45961
46169
  });
45962
46170
  perPrefix.push({ prefix, appliedCount });
45963
46171
  }
@@ -45970,8 +46178,9 @@ async function runDatabaseMigrations(options) {
45970
46178
  }
45971
46179
  return {
45972
46180
  processedPrefixes: selectedPrefixes,
45973
- totalMigrationFiles: migrationFiles.length,
46181
+ totalMigrationFiles,
45974
46182
  perPrefix,
46183
+ isSkippedDueToActiveMigrationLock,
45975
46184
  };
45976
46185
  }
45977
46186
  /**
@@ -46590,6 +46799,19 @@ function formatUsagePrice(usage) {
46590
46799
  return `${prefix}$${price.toFixed(2)}`;
46591
46800
  }
46592
46801
 
46802
+ /**
46803
+ * Formats optional attempt metadata stored in prompt status lines.
46804
+ */
46805
+ function formatPromptAttemptMetadata(status, attemptCount) {
46806
+ if (attemptCount <= 1) {
46807
+ return '';
46808
+ }
46809
+ if (status === 'done') {
46810
+ return `(${attemptCount} attempts) `;
46811
+ }
46812
+ return `(failed after ${attemptCount} attempts) `;
46813
+ }
46814
+
46593
46815
  /**
46594
46816
  * Formats runner details for prompt status lines.
46595
46817
  */
@@ -46609,7 +46831,7 @@ function formatRunnerSignature(runnerName, modelName) {
46609
46831
  /**
46610
46832
  * Marks a prompt section as done and records usage pricing and runner details.
46611
46833
  */
46612
- function markPromptDone(file, section, usage, runnerName, modelName, promptExecutionStartedDate) {
46834
+ function markPromptDone(file, section, usage, runnerName, modelName, promptExecutionStartedDate, attemptCount = 1) {
46613
46835
  if (section.statusLineIndex === undefined) {
46614
46836
  throw new Error(`Prompt ${section.index + 1} in ${file.name} does not have a status line.`);
46615
46837
  }
@@ -46619,16 +46841,17 @@ function markPromptDone(file, section, usage, runnerName, modelName, promptExecu
46619
46841
  }
46620
46842
  const priceString = formatUsagePrice(usage);
46621
46843
  const runnerSignature = formatRunnerSignature(runnerName, modelName);
46844
+ const attemptMetadata = formatPromptAttemptMetadata('done', attemptCount);
46622
46845
  const duration = moment().diff(promptExecutionStartedDate);
46623
46846
  const durationString = moment.duration(duration).humanize();
46624
46847
  // Replace "[ ]" or "[ ] !!..." with "[x] $price duration by runner"
46625
- file.lines[section.statusLineIndex] = line.replace(/\[\s*\]\s*!*\s*$/, `[x] ${priceString} ${durationString} by ${runnerSignature}`);
46848
+ file.lines[section.statusLineIndex] = line.replace(/\[\s*\]\s*!*\s*$/, `[x] ${attemptMetadata}${priceString} ${durationString} by ${runnerSignature}`);
46626
46849
  }
46627
46850
 
46628
46851
  /**
46629
46852
  * Marks a prompt section as failed and records runner details.
46630
46853
  */
46631
- function markPromptFailed(file, section, runnerName, modelName, promptExecutionStartedDate) {
46854
+ function markPromptFailed(file, section, runnerName, modelName, promptExecutionStartedDate, attemptCount = 1) {
46632
46855
  if (section.statusLineIndex === undefined) {
46633
46856
  throw new Error(`Prompt ${section.index + 1} in ${file.name} does not have a status line.`);
46634
46857
  }
@@ -46637,9 +46860,11 @@ function markPromptFailed(file, section, runnerName, modelName, promptExecutionS
46637
46860
  throw new Error(`Prompt ${section.index + 1} in ${file.name} points to a missing status line.`);
46638
46861
  }
46639
46862
  const runnerSignature = formatRunnerSignature(runnerName, modelName);
46863
+ const attemptMetadata = formatPromptAttemptMetadata('failed', attemptCount);
46640
46864
  const duration = moment().diff(promptExecutionStartedDate);
46641
46865
  const durationString = moment.duration(duration).humanize();
46642
- file.lines[section.statusLineIndex] = line.replace(/\[\s*\]\s*!*\s*$/, `[!] failed after ${durationString} by ${runnerSignature}`);
46866
+ const failureDetails = attemptMetadata === '' ? `failed after ${durationString} by ${runnerSignature}` : `${attemptMetadata}${durationString} by ${runnerSignature}`;
46867
+ file.lines[section.statusLineIndex] = line.replace(/\[\s*\]\s*!*\s*$/, `[!] ${failureDetails}`);
46643
46868
  }
46644
46869
 
46645
46870
  /**
@@ -46756,6 +46981,20 @@ async function waitForPromptStart(file, section, isFirstPrompt) {
46756
46981
  await waitForEnter(colors.bgWhite(`Press Enter to start the ${label}...`));
46757
46982
  }
46758
46983
 
46984
+ /**
46985
+ * Formats one unknown error-like value into readable text for logs and feedback.
46986
+ */
46987
+ function formatUnknownErrorDetails(error) {
46988
+ if (error instanceof Error) {
46989
+ return error.stack || error.message;
46990
+ }
46991
+ if (typeof error === 'string') {
46992
+ return error;
46993
+ }
46994
+ const serializedError = JSON.stringify(error, null, 2);
46995
+ return serializedError !== null && serializedError !== void 0 ? serializedError : String(error);
46996
+ }
46997
+
46759
46998
  /**
46760
46999
  * Writes the failure details for a single prompt run next to the prompt markdown file.
46761
47000
  */
@@ -46763,7 +47002,7 @@ async function writePromptErrorLog(options) {
46763
47002
  const logPath = buildPromptErrorLogPath(options.file.path);
46764
47003
  const label = buildPromptLabelForDisplay(options.file, options.section);
46765
47004
  const summary = buildPromptSummary(options.file, options.section);
46766
- const details = buildErrorDetails(options.error);
47005
+ const details = formatUnknownErrorDetails(options.error);
46767
47006
  const modelSuffix = options.modelName ? ` (${options.modelName})` : '';
46768
47007
  const runnerLabel = `${options.runnerName || 'unknown'}${modelSuffix}`;
46769
47008
  const log = [
@@ -46790,18 +47029,6 @@ function buildPromptErrorLogPath(promptPath) {
46790
47029
  }
46791
47030
  return `${promptPath}.error.log`;
46792
47031
  }
46793
- /**
46794
- * Formats unknown error values into a readable log payload.
46795
- */
46796
- function buildErrorDetails(error) {
46797
- if (error instanceof Error) {
46798
- return error.stack || error.message;
46799
- }
46800
- if (typeof error === 'string') {
46801
- return error;
46802
- }
46803
- return JSON.stringify(error, null, 2);
46804
- }
46805
47032
 
46806
47033
  /**
46807
47034
  * Writes updated prompt file content to disk.
@@ -47860,6 +48087,647 @@ class OpencodeRunner {
47860
48087
  }
47861
48088
  }
47862
48089
 
48090
+ /**
48091
+ * Runs the configured verification command inside the project root and returns its output.
48092
+ */
48093
+ async function runPromptTestCommand(options) {
48094
+ const projectPath = toPosixPath(options.projectPath);
48095
+ return await $runGoScriptWithOutput({
48096
+ scriptPath: options.scriptPath,
48097
+ scriptContent: spaceTrim(`
48098
+ cd "${projectPath}"
48099
+ ${options.command}
48100
+ `),
48101
+ });
48102
+ }
48103
+
48104
+ /**
48105
+ * Maximum number of coding attempts allowed for the same prompt when verification keeps failing.
48106
+ */
48107
+ const MAX_PROMPT_TEST_ATTEMPTS = 3;
48108
+ /**
48109
+ * Maximum amount of verification output sent back to the coding agent as retry feedback.
48110
+ */
48111
+ const MAX_TEST_FEEDBACK_OUTPUT_CHARS = 12000;
48112
+ /**
48113
+ * Runs one coding prompt and, when configured, verifies it with a shell command that can feed failures back.
48114
+ */
48115
+ async function runPromptWithTestFeedback(options) {
48116
+ var _a, _b, _c, _d;
48117
+ const normalizedTestCommand = (_a = options.testCommand) === null || _a === void 0 ? void 0 : _a.trim();
48118
+ if (!normalizedTestCommand) {
48119
+ (_b = options.onAttemptStarted) === null || _b === void 0 ? void 0 : _b.call(options, 1);
48120
+ const result = await options.runner.runPrompt({
48121
+ prompt: options.prompt,
48122
+ scriptPath: options.scriptPath,
48123
+ projectPath: options.projectPath,
48124
+ });
48125
+ return { ...result, attemptCount: 1 };
48126
+ }
48127
+ const runPromptTestCommandExecutor = (_c = options.runPromptTestCommandExecutor) !== null && _c !== void 0 ? _c : runPromptTestCommand;
48128
+ let promptForCurrentAttempt = options.prompt;
48129
+ for (let attemptCount = 1; attemptCount <= MAX_PROMPT_TEST_ATTEMPTS; attemptCount++) {
48130
+ (_d = options.onAttemptStarted) === null || _d === void 0 ? void 0 : _d.call(options, attemptCount);
48131
+ const result = await options.runner.runPrompt({
48132
+ prompt: promptForCurrentAttempt,
48133
+ scriptPath: options.scriptPath,
48134
+ projectPath: options.projectPath,
48135
+ });
48136
+ console.info(colors.gray(`Running verification command after attempt #${attemptCount}: ${normalizedTestCommand}`));
48137
+ try {
48138
+ await runPromptTestCommandExecutor({
48139
+ command: normalizedTestCommand,
48140
+ projectPath: options.projectPath,
48141
+ scriptPath: buildPromptTestScriptPath(options.scriptPath),
48142
+ });
48143
+ return { ...result, attemptCount };
48144
+ }
48145
+ catch (error) {
48146
+ const fullVerificationOutput = formatUnknownErrorDetails(error);
48147
+ const feedbackVerificationOutput = limitVerificationOutputForFeedback(fullVerificationOutput);
48148
+ if (attemptCount >= MAX_PROMPT_TEST_ATTEMPTS) {
48149
+ console.error(colors.red(`Verification failed for ${options.promptLabel} after ${attemptCount} attempts.`));
48150
+ throw new Error(buildFinalVerificationFailureMessage({
48151
+ promptLabel: options.promptLabel,
48152
+ testCommand: normalizedTestCommand,
48153
+ attemptCount,
48154
+ verificationOutput: fullVerificationOutput,
48155
+ }));
48156
+ }
48157
+ console.warn(colors.yellow(`Verification failed for ${options.promptLabel} on attempt #${attemptCount}. Sending feedback to ${options.runner.name} and retrying...`));
48158
+ promptForCurrentAttempt = appendCoderContext(options.prompt, buildVerificationFeedback({
48159
+ testCommand: normalizedTestCommand,
48160
+ failedAttemptCount: attemptCount,
48161
+ verificationOutput: feedbackVerificationOutput,
48162
+ }));
48163
+ }
48164
+ }
48165
+ throw new Error('Unexpected prompt verification state.');
48166
+ }
48167
+ /**
48168
+ * Builds one feedback block appended to the next coding attempt after tests fail.
48169
+ */
48170
+ function buildVerificationFeedback({ testCommand, failedAttemptCount, verificationOutput, }) {
48171
+ const nextAttemptCount = failedAttemptCount + 1;
48172
+ return spaceTrim((block) => `
48173
+ The previous implementation did not pass the required verification command.
48174
+
48175
+ ## Automated verification feedback
48176
+ - Retry attempt: ${nextAttemptCount} of ${MAX_PROMPT_TEST_ATTEMPTS}
48177
+ - Verification command: \`${testCommand}\`
48178
+ - Update the current implementation so the verification command passes without breaking the original task requirements.
48179
+
48180
+ ### Verification output
48181
+ \`\`\`
48182
+ ${block(verificationOutput)}
48183
+ \`\`\`
48184
+ `);
48185
+ }
48186
+ /**
48187
+ * Builds the final error message written when verification still fails after all retries.
48188
+ */
48189
+ function buildFinalVerificationFailureMessage({ promptLabel, testCommand, attemptCount, verificationOutput, }) {
48190
+ return spaceTrim((block) => `
48191
+ Verification command \`${testCommand}\` failed for \`${promptLabel}\` after ${attemptCount} attempts.
48192
+
48193
+ ### Verification output
48194
+ \`\`\`
48195
+ ${block(verificationOutput)}
48196
+ \`\`\`
48197
+ `);
48198
+ }
48199
+ /**
48200
+ * Limits verification output before it is embedded back into the next coding prompt.
48201
+ */
48202
+ function limitVerificationOutputForFeedback(verificationOutput) {
48203
+ const normalizedVerificationOutput = verificationOutput.trim();
48204
+ if (normalizedVerificationOutput.length <= MAX_TEST_FEEDBACK_OUTPUT_CHARS) {
48205
+ return normalizedVerificationOutput;
48206
+ }
48207
+ return spaceTrim(`
48208
+ [...verification output truncated to the last ${MAX_TEST_FEEDBACK_OUTPUT_CHARS} characters...]
48209
+ ${normalizedVerificationOutput.slice(-MAX_TEST_FEEDBACK_OUTPUT_CHARS)}
48210
+ `);
48211
+ }
48212
+ /**
48213
+ * Derives a dedicated temp-script path for verification commands.
48214
+ */
48215
+ function buildPromptTestScriptPath(scriptPath) {
48216
+ if (scriptPath.toLowerCase().endsWith('.sh')) {
48217
+ return `${scriptPath.slice(0, -3)}.test.sh`;
48218
+ }
48219
+ return `${scriptPath}.test.sh`;
48220
+ }
48221
+
48222
+ /**
48223
+ * Maximum number of agent output lines kept in the scrolling output area.
48224
+ *
48225
+ * @private internal constant of coder run UI
48226
+ */
48227
+ const MAX_AGENT_OUTPUT_LINES = 12;
48228
+ /**
48229
+ * Calendar formats used when displaying the estimated completion time.
48230
+ *
48231
+ * @private internal constant of coder run UI
48232
+ */
48233
+ const ESTIMATED_DONE_CALENDAR_FORMATS = {
48234
+ sameDay: '[Today] h:mm',
48235
+ nextDay: '[Tomorrow] h:mm',
48236
+ nextWeek: 'dddd h:mm',
48237
+ lastDay: '[Yesterday] h:mm',
48238
+ lastWeek: 'dddd h:mm',
48239
+ sameElse: 'MMM D h:mm',
48240
+ };
48241
+ /**
48242
+ * Reactive state manager for the coder run terminal UI.
48243
+ *
48244
+ * Holds all data the Ink components need and emits `'change'` events
48245
+ * whenever any property is updated so the UI can re-render.
48246
+ *
48247
+ * @private internal utility of coder run UI
48248
+ */
48249
+ class CoderRunUiState extends EventEmitter {
48250
+ constructor(startTime) {
48251
+ super();
48252
+ this.config = { agentName: '', priority: 0 };
48253
+ this.currentPromptLabel = '';
48254
+ this.currentAttempt = 1;
48255
+ this.maxAttempts = 3;
48256
+ this.agentOutputLines = [];
48257
+ this.phase = 'initializing';
48258
+ this.statusMessage = 'Initializing...';
48259
+ this.errors = [];
48260
+ this.stats = { done: 0, forAgent: 0, belowMinimumPriority: 0, toBeWritten: 0 };
48261
+ /**
48262
+ * Total milliseconds the timer was paused/waiting (excluded from elapsed display).
48263
+ */
48264
+ this.pausedMs = 0;
48265
+ this.startTime = startTime;
48266
+ // Timer starts paused — callers call `resumeTimer()` when actual work begins.
48267
+ this.pausedSince = startTime.clone();
48268
+ }
48269
+ /**
48270
+ * Pauses the elapsed timer (e.g. while waiting for user input or paused state).
48271
+ */
48272
+ pauseTimer() {
48273
+ if (this.pausedSince === undefined) {
48274
+ this.pausedSince = moment();
48275
+ }
48276
+ }
48277
+ /**
48278
+ * Resumes the elapsed timer after a pause.
48279
+ */
48280
+ resumeTimer() {
48281
+ if (this.pausedSince !== undefined) {
48282
+ this.pausedMs += moment().diff(this.pausedSince);
48283
+ this.pausedSince = undefined;
48284
+ }
48285
+ }
48286
+ /**
48287
+ * Replaces the configuration shown in the UI header.
48288
+ */
48289
+ setConfig(config) {
48290
+ this.config = config;
48291
+ this.emitChange();
48292
+ }
48293
+ /**
48294
+ * Feeds new prompt statistics from the main loop so the progress bar updates.
48295
+ */
48296
+ updateProgress(stats) {
48297
+ if (this.initialDone === undefined && (stats.done > 0 || stats.forAgent > 0 || stats.toBeWritten > 0)) {
48298
+ this.initialDone = stats.done;
48299
+ }
48300
+ this.stats = stats;
48301
+ this.emitChange();
48302
+ }
48303
+ /**
48304
+ * Computes a progress snapshot on demand so elapsed time ticks with periodic re-renders.
48305
+ */
48306
+ getProgress() {
48307
+ var _a;
48308
+ const stats = this.stats;
48309
+ const totalPrompts = stats.done + stats.forAgent + stats.toBeWritten;
48310
+ const sessionDone = Math.max(0, stats.done - ((_a = this.initialDone) !== null && _a !== void 0 ? _a : stats.done));
48311
+ const sessionTotal = sessionDone + stats.forAgent;
48312
+ const percentage = totalPrompts > 0 ? Math.round((stats.done / totalPrompts) * 100) : 0;
48313
+ const wallMs = moment().diff(this.startTime);
48314
+ const currentPauseMs = this.pausedSince !== undefined ? moment().diff(this.pausedSince) : 0;
48315
+ const activeMs = Math.max(0, wallMs - this.pausedMs - currentPauseMs);
48316
+ const elapsedDuration = moment.duration(activeMs);
48317
+ const elapsedText = formatDurationBrief(elapsedDuration);
48318
+ let estimatedTotalText = '\u2014';
48319
+ let estimatedLabel = 'unknown';
48320
+ if (totalPrompts > 0 && stats.done > 0) {
48321
+ const estimatedTotalMs = (elapsedDuration.asMilliseconds() * totalPrompts) / stats.done;
48322
+ const estimatedRemainingMs = estimatedTotalMs - elapsedDuration.asMilliseconds();
48323
+ const estimatedTotalDuration = moment.duration(estimatedTotalMs);
48324
+ const estimatedCompletion = moment().add(estimatedRemainingMs, 'milliseconds');
48325
+ estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
48326
+ estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
48327
+ }
48328
+ return {
48329
+ totalPrompts,
48330
+ sessionDone,
48331
+ sessionTotal,
48332
+ percentage,
48333
+ elapsedText,
48334
+ estimatedTotalText,
48335
+ estimatedLabel,
48336
+ };
48337
+ }
48338
+ /**
48339
+ * Sets the label of the prompt currently being processed and resets per-prompt state.
48340
+ */
48341
+ setCurrentPrompt(label) {
48342
+ this.currentPromptLabel = label;
48343
+ this.agentOutputLines = [];
48344
+ this.currentAttempt = 1;
48345
+ this.emitChange();
48346
+ }
48347
+ /**
48348
+ * Updates the current retry attempt number.
48349
+ */
48350
+ setAttempt(attempt) {
48351
+ this.currentAttempt = attempt;
48352
+ this.emitChange();
48353
+ }
48354
+ /**
48355
+ * Appends raw agent output text, keeping only the last `MAX_AGENT_OUTPUT_LINES`.
48356
+ */
48357
+ addAgentOutput(text) {
48358
+ const lines = text.split(/\r?\n/).filter((line) => line.trim() !== '');
48359
+ if (lines.length === 0) {
48360
+ return;
48361
+ }
48362
+ this.agentOutputLines.push(...lines);
48363
+ if (this.agentOutputLines.length > MAX_AGENT_OUTPUT_LINES) {
48364
+ this.agentOutputLines = this.agentOutputLines.slice(-MAX_AGENT_OUTPUT_LINES);
48365
+ }
48366
+ this.emitChange();
48367
+ }
48368
+ /**
48369
+ * Transitions the execution phase shown in the UI.
48370
+ */
48371
+ setPhase(phase) {
48372
+ this.phase = phase;
48373
+ this.emitChange();
48374
+ }
48375
+ /**
48376
+ * Updates the status message line beneath the current prompt label.
48377
+ */
48378
+ setStatusMessage(message) {
48379
+ this.statusMessage = message;
48380
+ this.emitChange();
48381
+ }
48382
+ /**
48383
+ * Appends an error message to the error list shown in the UI.
48384
+ */
48385
+ addError(errorMessage) {
48386
+ this.errors.push(errorMessage);
48387
+ this.emitChange();
48388
+ }
48389
+ emitChange() {
48390
+ this.emit('change');
48391
+ }
48392
+ }
48393
+ /**
48394
+ * Formats a duration into a compact string such as "3h 12m" or "45s".
48395
+ *
48396
+ * @private internal utility of coder run UI
48397
+ */
48398
+ function formatDurationBrief(duration) {
48399
+ const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
48400
+ const hours = Math.floor(totalSeconds / 3600);
48401
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
48402
+ const seconds = totalSeconds % 60;
48403
+ const parts = [];
48404
+ if (hours > 0) {
48405
+ parts.push(`${hours}h`);
48406
+ }
48407
+ if (minutes > 0) {
48408
+ parts.push(`${minutes}m`);
48409
+ }
48410
+ if (!parts.length && seconds > 0) {
48411
+ parts.push(`${seconds}s`);
48412
+ }
48413
+ if (!parts.length) {
48414
+ parts.push('0s');
48415
+ }
48416
+ return parts.join(' ');
48417
+ }
48418
+
48419
+ /**
48420
+ * Refresh interval for the terminal UI in milliseconds.
48421
+ *
48422
+ * @private internal constant of coder run UI
48423
+ */
48424
+ const UI_REFRESH_INTERVAL_MS = 200;
48425
+ /**
48426
+ * Character width used for the text progress bar.
48427
+ *
48428
+ * @private internal constant of coder run UI
48429
+ */
48430
+ const PROGRESS_BAR_WIDTH = 40;
48431
+ /**
48432
+ * Maximum number of output lines reserved for agent output in the UI.
48433
+ *
48434
+ * @private internal constant of coder run UI
48435
+ */
48436
+ const MAX_VISIBLE_OUTPUT_LINES = 8;
48437
+ /**
48438
+ * Spinner animation frames.
48439
+ *
48440
+ * @private internal constant of coder run UI
48441
+ */
48442
+ const SPINNER_FRAMES = [
48443
+ '\u280B',
48444
+ '\u2819',
48445
+ '\u2839',
48446
+ '\u2838',
48447
+ '\u283C',
48448
+ '\u2834',
48449
+ '\u2826',
48450
+ '\u2827',
48451
+ '\u2807',
48452
+ '\u280F',
48453
+ ];
48454
+ /**
48455
+ * Strips ANSI escape codes from a string.
48456
+ *
48457
+ * @private internal utility of coder run UI
48458
+ */
48459
+ function stripAnsi(text) {
48460
+ // eslint-disable-next-line no-control-regex
48461
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
48462
+ }
48463
+ /**
48464
+ * Returns the usable terminal width, capped at 80.
48465
+ *
48466
+ * @private internal utility of coder run UI
48467
+ */
48468
+ function getTerminalWidth() {
48469
+ return Math.min(process.stdout.columns || 80, 80);
48470
+ }
48471
+ /**
48472
+ * Builds a text progress bar string from a percentage.
48473
+ *
48474
+ * @private internal utility of coder run UI
48475
+ */
48476
+ function buildProgressBar(percentage) {
48477
+ const filled = Math.round((percentage / 100) * PROGRESS_BAR_WIDTH);
48478
+ const empty = PROGRESS_BAR_WIDTH - filled;
48479
+ return colors.green('\u2588'.repeat(filled)) + colors.gray('\u2591'.repeat(empty)) + ` ${percentage}%`;
48480
+ }
48481
+ /**
48482
+ * Boots the ANSI terminal UI for `ptbk coder run`.
48483
+ *
48484
+ * The UI reserves a fixed number of terminal lines and repaints them periodically.
48485
+ * Between repaints, any console output from runners is captured and fed into the
48486
+ * scrolling agent-output area.
48487
+ *
48488
+ * On non-interactive (non-TTY) terminals the UI is skipped entirely and
48489
+ * only the state object is provided.
48490
+ *
48491
+ * @private internal entry point of coder run UI
48492
+ */
48493
+ function renderCoderRunUi(startTime) {
48494
+ const state = new CoderRunUiState(startTime);
48495
+ if (!process.stdout.isTTY) {
48496
+ return {
48497
+ state,
48498
+ startCapturingAgentOutput: () => { },
48499
+ stopCapturingAgentOutput: () => { },
48500
+ cleanup: () => { },
48501
+ };
48502
+ }
48503
+ // --- Console interception ---
48504
+ const originalConsoleInfo = console.info;
48505
+ const originalConsoleWarn = console.warn;
48506
+ const originalConsoleError = console.error;
48507
+ const originalConsoleLog = console.log;
48508
+ let isCapturing = false;
48509
+ console.info = (...args) => {
48510
+ if (isCapturing) {
48511
+ state.addAgentOutput(args.map(String).join(' '));
48512
+ }
48513
+ // In UI mode, non-captured output is intentionally suppressed
48514
+ // so it does not interfere with the repainted frame.
48515
+ };
48516
+ console.warn = (...args) => {
48517
+ if (isCapturing) {
48518
+ state.addAgentOutput(args.map(String).join(' '));
48519
+ }
48520
+ };
48521
+ console.error = (...args) => {
48522
+ if (isCapturing) {
48523
+ state.addError(args.map(String).join(' '));
48524
+ }
48525
+ };
48526
+ console.log = (...args) => {
48527
+ if (isCapturing) {
48528
+ state.addAgentOutput(args.map(String).join(' '));
48529
+ }
48530
+ };
48531
+ // --- Keyboard input (pause) ---
48532
+ const readline = require('readline');
48533
+ readline.emitKeypressEvents(process.stdin);
48534
+ if (process.stdin.isTTY) {
48535
+ process.stdin.setRawMode(true);
48536
+ }
48537
+ const keypressHandler = (_str, key) => {
48538
+ if (key.ctrl && key.name === 'c') {
48539
+ cleanup();
48540
+ process.exit(0);
48541
+ }
48542
+ if (key.name === 'p') {
48543
+ const current = getPauseState();
48544
+ if (current === 'RUNNING') {
48545
+ requestPause();
48546
+ }
48547
+ else {
48548
+ requestResume();
48549
+ }
48550
+ }
48551
+ };
48552
+ process.stdin.on('keypress', keypressHandler);
48553
+ // --- Rendering ---
48554
+ let spinnerFrame = 0;
48555
+ let previousFrameLineCount = 0;
48556
+ let isRendering = false;
48557
+ let renderScheduled = false;
48558
+ /**
48559
+ * Schedules a render on the next tick if one isn't already pending.
48560
+ * Prevents overlapping renders that cause cursor desync.
48561
+ */
48562
+ function scheduleRender() {
48563
+ if (renderScheduled) {
48564
+ return;
48565
+ }
48566
+ renderScheduled = true;
48567
+ setImmediate(() => {
48568
+ renderScheduled = false;
48569
+ render();
48570
+ });
48571
+ }
48572
+ /**
48573
+ * Clears previously rendered lines and writes a new frame.
48574
+ */
48575
+ function render() {
48576
+ if (isRendering) {
48577
+ return;
48578
+ }
48579
+ isRendering = true;
48580
+ try {
48581
+ const lines = buildFrame();
48582
+ // Move cursor up to clear the previous frame.
48583
+ if (previousFrameLineCount > 0) {
48584
+ process.stdout.write(`\x1b[${previousFrameLineCount}A`);
48585
+ }
48586
+ for (let i = 0; i < lines.length; i++) {
48587
+ clearLine(process.stdout, 0);
48588
+ cursorTo(process.stdout, 0);
48589
+ process.stdout.write(lines[i]);
48590
+ if (i < lines.length - 1) {
48591
+ process.stdout.write('\n');
48592
+ }
48593
+ }
48594
+ // Clear any leftover lines from a previous longer frame.
48595
+ if (lines.length < previousFrameLineCount) {
48596
+ for (let i = lines.length; i < previousFrameLineCount; i++) {
48597
+ process.stdout.write('\n');
48598
+ clearLine(process.stdout, 0);
48599
+ cursorTo(process.stdout, 0);
48600
+ }
48601
+ // Move back up to the end of the current frame.
48602
+ const overshoot = previousFrameLineCount - lines.length;
48603
+ if (overshoot > 0) {
48604
+ process.stdout.write(`\x1b[${overshoot}A`);
48605
+ }
48606
+ }
48607
+ previousFrameLineCount = lines.length;
48608
+ spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
48609
+ }
48610
+ finally {
48611
+ isRendering = false;
48612
+ }
48613
+ }
48614
+ /**
48615
+ * Builds the complete frame as an array of terminal lines.
48616
+ */
48617
+ function buildFrame() {
48618
+ const w = getTerminalWidth();
48619
+ const sep = colors.gray('\u2500'.repeat(w - 2));
48620
+ const spinner = SPINNER_FRAMES[spinnerFrame];
48621
+ const { config, phase, currentPromptLabel, currentAttempt, maxAttempts, statusMessage, agentOutputLines, errors, } = state;
48622
+ const progress = state.getProgress();
48623
+ const isPaused = getPauseState() !== 'RUNNING';
48624
+ const isActive = phase === 'running' || phase === 'verifying' || phase === 'loading';
48625
+ const lines = [];
48626
+ // --- Branding ---
48627
+ lines.push(colors.bold.cyan('\u2728 Promptbook Coder'));
48628
+ // --- Config ---
48629
+ let configLine1 = `Agent: ${colors.bold.green(config.agentName)}`;
48630
+ if (config.modelName) {
48631
+ configLine1 += ` \u2502 Model: ${colors.bold(config.modelName)}`;
48632
+ }
48633
+ if (config.thinkingLevel) {
48634
+ configLine1 += ` \u2502 Thinking: ${colors.bold(config.thinkingLevel)}`;
48635
+ }
48636
+ lines.push(configLine1);
48637
+ let configLine2 = '';
48638
+ if (config.context) {
48639
+ configLine2 += `Context: ${colors.yellow(config.context)} \u2502 `;
48640
+ }
48641
+ configLine2 += `Priority: \u2265${config.priority}`;
48642
+ if (config.testCommand) {
48643
+ configLine2 += ` \u2502 Test: ${colors.gray(config.testCommand)}`;
48644
+ }
48645
+ lines.push(configLine2);
48646
+ // --- Separator ---
48647
+ lines.push(sep);
48648
+ // --- Progress ---
48649
+ const progressSummary = [
48650
+ `${progress.sessionDone}/${progress.sessionTotal} Prompts (${progress.totalPrompts} total)`,
48651
+ `${progress.elapsedText}/${progress.estimatedTotalText}`,
48652
+ `Est. done ${progress.estimatedLabel}`,
48653
+ ].join(' \u2502 ');
48654
+ lines.push(progressSummary);
48655
+ lines.push(buildProgressBar(progress.percentage));
48656
+ // --- Separator ---
48657
+ lines.push(sep);
48658
+ // --- Current prompt ---
48659
+ if (currentPromptLabel) {
48660
+ const spinnerPrefix = isActive ? colors.yellow(`${spinner} `) : ' ';
48661
+ lines.push(spinnerPrefix + colors.bold(currentPromptLabel));
48662
+ lines.push(colors.gray(`Attempt ${currentAttempt}/${maxAttempts} \u2502 ${statusMessage}`));
48663
+ }
48664
+ else {
48665
+ lines.push(colors.gray(statusMessage));
48666
+ }
48667
+ // --- Agent output ---
48668
+ if (agentOutputLines.length > 0) {
48669
+ lines.push('');
48670
+ lines.push(colors.gray.bold('Agent output:'));
48671
+ const visibleLines = agentOutputLines.slice(-MAX_VISIBLE_OUTPUT_LINES);
48672
+ for (const line of visibleLines) {
48673
+ const cleanLine = stripAnsi(line);
48674
+ // Truncate to terminal width.
48675
+ const truncated = cleanLine.length > w - 2 ? cleanLine.slice(0, w - 5) + '...' : cleanLine;
48676
+ lines.push(colors.gray(truncated));
48677
+ }
48678
+ }
48679
+ // --- Errors ---
48680
+ if (errors.length > 0) {
48681
+ lines.push('');
48682
+ for (const err of errors) {
48683
+ lines.push(colors.red(`\u2717 ${err}`));
48684
+ }
48685
+ }
48686
+ // --- Separator ---
48687
+ lines.push(sep);
48688
+ // --- Controls ---
48689
+ const pauseLabel = isPaused
48690
+ ? colors.bgYellow.black(' PAUSED ') + colors.gray(' [P] Resume \u2502 Ctrl+C Exit')
48691
+ : colors.gray('[P] Pause \u2502 Ctrl+C Exit');
48692
+ lines.push(pauseLabel);
48693
+ return lines;
48694
+ }
48695
+ // Initial render.
48696
+ process.stdout.write('\n');
48697
+ render();
48698
+ const interval = setInterval(scheduleRender, UI_REFRESH_INTERVAL_MS);
48699
+ // Listen for state changes and schedule a re-render (debounced).
48700
+ state.on('change', scheduleRender);
48701
+ // --- Cleanup ---
48702
+ function cleanup() {
48703
+ clearInterval(interval);
48704
+ state.off('change', scheduleRender);
48705
+ process.stdin.off('keypress', keypressHandler);
48706
+ if (process.stdin.isTTY) {
48707
+ process.stdin.setRawMode(false);
48708
+ }
48709
+ isCapturing = false;
48710
+ console.info = originalConsoleInfo;
48711
+ console.warn = originalConsoleWarn;
48712
+ console.error = originalConsoleError;
48713
+ console.log = originalConsoleLog;
48714
+ // Render one final frame so the user sees the last state.
48715
+ render();
48716
+ process.stdout.write('\n');
48717
+ }
48718
+ return {
48719
+ state,
48720
+ startCapturingAgentOutput() {
48721
+ isCapturing = true;
48722
+ },
48723
+ stopCapturingAgentOutput() {
48724
+ isCapturing = false;
48725
+ },
48726
+ cleanup,
48727
+ };
48728
+ }
48729
+ // Note: [💞] Ignore a discrepancy between file name and entity name
48730
+
47863
48731
  /**
47864
48732
  * Constant for prompts dir.
47865
48733
  */
@@ -47922,8 +48790,13 @@ async function runCodexPrompts(providedOptions) {
47922
48790
  `));
47923
48791
  }
47924
48792
  const runStartDate = moment();
47925
- const progressDisplay = options.dryRun ? undefined : new CliProgressDisplay(runStartDate);
47926
- listenForPause();
48793
+ const isUiMode = !options.dryRun && Boolean(process.stdout.isTTY);
48794
+ const progressDisplay = options.dryRun || isUiMode ? undefined : new CliProgressDisplay(runStartDate);
48795
+ const uiHandle = isUiMode ? renderCoderRunUi(runStartDate) : undefined;
48796
+ // When the Ink UI is active it handles keyboard input itself, so skip the raw stdin listener.
48797
+ if (!isUiMode) {
48798
+ listenForPause();
48799
+ }
47927
48800
  try {
47928
48801
  const resolvedCoderContext = await resolveCoderContext(options.context, process.cwd());
47929
48802
  if (options.dryRun) {
@@ -48022,35 +48895,78 @@ async function runCodexPrompts(providedOptions) {
48022
48895
  }
48023
48896
  console.info(colors.green(`Running prompts with ${runner.name}`));
48024
48897
  const runnerMetadata = getRunnerMetadata(options, actualRunnerModel);
48898
+ // Feed configuration into the terminal UI
48899
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setConfig({
48900
+ agentName: runner.name,
48901
+ modelName: actualRunnerModel,
48902
+ thinkingLevel: options.thinkingLevel,
48903
+ context: options.context,
48904
+ priority: options.priority,
48905
+ testCommand: options.testCommand,
48906
+ });
48907
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
48908
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Running prompts with ${runner.name}`);
48025
48909
  let hasShownUpcomingTasks = false;
48026
48910
  let hasWaitedForStart = false;
48027
48911
  while (just(true)) {
48028
- await checkPause();
48912
+ await checkPause({
48913
+ silent: isUiMode,
48914
+ onPaused: () => {
48915
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
48916
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('paused');
48917
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Paused');
48918
+ },
48919
+ onResumed: () => {
48920
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
48921
+ },
48922
+ });
48923
+ if (isUiMode) {
48924
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
48925
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Loading prompts...');
48926
+ }
48029
48927
  const promptFiles = await loadPromptFiles(PROMPTS_DIR$1);
48030
48928
  const stats = summarizePrompts(promptFiles, options.priority);
48031
48929
  progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.update(stats);
48032
- printStats(stats, options.priority);
48930
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.updateProgress(stats);
48931
+ if (!isUiMode) {
48932
+ printStats(stats, options.priority);
48933
+ }
48033
48934
  const nextPrompt = findNextTodoPrompt(promptFiles, options.priority);
48034
48935
  if (!hasShownUpcomingTasks) {
48035
- if (stats.toBeWritten > 0) {
48936
+ if (stats.toBeWritten > 0 && !isUiMode) {
48036
48937
  console.info(colors.yellow('Following prompts need to be written:'));
48037
48938
  printPromptsToBeWritten(promptFiles, options.priority);
48038
48939
  console.info('');
48039
48940
  }
48040
- printUpcomingTasks(listUpcomingTasks(promptFiles, options.priority));
48941
+ if (!isUiMode) {
48942
+ printUpcomingTasks(listUpcomingTasks(promptFiles, options.priority));
48943
+ }
48041
48944
  hasShownUpcomingTasks = true;
48042
48945
  }
48043
48946
  if (!nextPrompt) {
48044
48947
  if (stats.toBeWritten > 0) {
48045
- console.info(colors.yellow('No prompts ready for agent.'));
48948
+ const message = 'No prompts ready for agent.';
48949
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
48950
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
48951
+ if (!isUiMode) {
48952
+ console.info(colors.yellow(message));
48953
+ }
48046
48954
  }
48047
48955
  else {
48048
- console.info(colors.green('All prompts are done.'));
48956
+ const message = 'All prompts are done.';
48957
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
48958
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
48959
+ if (!isUiMode) {
48960
+ console.info(colors.green(message));
48961
+ }
48049
48962
  }
48050
48963
  return;
48051
48964
  }
48052
48965
  if (options.waitForUser) {
48966
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
48967
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(hasWaitedForStart ? 'Waiting... Press Enter to continue' : 'Waiting... Press Enter to start');
48053
48968
  await waitForPromptStart(nextPrompt.file, nextPrompt.section, !hasWaitedForStart);
48969
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
48054
48970
  hasWaitedForStart = true;
48055
48971
  }
48056
48972
  if (!options.ignoreGitChanges) {
@@ -48060,29 +48976,57 @@ async function runCodexPrompts(providedOptions) {
48060
48976
  const codexPrompt = appendCoderContext(buildCodexPrompt(nextPrompt.file, nextPrompt.section), resolvedCoderContext);
48061
48977
  const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
48062
48978
  const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
48063
- console.info(colors.blue(`Processing ${promptLabel}`));
48979
+ if (isUiMode) {
48980
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
48981
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('running');
48982
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Running');
48983
+ }
48984
+ else {
48985
+ console.info(colors.blue(`Processing ${promptLabel}`));
48986
+ }
48064
48987
  const promptExecutionStartedDate = moment();
48988
+ let attemptCount = 1;
48065
48989
  const roundChangedFilesSnapshot = options.normalizeLineEndings
48066
48990
  ? await captureChangedFilesSnapshot(process.cwd())
48067
48991
  : undefined;
48068
48992
  try {
48069
- const result = await runner.runPrompt({
48993
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
48994
+ const result = await runPromptWithTestFeedback({
48995
+ runner,
48070
48996
  prompt: codexPrompt,
48071
48997
  scriptPath,
48072
48998
  projectPath: process.cwd(),
48999
+ promptLabel,
49000
+ testCommand: options.testCommand,
49001
+ onAttemptStarted: (nextAttemptCount) => {
49002
+ attemptCount = nextAttemptCount;
49003
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(nextAttemptCount);
49004
+ if (nextAttemptCount > 1) {
49005
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Retrying (attempt ${nextAttemptCount})`);
49006
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('verifying');
49007
+ }
49008
+ },
48073
49009
  });
48074
- markPromptDone(nextPrompt.file, nextPrompt.section, result.usage, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate);
49010
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49011
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Committing changes');
49012
+ markPromptDone(nextPrompt.file, nextPrompt.section, result.usage, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, result.attemptCount);
48075
49013
  await writePromptFile(nextPrompt.file);
48076
49014
  await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
48077
49015
  if (options.waitForUser) {
49016
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
49017
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Waiting... Press Enter to commit');
48078
49018
  printCommitMessage(commitMessage);
48079
49019
  await waitForEnter(colors.bgWhite('Press Enter to commit and continue...'));
49020
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
48080
49021
  }
48081
- await commitChanges(commitMessage, { noPush: options.noPush });
49022
+ await commitChanges(commitMessage, { autoPush: options.autoPush });
48082
49023
  await runPostPromptAutoMigrationIfEnabled(options);
48083
49024
  }
48084
49025
  catch (error) {
48085
- markPromptFailed(nextPrompt.file, nextPrompt.section, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate);
49026
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
49027
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
49028
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
49029
+ markPromptFailed(nextPrompt.file, nextPrompt.section, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate, attemptCount);
48086
49030
  await writePromptFile(nextPrompt.file);
48087
49031
  await writePromptErrorLog({
48088
49032
  file: nextPrompt.file,
@@ -48098,6 +49042,7 @@ async function runCodexPrompts(providedOptions) {
48098
49042
  }
48099
49043
  finally {
48100
49044
  progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.stop();
49045
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.cleanup();
48101
49046
  if (!options.dryRun) {
48102
49047
  printAgentGitIdentityTipIfNeeded();
48103
49048
  }
@@ -48159,24 +49104,26 @@ const SNIPPET_CHAR_LIMIT = 900;
48159
49104
  */
48160
49105
  const MAX_PENDING_FILE_NAMES = 8;
48161
49106
  /**
48162
- * Parse command-line arguments
49107
+ * Default verification options parsed from the current process arguments.
48163
49108
  */
48164
- const REVERSE_ORDER = process.argv.includes('--reverse');
49109
+ const DEFAULT_VERIFY_PROMPTS_OPTIONS = normalizeVerifyPromptsOptions(parseVerifyPromptsCliOptions(process.argv.slice(2)));
48165
49110
  /**
48166
49111
  * Starts the verification loop and exits when no `[ ]` prompts remain.
48167
49112
  *
48168
- * @param reverse - Process files in reverse order
48169
- *
48170
49113
  * @public exported from `@promptbook/cli`
48171
49114
  */
48172
- async function verifyPrompts(reverse = REVERSE_ORDER) {
49115
+ async function verifyPrompts(options = DEFAULT_VERIFY_PROMPTS_OPTIONS) {
49116
+ const normalizedOptions = normalizeVerifyPromptsOptions(options);
48173
49117
  console.info(colors.cyan.bold('📋 Prompt verification helper'));
48174
- if (reverse) {
49118
+ if (normalizedOptions.reverse) {
48175
49119
  console.info(colors.gray('Processing files in reverse order'));
48176
49120
  }
48177
- const initialFiles = await loadPromptFiles(PROMPTS_DIR);
48178
- if (reverse) {
48179
- initialFiles.reverse();
49121
+ if (normalizedOptions.ignore.length > 0) {
49122
+ console.info(colors.gray(`Ignoring candidates matching: ${normalizedOptions.ignore.join(', ')}`));
49123
+ }
49124
+ const { promptFiles: initialFiles, ignoredPromptFiles } = await loadPromptFilesForVerification(normalizedOptions);
49125
+ if (ignoredPromptFiles.length > 0) {
49126
+ console.info(colors.gray(`Ignored ${ignoredPromptFiles.length} prompt file(s) for this run.`));
48180
49127
  }
48181
49128
  displayTopLevelFileList(initialFiles);
48182
49129
  await prepareArchiveDirectory();
@@ -48191,10 +49138,7 @@ async function verifyPrompts(reverse = REVERSE_ORDER) {
48191
49138
  if (wasSkipped) {
48192
49139
  skippedFiles.add(fileWithAllDone.path);
48193
49140
  }
48194
- promptFiles = await loadPromptFiles(PROMPTS_DIR);
48195
- if (REVERSE_ORDER) {
48196
- promptFiles.reverse();
48197
- }
49141
+ promptFiles = (await loadPromptFilesForVerification(normalizedOptions)).promptFiles;
48198
49142
  continue;
48199
49143
  }
48200
49144
  // Second priority: process todo prompts
@@ -48204,11 +49148,56 @@ async function verifyPrompts(reverse = REVERSE_ORDER) {
48204
49148
  break;
48205
49149
  }
48206
49150
  await resolvePrompt(nextPrompt);
48207
- promptFiles = await loadPromptFiles(PROMPTS_DIR);
48208
- if (REVERSE_ORDER) {
48209
- promptFiles.reverse();
49151
+ promptFiles = (await loadPromptFilesForVerification(normalizedOptions)).promptFiles;
49152
+ }
49153
+ }
49154
+ /**
49155
+ * Parses supported command-line arguments for the standalone verification script.
49156
+ */
49157
+ function parseVerifyPromptsCliOptions(args) {
49158
+ return {
49159
+ reverse: args.includes('--reverse'),
49160
+ ignore: readRepeatableStringOption(args, '--ignore'),
49161
+ };
49162
+ }
49163
+ /**
49164
+ * Loads prompt files and applies ordering plus ignore filters for one verification pass.
49165
+ */
49166
+ async function loadPromptFilesForVerification(options) {
49167
+ const loadedPromptFiles = await loadPromptFiles(PROMPTS_DIR);
49168
+ const { promptFiles, ignoredPromptFiles } = partitionPromptFilesByIgnore(loadedPromptFiles, options.ignore);
49169
+ if (options.reverse) {
49170
+ promptFiles.reverse();
49171
+ }
49172
+ return { promptFiles, ignoredPromptFiles };
49173
+ }
49174
+ /**
49175
+ * Splits prompt files into files that should be verified now and files ignored for this run.
49176
+ *
49177
+ * @public exported from `@promptbook/cli`
49178
+ */
49179
+ function partitionPromptFilesByIgnore(promptFiles, ignoreValues) {
49180
+ const normalizedIgnoreValues = normalizeIgnoreValues(ignoreValues);
49181
+ if (normalizedIgnoreValues.length === 0) {
49182
+ return {
49183
+ promptFiles: [...promptFiles],
49184
+ ignoredPromptFiles: [],
49185
+ };
49186
+ }
49187
+ const promptFilesToVerify = [];
49188
+ const ignoredPromptFiles = [];
49189
+ for (const promptFile of promptFiles) {
49190
+ if (matchesIgnoredPromptFile(promptFile, normalizedIgnoreValues)) {
49191
+ ignoredPromptFiles.push(promptFile);
49192
+ }
49193
+ else {
49194
+ promptFilesToVerify.push(promptFile);
48210
49195
  }
48211
49196
  }
49197
+ return {
49198
+ promptFiles: promptFilesToVerify,
49199
+ ignoredPromptFiles,
49200
+ };
48212
49201
  }
48213
49202
  /**
48214
49203
  * Ensures the destination directory for completed prompts exists.
@@ -48216,13 +49205,92 @@ async function verifyPrompts(reverse = REVERSE_ORDER) {
48216
49205
  async function prepareArchiveDirectory() {
48217
49206
  await mkdir(DONE_PROMPTS_DIR, { recursive: true });
48218
49207
  }
49208
+ /**
49209
+ * Normalizes verification options so the rest of the flow can assume stable defaults.
49210
+ */
49211
+ function normalizeVerifyPromptsOptions(options) {
49212
+ var _a, _b;
49213
+ return {
49214
+ reverse: (_a = options.reverse) !== null && _a !== void 0 ? _a : false,
49215
+ ignore: normalizeIgnoreValues((_b = options.ignore) !== null && _b !== void 0 ? _b : []),
49216
+ };
49217
+ }
49218
+ /**
49219
+ * Normalizes ignore values and removes empty or duplicate entries case-insensitively.
49220
+ */
49221
+ function normalizeIgnoreValues(ignoreValues) {
49222
+ const normalizedIgnoreValues = [];
49223
+ const seenIgnoreValues = new Set();
49224
+ for (const ignoreValue of ignoreValues) {
49225
+ const trimmedIgnoreValue = ignoreValue.trim();
49226
+ if (!trimmedIgnoreValue) {
49227
+ continue;
49228
+ }
49229
+ const lowerCasedIgnoreValue = trimmedIgnoreValue.toLowerCase();
49230
+ if (seenIgnoreValues.has(lowerCasedIgnoreValue)) {
49231
+ continue;
49232
+ }
49233
+ seenIgnoreValues.add(lowerCasedIgnoreValue);
49234
+ normalizedIgnoreValues.push(trimmedIgnoreValue);
49235
+ }
49236
+ return normalizedIgnoreValues;
49237
+ }
49238
+ /**
49239
+ * Reads one repeatable string option from raw CLI arguments.
49240
+ */
49241
+ function readRepeatableStringOption(args, flag) {
49242
+ const values = [];
49243
+ for (let index = 0; index < args.length; index += 1) {
49244
+ const argument = args[index];
49245
+ if (!argument) {
49246
+ continue;
49247
+ }
49248
+ if (argument === flag) {
49249
+ const nextValue = args[index + 1];
49250
+ if (nextValue !== undefined && !nextValue.startsWith('--')) {
49251
+ values.push(nextValue);
49252
+ index += 1;
49253
+ }
49254
+ continue;
49255
+ }
49256
+ if (argument.startsWith(`${flag}=`)) {
49257
+ values.push(argument.slice(flag.length + 1));
49258
+ }
49259
+ }
49260
+ return values;
49261
+ }
49262
+ /**
49263
+ * Resolves whether a prompt file should be ignored for this verification run.
49264
+ */
49265
+ function matchesIgnoredPromptFile(promptFile, ignoreValues) {
49266
+ const searchableValues = [promptFile.name, getPromptFileFirstLine(promptFile)].map((value) => value.toLowerCase());
49267
+ return ignoreValues.some((ignoreValue) => {
49268
+ const lowerCasedIgnoreValue = ignoreValue.toLowerCase();
49269
+ return searchableValues.some((searchableValue) => searchableValue.includes(lowerCasedIgnoreValue));
49270
+ });
49271
+ }
49272
+ /**
49273
+ * Returns the first meaningful line of the prompt file after the status line.
49274
+ */
49275
+ function getPromptFileFirstLine(promptFile) {
49276
+ var _a;
49277
+ const firstSection = promptFile.sections[0];
49278
+ const startLineIndex = (firstSection === null || firstSection === void 0 ? void 0 : firstSection.statusLineIndex) !== undefined ? firstSection.statusLineIndex + 1 : ((_a = firstSection === null || firstSection === void 0 ? void 0 : firstSection.startLine) !== null && _a !== void 0 ? _a : 0);
49279
+ for (let index = startLineIndex; index < promptFile.lines.length; index += 1) {
49280
+ const line = promptFile.lines[index];
49281
+ if (line !== undefined && line.trim() !== '') {
49282
+ return line.trim();
49283
+ }
49284
+ }
49285
+ return '';
49286
+ }
48219
49287
  /**
48220
49288
  * Displays the list of files that currently live in the prompts root.
48221
49289
  */
48222
49290
  function displayTopLevelFileList(promptFiles) {
48223
49291
  console.info(colors.cyan('\nTop-level prompt files:'));
48224
49292
  if (!promptFiles.length) {
48225
- console.info(colors.gray(' (no markdown files found in prompts/)'));
49293
+ console.info(colors.gray(' (no prompt markdown files found for this run in prompts/)'));
48226
49294
  return;
48227
49295
  }
48228
49296
  for (const file of promptFiles) {
@@ -48539,7 +49607,7 @@ function isNotFound(error) {
48539
49607
  }
48540
49608
  // Note: When run as a standalone script, call the exported function
48541
49609
  if (require.main === module) {
48542
- verifyPrompts(REVERSE_ORDER).catch((error) => {
49610
+ verifyPrompts(DEFAULT_VERIFY_PROMPTS_OPTIONS).catch((error) => {
48543
49611
  console.error(colors.bgRed('Prompt verification failed:'), error);
48544
49612
  process.exit(1);
48545
49613
  });
@@ -48547,7 +49615,8 @@ if (require.main === module) {
48547
49615
 
48548
49616
  var verifyPrompts$1 = /*#__PURE__*/Object.freeze({
48549
49617
  __proto__: null,
48550
- verifyPrompts: verifyPrompts
49618
+ verifyPrompts: verifyPrompts,
49619
+ partitionPromptFilesByIgnore: partitionPromptFilesByIgnore
48551
49620
  });
48552
49621
 
48553
49622
  /**
@@ -49596,7 +50665,7 @@ function promptbookifyAiText(text) {
49596
50665
  /**
49597
50666
  * Constant for default agent kit model name.
49598
50667
  */
49599
- const DEFAULT_AGENT_KIT_MODEL_NAME = 'gpt-5.4-nano';
50668
+ const DEFAULT_AGENT_KIT_MODEL_NAME = 'gpt-5.4-mini';
49600
50669
  /**
49601
50670
  * Creates one structured log entry for streamed tool-call updates.
49602
50671
  *
@@ -50646,6 +51715,7 @@ class AgentLlmExecutionTools {
50646
51715
  * @param agentSource The agent source string that defines the agent's behavior
50647
51716
  */
50648
51717
  constructor(options) {
51718
+ var _a;
50649
51719
  this.options = options;
50650
51720
  /**
50651
51721
  * Cached model requirements to avoid re-parsing the agent source
@@ -50655,6 +51725,7 @@ class AgentLlmExecutionTools {
50655
51725
  * Cached parsed agent information
50656
51726
  */
50657
51727
  this._cachedAgentInfo = null;
51728
+ this.precomputedModelRequirements = (_a = options.precomputedModelRequirements) !== null && _a !== void 0 ? _a : null;
50658
51729
  }
50659
51730
  /**
50660
51731
  * Updates the agent source and clears the cache
@@ -50662,9 +51733,13 @@ class AgentLlmExecutionTools {
50662
51733
  * @param agentSource The new agent source string
50663
51734
  */
50664
51735
  updateAgentSource(agentSource) {
51736
+ if (this.options.agentSource === agentSource) {
51737
+ return;
51738
+ }
50665
51739
  this.options.agentSource = agentSource;
50666
51740
  this._cachedAgentInfo = null;
50667
51741
  this._cachedModelRequirements = null;
51742
+ this.precomputedModelRequirements = null;
50668
51743
  }
50669
51744
  /**
50670
51745
  * Get cached or parse agent information
@@ -50681,6 +51756,16 @@ class AgentLlmExecutionTools {
50681
51756
  * Note: [🐤] This is names `getModelRequirements` *(not `getAgentModelRequirements`)* because in future these two will be united
50682
51757
  */
50683
51758
  async getModelRequirements() {
51759
+ var _a, _b;
51760
+ if (this.precomputedModelRequirements !== null) {
51761
+ if (this.options.isVerbose) {
51762
+ console.info('[🤰]', 'Using precomputed agent model requirements', {
51763
+ agent: this.title,
51764
+ toolCount: (_b = (_a = this.precomputedModelRequirements.tools) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0,
51765
+ });
51766
+ }
51767
+ return this.precomputedModelRequirements;
51768
+ }
50684
51769
  if (this._cachedModelRequirements === null) {
50685
51770
  const preparationStartedAtMs = Date.now();
50686
51771
  if (this.options.isVerbose) {
@@ -50790,6 +51875,7 @@ class AgentLlmExecutionTools {
50790
51875
  * Resolves agent requirements, attachments, and runtime overrides into one forwarded chat prompt.
50791
51876
  */
50792
51877
  async prepareChatPrompt(prompt) {
51878
+ var _a;
50793
51879
  const chatPrompt = this.requireChatPrompt(prompt);
50794
51880
  const { sanitizedRequirements, promptSuffix } = await this.getSanitizedAgentModelRequirements();
50795
51881
  const attachments = normalizeChatAttachments(chatPrompt.attachments);
@@ -50807,7 +51893,16 @@ class AgentLlmExecutionTools {
50807
51893
  mergedTools,
50808
51894
  knowledgeSourcesForAgent,
50809
51895
  });
50810
- console.log('!!!! promptWithAgentModelRequirements:', forwardedPrompt);
51896
+ if (this.options.isVerbose) {
51897
+ console.info('[🤰]', 'Prepared agent chat prompt', {
51898
+ agent: this.title,
51899
+ usedPrecomputedModelRequirements: this.precomputedModelRequirements !== null,
51900
+ toolNames: mergedTools.map((tool) => tool.name),
51901
+ knowledgeSourcesCount: (_a = knowledgeSourcesForAgent === null || knowledgeSourcesForAgent === void 0 ? void 0 : knowledgeSourcesForAgent.length) !== null && _a !== void 0 ? _a : 0,
51902
+ promptSuffixLength: promptSuffix.length,
51903
+ systemMessageLength: sanitizedRequirements.systemMessage.length,
51904
+ });
51905
+ }
50811
51906
  return {
50812
51907
  forwardedPrompt,
50813
51908
  sanitizedRequirements,
@@ -50994,6 +52089,7 @@ class AgentLlmExecutionTools {
50994
52089
  * Runs one prepared prompt through the deprecated OpenAI Assistant backend.
50995
52090
  */
50996
52091
  async callOpenAiAssistantChatModelStream(options) {
52092
+ var _a, _b, _c, _d;
50997
52093
  const assistant = await this.getOrPrepareOpenAiAssistant({
50998
52094
  llmTools: options.llmTools,
50999
52095
  originalPrompt: options.originalPrompt,
@@ -51001,7 +52097,14 @@ class AgentLlmExecutionTools {
51001
52097
  onProgress: options.onProgress,
51002
52098
  });
51003
52099
  const promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools = createOpenAiAssistantPrompt(options.preparedChatPrompt.forwardedPrompt);
51004
- console.log('!!!! promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools:', promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools);
52100
+ if (this.options.isVerbose) {
52101
+ console.info('[🤰]', 'Prepared OpenAI Assistant prompt', {
52102
+ agent: this.title,
52103
+ toolNames: (_b = (_a = promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools.modelRequirements.tools) === null || _a === void 0 ? void 0 : _a.map((tool) => tool.name)) !== null && _b !== void 0 ? _b : [],
52104
+ knowledgeSourcesCount: (_d = (_c = promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools.modelRequirements
52105
+ .knowledgeSources) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0,
52106
+ });
52107
+ }
51005
52108
  return assistant.callChatModelStream(promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools, options.onProgress, options.streamOptions);
51006
52109
  }
51007
52110
  /**
@@ -51692,7 +52795,8 @@ class Agent extends AgentLlmExecutionTools {
51692
52795
  isVerbose: options.isVerbose,
51693
52796
  llmTools: getSingleLlmExecutionTools(options.executionTools.llm),
51694
52797
  assistantPreparationMode: options.assistantPreparationMode,
51695
- agentSource: agentSource.value, // <- TODO: [🐱‍🚀] Allow to pass BehaviorSubject<string_book> OR refresh llmExecutionTools.callChat on agentSource change
52798
+ agentSource: agentSource.value,
52799
+ precomputedModelRequirements: options.precomputedModelRequirements,
51696
52800
  });
51697
52801
  this._agentName = undefined;
51698
52802
  /**