@promptbook/cli 0.112.0-42 → 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 (44) hide show
  1. package/README.md +10 -3
  2. package/esm/index.es.js +1030 -86
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/scripts/run-codex-prompts/common/formatUnknownErrorDetails.d.ts +4 -0
  5. package/esm/scripts/run-codex-prompts/common/waitForPause.d.ts +21 -1
  6. package/esm/scripts/run-codex-prompts/git/commitChanges.d.ts +2 -2
  7. package/esm/scripts/run-codex-prompts/prompts/formatPromptAttemptMetadata.d.ts +4 -0
  8. package/esm/scripts/run-codex-prompts/prompts/markPromptDone.d.ts +1 -1
  9. package/esm/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +1 -1
  10. package/esm/scripts/run-codex-prompts/testing/runPromptTestCommand.d.ts +13 -0
  11. package/esm/scripts/run-codex-prompts/testing/runPromptWithTestFeedback.d.ts +25 -0
  12. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +112 -0
  13. package/esm/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +30 -0
  14. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +1 -1
  15. package/esm/src/cli/cli-commands/coder/getTypescriptModule.d.ts +19 -0
  16. package/esm/src/cli/cli-commands/coder/getTypescriptModule.test.d.ts +1 -0
  17. package/esm/src/cli/cli-commands/coder/mergeStringRecordJsonFile.test.d.ts +1 -0
  18. package/esm/src/llm-providers/agent/Agent.test.d.ts +1 -0
  19. package/esm/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +4 -0
  20. package/esm/src/llm-providers/agent/AgentOptions.d.ts +8 -0
  21. package/esm/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +9 -0
  22. package/esm/src/version.d.ts +1 -1
  23. package/package.json +3 -2
  24. package/umd/index.umd.js +1033 -90
  25. package/umd/index.umd.js.map +1 -1
  26. package/umd/scripts/run-codex-prompts/common/formatUnknownErrorDetails.d.ts +4 -0
  27. package/umd/scripts/run-codex-prompts/common/waitForPause.d.ts +21 -1
  28. package/umd/scripts/run-codex-prompts/git/commitChanges.d.ts +2 -2
  29. package/umd/scripts/run-codex-prompts/prompts/formatPromptAttemptMetadata.d.ts +4 -0
  30. package/umd/scripts/run-codex-prompts/prompts/markPromptDone.d.ts +1 -1
  31. package/umd/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +1 -1
  32. package/umd/scripts/run-codex-prompts/testing/runPromptTestCommand.d.ts +13 -0
  33. package/umd/scripts/run-codex-prompts/testing/runPromptWithTestFeedback.d.ts +25 -0
  34. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +112 -0
  35. package/umd/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +30 -0
  36. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +1 -1
  37. package/umd/src/cli/cli-commands/coder/getTypescriptModule.d.ts +19 -0
  38. package/umd/src/cli/cli-commands/coder/getTypescriptModule.test.d.ts +1 -0
  39. package/umd/src/cli/cli-commands/coder/mergeStringRecordJsonFile.test.d.ts +1 -0
  40. package/umd/src/llm-providers/agent/Agent.test.d.ts +1 -0
  41. package/umd/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +4 -0
  42. package/umd/src/llm-providers/agent/AgentOptions.d.ts +8 -0
  43. package/umd/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +9 -0
  44. package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('fs'), require('path'), require('fs/promises'), require('waitasecond'), require('prompts'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('socket.io-client'), require('crypto-js'), require('jszip'), require('crypto'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('glob-promise'), require('moment'), require('express'), require('express-openapi-validator'), require('http'), require('socket.io'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('typescript'), require('ignore'), require('readline'), require('child_process'), require('pg'), require('@supabase/supabase-js'), require('path/posix'), require('rxjs'), require('mime-types'), require('papaparse'), require('@openai/agents'), require('openai')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'fs', 'path', 'fs/promises', 'waitasecond', 'prompts', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'socket.io-client', 'crypto-js', 'jszip', 'crypto', '@mozilla/readability', 'jsdom', 'showdown', 'glob-promise', 'moment', 'express', 'express-openapi-validator', 'http', 'socket.io', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'typescript', 'ignore', 'readline', 'child_process', 'pg', '@supabase/supabase-js', 'path/posix', 'rxjs', 'mime-types', 'papaparse', '@openai/agents', 'openai'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global._spaceTrim, global.fs, global.path, global.promises, global.waitasecond, global.prompts, global.dotenv, global.hexEncoder, global.sha256, global.socket_ioClient, global.cryptoJs, global.JSZip, global.crypto, global.readability, global.jsdom, global.showdown, global.glob, global.moment, global.express, global.OpenApiValidator, global.http, global.socket_io, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.ts, global.ignore, global.readline, global.child_process, global.pg, null, global.posix, global.rxjs, global.mimeTypes, global.papaparse, global.agents, global.OpenAI));
5
- })(this, (function (exports, colors, commander, _spaceTrim, fs, path, promises, waitasecond, prompts, dotenv, hexEncoder, sha256, socket_ioClient, cryptoJs, JSZip, crypto, readability, jsdom, showdown, glob, moment, express, OpenApiValidator, http, socket_io, swaggerUi, react, server, Anthropic, Bottleneck, openai, ts, ignore, readline, child_process, pg, supabaseJs, posix, rxjs, mimeTypes, papaparse, agents, OpenAI) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('fs'), require('path'), require('fs/promises'), require('waitasecond'), require('prompts'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('socket.io-client'), require('crypto-js'), require('jszip'), require('crypto'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('glob-promise'), require('moment'), require('express'), require('express-openapi-validator'), require('http'), require('socket.io'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('typescript'), require('ignore'), require('readline'), require('child_process'), require('pg'), require('@supabase/supabase-js'), require('path/posix'), require('events'), require('rxjs'), require('mime-types'), require('papaparse'), require('@openai/agents'), require('openai')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'fs', 'path', 'fs/promises', 'waitasecond', 'prompts', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'socket.io-client', 'crypto-js', 'jszip', 'crypto', '@mozilla/readability', 'jsdom', 'showdown', 'glob-promise', 'moment', 'express', 'express-openapi-validator', 'http', 'socket.io', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'typescript', 'ignore', 'readline', 'child_process', 'pg', '@supabase/supabase-js', 'path/posix', 'events', 'rxjs', 'mime-types', 'papaparse', '@openai/agents', 'openai'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global._spaceTrim, global.fs, global.path, global.promises, global.waitasecond, global.prompts, global.dotenv, global.hexEncoder, global.sha256, global.socket_ioClient, global.cryptoJs, global.JSZip, global.crypto, global.readability, global.jsdom, global.showdown, global.glob, global.moment, global.express, global.OpenApiValidator, global.http, global.socket_io, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.ts, global.ignore, global.readline, global.child_process, global.pg, null, global.posix, global.events, global.rxjs, global.mimeTypes, global.papaparse, global.agents, global.OpenAI));
5
+ })(this, (function (exports, colors, commander, _spaceTrim, fs, path, promises, waitasecond, prompts, dotenv, hexEncoder, sha256, socket_ioClient, cryptoJs, JSZip, crypto, readability, jsdom, showdown, glob, moment, express, OpenApiValidator, http, socket_io, swaggerUi, react, server, Anthropic, Bottleneck, openai, ts, ignore, readline, child_process, pg, supabaseJs, posix, events, rxjs, mimeTypes, papaparse, agents, OpenAI) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
@@ -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-42';
63
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-43';
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
@@ -2323,7 +2323,7 @@
2323
2323
  '',
2324
2324
  '## Customizing the workflow',
2325
2325
  '- Edit `package.json` if you want `npm run coder:run` to use another coding agent, model, thinking level, context file, or wait mode.',
2326
- '- Use direct CLI commands when you need one-off flags such as `--priority`, `--ignore-git-changes`, `--dry-run`, `--allow-credits`, or `--auto-migrate`.',
2326
+ '- Use direct CLI commands when you need one-off flags such as `--priority`, `--ignore-git-changes`, `--dry-run`, `--test`, `--allow-credits`, or `--auto-migrate`.',
2327
2327
  '- Use `npx ptbk coder --help` and `npx ptbk coder <command> --help` for the full CLI reference.',
2328
2328
  ].join('\n');
2329
2329
  }
@@ -2583,6 +2583,26 @@
2583
2583
  }
2584
2584
  // TODO: Maybe split `ParseError` and `ApplyError`
2585
2585
 
2586
+ /**
2587
+ * Loads the TypeScript runtime used for parsing JSONC-style project files.
2588
+ *
2589
+ * @private internal utility of `coder init`
2590
+ */
2591
+ async function getTypescriptModule() {
2592
+ return normalizeImportedTypescriptModule((await import('typescript')));
2593
+ }
2594
+ /**
2595
+ * Normalizes CommonJS-via-`default` and direct namespace imports of TypeScript.
2596
+ *
2597
+ * @private internal utility of `getTypescriptModule`
2598
+ */
2599
+ function normalizeImportedTypescriptModule(importedTypescriptModule) {
2600
+ return 'parseConfigFileTextToJson' in importedTypescriptModule
2601
+ ? importedTypescriptModule
2602
+ : importedTypescriptModule.default;
2603
+ }
2604
+ // Note: [🟡] Code for coder init TypeScript loading [getTypescriptModule](src/cli/cli-commands/coder/getTypescriptModule.ts) should never be published outside of `@promptbook/cli`
2605
+
2586
2606
  /**
2587
2607
  * Default indentation used when creating new JSON configuration files.
2588
2608
  */
@@ -2628,7 +2648,7 @@
2628
2648
  if (fileContent.trim() === '') {
2629
2649
  return {};
2630
2650
  }
2631
- const typescript = await import('typescript');
2651
+ const typescript = await getTypescriptModule();
2632
2652
  const parsedFile = typescript.parseConfigFileTextToJson(relativeFilePath, fileContent);
2633
2653
  if (parsedFile.error) {
2634
2654
  throw new ParseError(_spaceTrim.spaceTrim(`
@@ -2986,7 +3006,9 @@
2986
3006
 
2987
3007
  Features:
2988
3008
  - Automatically stages and commits changes with agent identity
3009
+ - Optional post-commit git push with explicit --auto-push opt-in
2989
3010
  - Supports GPG signing of commits
3011
+ - Optional post-prompt verification with test-feedback retries
2990
3012
  - Progress tracking and interactive controls
2991
3013
  - Dry-run mode to preview prompts
2992
3014
  `));
@@ -2999,17 +3021,19 @@
2999
3021
  Gemini examples: gemini-3-flash-preview, default
3000
3022
  `));
3001
3023
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
3024
+ command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
3002
3025
  command.addOption(new commander.Option('--thinking-level <thinking-level>', `Set reasoning effort for supported runners (${THINKING_LEVEL_VALUES.join(', ')})`).choices([...THINKING_LEVEL_VALUES]));
3003
3026
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
3004
3027
  command.option('--no-wait', 'Skip user prompts between processing');
3005
3028
  command.option('--ignore-git-changes', 'Skip clean working tree check before running prompts', false);
3006
3029
  command.option('--allow-credits', 'Allow OpenAI Codex runner to spend credits when rate limits are exhausted', false);
3007
3030
  command.option('--no-normalize-line-endings', 'Disable automatic LF normalization for files changed in each coding round');
3008
- command.option('--no-push', 'Disable automatic git push after each commit');
3031
+ command.option('--auto-push', 'Automatically git push after each commit', false);
3009
3032
  command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
3010
3033
  command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
3011
3034
  command.action(handleActionErrors(async (cliOptions) => {
3012
- const { dryRun, agent, model, context, thinkingLevel, priority, wait, ignoreGitChanges, allowCredits, normalizeLineEndings, autoMigrate, allowDestructiveAutoMigrate, push, } = cliOptions;
3035
+ const { dryRun, agent, model, context, test, thinkingLevel, priority, wait, ignoreGitChanges, allowCredits, normalizeLineEndings, autoMigrate, allowDestructiveAutoMigrate, autoPush, } = cliOptions;
3036
+ const testCommand = normalizeCommandOptionValue(test);
3013
3037
  // Validate agent
3014
3038
  let agentName = undefined;
3015
3039
  if (agent) {
@@ -3038,13 +3062,14 @@
3038
3062
  agentName,
3039
3063
  model,
3040
3064
  context,
3065
+ testCommand,
3041
3066
  thinkingLevel,
3042
3067
  priority,
3043
3068
  normalizeLineEndings,
3044
3069
  allowCredits,
3045
3070
  autoMigrate,
3046
3071
  allowDestructiveAutoMigrate,
3047
- noPush: !push,
3072
+ autoPush,
3048
3073
  };
3049
3074
  // Note: Import the function dynamically to avoid loading heavy dependencies until needed
3050
3075
  const { runCodexPrompts } = await Promise.resolve().then(function () { return runCodexPrompts$1; });
@@ -3073,6 +3098,19 @@
3073
3098
  }
3074
3099
  return parsed;
3075
3100
  }
3101
+ /**
3102
+ * Joins one Commander option that may be parsed either as a single string or a variadic token array.
3103
+ *
3104
+ * @private internal utility of `coder run` command
3105
+ */
3106
+ function normalizeCommandOptionValue(value) {
3107
+ if (value === undefined) {
3108
+ return undefined;
3109
+ }
3110
+ const parts = Array.isArray(value) ? value : [value];
3111
+ const normalizedValue = parts.map((part) => part.trim()).filter(Boolean).join(' ').trim();
3112
+ return normalizedValue === '' ? undefined : normalizedValue;
3113
+ }
3076
3114
  // Note: [🟡] Code for CLI command [run](src/cli/cli-commands/coder/run.ts) should never be published outside of `@promptbook/cli`
3077
3115
  // Note: [💞] Ignore a discrepancy between file name and entity name
3078
3116
 
@@ -22559,6 +22597,15 @@
22559
22597
  * Map of team tool titles.
22560
22598
  */
22561
22599
  const teamToolTitles = {};
22600
+ /**
22601
+ * Shared TEAM usage rules appended ahead of teammate listings.
22602
+ *
22603
+ * @private
22604
+ */
22605
+ const TEAM_SYSTEM_MESSAGE_GUIDANCE_LINES = [
22606
+ '- If a teammate is relevant to the request, consult that teammate using the matching tool.',
22607
+ '- Do not ask the user for information that a listed teammate can provide directly.',
22608
+ ];
22562
22609
  /**
22563
22610
  * Constant for remote agents by Url.
22564
22611
  */
@@ -22648,12 +22695,9 @@
22648
22695
  if (updatedTools.some((tool) => tool.name === entry.toolName)) {
22649
22696
  continue;
22650
22697
  }
22651
- const toolDescription = entry.description
22652
- ? `Consult teammate ${entry.teammate.label}\n${entry.description}`
22653
- : `Consult teammate ${entry.teammate.label}`;
22654
22698
  updatedTools.push({
22655
22699
  name: entry.toolName,
22656
- description: toolDescription,
22700
+ description: buildTeamToolDescription(entry),
22657
22701
  parameters: {
22658
22702
  type: 'object',
22659
22703
  properties: {
@@ -22722,22 +22766,72 @@
22722
22766
  /**
22723
22767
  * Builds the textual TEAM section body for the final system message.
22724
22768
  *
22725
- * Each teammate is listed with its tool name and, when available, a one-line description.
22726
- * Uses `spaceTrim` to ensure consistent whitespace and indentation.
22769
+ * Each teammate is listed with its tool name, TEAM instructions, and optional profile hints.
22727
22770
  */
22728
22771
  function buildTeamSystemMessageBody(teamEntries) {
22729
- const lines = teamEntries.map((entry, index) => {
22730
- const toolLine = `${index + 1}) ${entry.teammate.label} tool \`${entry.toolName}\``;
22731
- if (!entry.description) {
22732
- return toolLine;
22733
- }
22734
- return _spaceTrim.spaceTrim(`
22735
- ${toolLine}
22736
- ${entry.description}
22737
- `);
22738
- });
22772
+ const lines = [
22773
+ ...TEAM_SYSTEM_MESSAGE_GUIDANCE_LINES,
22774
+ '',
22775
+ ...teamEntries.map((entry, index) => {
22776
+ const toolLine = `${index + 1}) ${entry.teammate.label} tool \`${entry.toolName}\``;
22777
+ const detailLines = collectTeamEntryDetails(entry).map(formatTeamEntryDetailLine);
22778
+ return [toolLine, ...detailLines].join('\n');
22779
+ }),
22780
+ ];
22739
22781
  return lines.join('\n');
22740
22782
  }
22783
+ /**
22784
+ * Builds the model-visible description for one teammate tool.
22785
+ *
22786
+ * @private
22787
+ */
22788
+ function buildTeamToolDescription(entry) {
22789
+ const detailLines = collectTeamEntryDetails(entry).map(({ label, content }) => `${label}: ${content}`);
22790
+ return [`Consult teammate ${entry.teammate.label}`, ...detailLines].join('\n');
22791
+ }
22792
+ /**
22793
+ * Collects structured teammate details that should stay visible to the model.
22794
+ *
22795
+ * @private
22796
+ */
22797
+ function collectTeamEntryDetails(entry) {
22798
+ var _a;
22799
+ const details = [];
22800
+ const instructions = entry.teammate.instructions.trim();
22801
+ const description = ((_a = entry.description) === null || _a === void 0 ? void 0 : _a.trim()) || '';
22802
+ if (instructions) {
22803
+ details.push({
22804
+ label: 'TEAM instructions',
22805
+ content: instructions,
22806
+ });
22807
+ }
22808
+ if (description) {
22809
+ details.push({
22810
+ label: 'Profile',
22811
+ content: description,
22812
+ });
22813
+ }
22814
+ return details;
22815
+ }
22816
+ /**
22817
+ * Formats one teammate detail line for the TEAM system-message section.
22818
+ *
22819
+ * @private
22820
+ */
22821
+ function formatTeamEntryDetailLine(detail) {
22822
+ return indentMultilineText(`${detail.label}: ${detail.content}`, ' ');
22823
+ }
22824
+ /**
22825
+ * Indents all lines of one potentially multi-line text block.
22826
+ *
22827
+ * @private
22828
+ */
22829
+ function indentMultilineText(text, prefix) {
22830
+ return text
22831
+ .split('\n')
22832
+ .map((line) => `${prefix}${line}`)
22833
+ .join('\n');
22834
+ }
22741
22835
  /**
22742
22836
  * Registers tool function and title for a teammate tool.
22743
22837
  */
@@ -40910,7 +41004,7 @@
40910
41004
  systemMessage: '',
40911
41005
  promptSuffix: '',
40912
41006
  // modelName: 'gpt-5',
40913
- modelName: 'gemini-2.5-flash-lite',
41007
+ modelName: 'gpt-5.4-mini',
40914
41008
  temperature: 0.7,
40915
41009
  topP: 0.9,
40916
41010
  topK: 50,
@@ -44090,7 +44184,26 @@
44090
44184
  /**
44091
44185
  * CLI usage text for this script.
44092
44186
  */
44093
- 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]';
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]';
44188
+ /**
44189
+ * Top-level flags supported by this command.
44190
+ */
44191
+ const KNOWN_OPTION_FLAGS = new Set([
44192
+ '--dry-run',
44193
+ '--agent',
44194
+ '--model',
44195
+ '--context',
44196
+ '--test',
44197
+ '--thinking-level',
44198
+ '--priority',
44199
+ '--allow-credits',
44200
+ '--auto-migrate',
44201
+ '--allow-destructive-auto-migrate',
44202
+ '--no-wait',
44203
+ '--ignore-git-changes',
44204
+ '--no-normalize-line-endings',
44205
+ '--auto-push',
44206
+ ]);
44094
44207
  /**
44095
44208
  * Parses CLI arguments into runner options.
44096
44209
  */
@@ -44111,6 +44224,8 @@
44111
44224
  }
44112
44225
  const model = readOptionValue(args, '--model');
44113
44226
  const context = readOptionValue(args, '--context');
44227
+ const hasTestCommandFlag = args.includes('--test');
44228
+ const testCommand = readVariadicOptionValue(args, '--test');
44114
44229
  const hasThinkingLevelFlag = args.includes('--thinking-level');
44115
44230
  const thinkingLevelValue = readOptionValue(args, '--thinking-level');
44116
44231
  const hasPriorityFlag = args.includes('--priority');
@@ -44120,8 +44235,11 @@
44120
44235
  const allowCredits = args.includes('--allow-credits');
44121
44236
  const autoMigrate = args.includes('--auto-migrate');
44122
44237
  const allowDestructiveAutoMigrate = args.includes('--allow-destructive-auto-migrate');
44123
- const noPush = args.includes('--no-push');
44238
+ const autoPush = args.includes('--auto-push');
44124
44239
  let thinkingLevel;
44240
+ if (hasTestCommandFlag && testCommand === undefined) {
44241
+ exitWithUsageError('Missing value for --test. Use a shell command such as `npm run test` and quote it when it contains top-level CLI flags.');
44242
+ }
44125
44243
  if (hasThinkingLevelFlag && thinkingLevelValue === undefined) {
44126
44244
  exitWithUsageError(`Missing value for --thinking-level. Use one of: ${THINKING_LEVEL_VALUES.join(', ')}.`);
44127
44245
  }
@@ -44142,10 +44260,11 @@
44142
44260
  allowCredits,
44143
44261
  autoMigrate,
44144
44262
  allowDestructiveAutoMigrate,
44145
- noPush,
44263
+ autoPush,
44146
44264
  agentName,
44147
44265
  model,
44148
44266
  context,
44267
+ testCommand,
44149
44268
  thinkingLevel,
44150
44269
  priority,
44151
44270
  };
@@ -44160,6 +44279,28 @@
44160
44279
  const index = args.indexOf(flag);
44161
44280
  return args[index + 1];
44162
44281
  }
44282
+ /**
44283
+ * Reads a multi-token shell command value that follows a given flag.
44284
+ */
44285
+ function readVariadicOptionValue(args, flag) {
44286
+ if (!args.includes(flag)) {
44287
+ return undefined;
44288
+ }
44289
+ const index = args.indexOf(flag);
44290
+ const valueParts = [];
44291
+ for (let i = index + 1; i < args.length; i++) {
44292
+ const valuePart = args[i];
44293
+ if (valuePart === undefined) {
44294
+ continue;
44295
+ }
44296
+ if (KNOWN_OPTION_FLAGS.has(valuePart)) {
44297
+ break;
44298
+ }
44299
+ valueParts.push(valuePart);
44300
+ }
44301
+ const normalizedValue = valueParts.join(' ').trim();
44302
+ return normalizedValue === '' ? undefined : normalizedValue;
44303
+ }
44163
44304
  /**
44164
44305
  * Parses and validates the minimum prompt priority.
44165
44306
  */
@@ -44185,6 +44326,21 @@
44185
44326
  process.exit(1);
44186
44327
  }
44187
44328
 
44329
+ /**
44330
+ * Appends optional coding context to a runner prompt.
44331
+ */
44332
+ function appendCoderContext(prompt, context) {
44333
+ const normalizedContext = context === null || context === void 0 ? void 0 : context.trim();
44334
+ if (!normalizedContext) {
44335
+ return prompt;
44336
+ }
44337
+ const normalizedPrompt = prompt.trimEnd();
44338
+ if (normalizedPrompt === '') {
44339
+ return normalizedContext;
44340
+ }
44341
+ return `${normalizedPrompt}\n\n${normalizedContext}`;
44342
+ }
44343
+
44188
44344
  /**
44189
44345
  * Refresh interval for the progress header in milliseconds.
44190
44346
  */
@@ -44196,7 +44352,7 @@
44196
44352
  /**
44197
44353
  * Calendar formats used when displaying the estimated completion time.
44198
44354
  */
44199
- const ESTIMATED_DONE_CALENDAR_FORMATS = {
44355
+ const ESTIMATED_DONE_CALENDAR_FORMATS$1 = {
44200
44356
  sameDay: '[Today] h:mm',
44201
44357
  nextDay: '[Tomorrow] h:mm',
44202
44358
  nextWeek: 'dddd h:mm',
@@ -44295,15 +44451,15 @@
44295
44451
  const sessionTotal = sessionDone + stats.forAgent;
44296
44452
  const percentage = totalPrompts > 0 ? Math.round((completedPrompts / totalPrompts) * 100) : 0;
44297
44453
  const elapsedDuration = moment__default["default"].duration(moment__default["default"]().diff(startTime));
44298
- const elapsedText = formatDurationBrief(elapsedDuration);
44454
+ const elapsedText = formatDurationBrief$1(elapsedDuration);
44299
44455
  let estimatedTotalText = '—';
44300
44456
  let estimatedLabel = 'unknown';
44301
44457
  if (totalPrompts > 0 && completedPrompts > 0) {
44302
44458
  const estimatedTotalMs = (elapsedDuration.asMilliseconds() * totalPrompts) / completedPrompts;
44303
44459
  const estimatedTotalDuration = moment__default["default"].duration(estimatedTotalMs);
44304
44460
  const estimatedCompletion = startTime.clone().add(estimatedTotalDuration);
44305
- estimatedTotalText = formatDurationBrief(estimatedTotalDuration);
44306
- estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS);
44461
+ estimatedTotalText = formatDurationBrief$1(estimatedTotalDuration);
44462
+ estimatedLabel = estimatedCompletion.calendar(null, ESTIMATED_DONE_CALENDAR_FORMATS$1);
44307
44463
  }
44308
44464
  return {
44309
44465
  totalPrompts,
@@ -44319,7 +44475,7 @@
44319
44475
  /**
44320
44476
  * Formats a duration into a compact string such as "3h 12m" or "45s".
44321
44477
  */
44322
- function formatDurationBrief(duration) {
44478
+ function formatDurationBrief$1(duration) {
44323
44479
  const totalSeconds = Math.max(0, Math.round(duration.asSeconds()));
44324
44480
  const hours = Math.floor(totalSeconds / 3600);
44325
44481
  const minutes = Math.floor((totalSeconds % 3600) / 60);
@@ -44340,21 +44496,6 @@
44340
44496
  return parts.join(' ');
44341
44497
  }
44342
44498
 
44343
- /**
44344
- * Appends optional coding context to a runner prompt.
44345
- */
44346
- function appendCoderContext(prompt, context) {
44347
- const normalizedContext = context === null || context === void 0 ? void 0 : context.trim();
44348
- if (!normalizedContext) {
44349
- return prompt;
44350
- }
44351
- const normalizedPrompt = prompt.trimEnd();
44352
- if (normalizedPrompt === '') {
44353
- return normalizedContext;
44354
- }
44355
- return `${normalizedPrompt}\n\n${normalizedContext}`;
44356
- }
44357
-
44358
44499
  /**
44359
44500
  * Git commands used to list changed and untracked files in the working tree.
44360
44501
  */
@@ -44624,7 +44765,7 @@
44624
44765
  }
44625
44766
 
44626
44767
  /**
44627
- * Constant for pause state.
44768
+ * Current pause state.
44628
44769
  */
44629
44770
  let pauseState = 'RUNNING';
44630
44771
  /**
@@ -44661,17 +44802,48 @@
44661
44802
  }
44662
44803
  /**
44663
44804
  * If the execution is paused, it will wait until it is resumed.
44805
+ *
44806
+ * @param options.silent - When `true`, suppresses console output (used when the terminal UI handles display).
44807
+ * @param options.onPaused - Callback invoked when entering the PAUSED state.
44808
+ * @param options.onResumed - Callback invoked when leaving the PAUSED state.
44664
44809
  */
44665
- async function checkPause() {
44810
+ async function checkPause(options) {
44811
+ var _a, _b;
44666
44812
  if (pauseState === 'PAUSING') {
44667
44813
  pauseState = 'PAUSED';
44668
- console.log(colors__default["default"].bgWhite.black('Paused') + colors__default["default"].gray(' (Press "p" to resume)'));
44814
+ if (!(options === null || options === void 0 ? void 0 : options.silent)) {
44815
+ console.log(colors__default["default"].bgWhite.black('Paused') + colors__default["default"].gray(' (Press "p" to resume)'));
44816
+ }
44817
+ (_a = options === null || options === void 0 ? void 0 : options.onPaused) === null || _a === void 0 ? void 0 : _a.call(options);
44669
44818
  while (pauseState === 'PAUSED') {
44670
44819
  await new Promise((resolve) => setTimeout(resolve, 100));
44671
44820
  }
44672
- console.log(colors__default["default"].green('Resuming...'));
44821
+ (_b = options === null || options === void 0 ? void 0 : options.onResumed) === null || _b === void 0 ? void 0 : _b.call(options);
44822
+ if (!(options === null || options === void 0 ? void 0 : options.silent)) {
44823
+ console.log(colors__default["default"].green('Resuming...'));
44824
+ }
44673
44825
  }
44674
44826
  }
44827
+ /**
44828
+ * Returns the current pause state for external consumers such as the terminal UI.
44829
+ */
44830
+ function getPauseState() {
44831
+ return pauseState;
44832
+ }
44833
+ /**
44834
+ * Requests a pause from an external controller (e.g. the Ink UI).
44835
+ */
44836
+ function requestPause() {
44837
+ if (pauseState === 'RUNNING') {
44838
+ pauseState = 'PAUSING';
44839
+ }
44840
+ }
44841
+ /**
44842
+ * Resumes execution from an external controller after a pause.
44843
+ */
44844
+ function requestResume() {
44845
+ pauseState = 'RUNNING';
44846
+ }
44675
44847
 
44676
44848
  /**
44677
44849
  * Environment variable that configures the name used for agent commits.
@@ -45016,7 +45188,7 @@
45016
45188
 
45017
45189
  /**
45018
45190
  * Commits staged changes with the provided message using the dedicated coding-agent identity when configured,
45019
- * otherwise falls back to the default Git configuration.
45191
+ * otherwise falls back to the default Git configuration. Remote pushing is opt-in via `options.autoPush`.
45020
45192
  */
45021
45193
  async function commitChanges(message, options) {
45022
45194
  const projectPath = process.cwd();
@@ -45036,7 +45208,7 @@
45036
45208
  cwd: projectPath,
45037
45209
  env: agentEnv,
45038
45210
  });
45039
- if (!(options === null || options === void 0 ? void 0 : options.noPush)) {
45211
+ if (options === null || options === void 0 ? void 0 : options.autoPush) {
45040
45212
  await pushCommittedChanges(projectPath, agentEnv);
45041
45213
  }
45042
45214
  }
@@ -46629,6 +46801,19 @@
46629
46801
  return `${prefix}$${price.toFixed(2)}`;
46630
46802
  }
46631
46803
 
46804
+ /**
46805
+ * Formats optional attempt metadata stored in prompt status lines.
46806
+ */
46807
+ function formatPromptAttemptMetadata(status, attemptCount) {
46808
+ if (attemptCount <= 1) {
46809
+ return '';
46810
+ }
46811
+ if (status === 'done') {
46812
+ return `(${attemptCount} attempts) `;
46813
+ }
46814
+ return `(failed after ${attemptCount} attempts) `;
46815
+ }
46816
+
46632
46817
  /**
46633
46818
  * Formats runner details for prompt status lines.
46634
46819
  */
@@ -46648,7 +46833,7 @@
46648
46833
  /**
46649
46834
  * Marks a prompt section as done and records usage pricing and runner details.
46650
46835
  */
46651
- function markPromptDone(file, section, usage, runnerName, modelName, promptExecutionStartedDate) {
46836
+ function markPromptDone(file, section, usage, runnerName, modelName, promptExecutionStartedDate, attemptCount = 1) {
46652
46837
  if (section.statusLineIndex === undefined) {
46653
46838
  throw new Error(`Prompt ${section.index + 1} in ${file.name} does not have a status line.`);
46654
46839
  }
@@ -46658,16 +46843,17 @@
46658
46843
  }
46659
46844
  const priceString = formatUsagePrice(usage);
46660
46845
  const runnerSignature = formatRunnerSignature(runnerName, modelName);
46846
+ const attemptMetadata = formatPromptAttemptMetadata('done', attemptCount);
46661
46847
  const duration = moment__default["default"]().diff(promptExecutionStartedDate);
46662
46848
  const durationString = moment__default["default"].duration(duration).humanize();
46663
46849
  // Replace "[ ]" or "[ ] !!..." with "[x] $price duration by runner"
46664
- file.lines[section.statusLineIndex] = line.replace(/\[\s*\]\s*!*\s*$/, `[x] ${priceString} ${durationString} by ${runnerSignature}`);
46850
+ file.lines[section.statusLineIndex] = line.replace(/\[\s*\]\s*!*\s*$/, `[x] ${attemptMetadata}${priceString} ${durationString} by ${runnerSignature}`);
46665
46851
  }
46666
46852
 
46667
46853
  /**
46668
46854
  * Marks a prompt section as failed and records runner details.
46669
46855
  */
46670
- function markPromptFailed(file, section, runnerName, modelName, promptExecutionStartedDate) {
46856
+ function markPromptFailed(file, section, runnerName, modelName, promptExecutionStartedDate, attemptCount = 1) {
46671
46857
  if (section.statusLineIndex === undefined) {
46672
46858
  throw new Error(`Prompt ${section.index + 1} in ${file.name} does not have a status line.`);
46673
46859
  }
@@ -46676,9 +46862,11 @@
46676
46862
  throw new Error(`Prompt ${section.index + 1} in ${file.name} points to a missing status line.`);
46677
46863
  }
46678
46864
  const runnerSignature = formatRunnerSignature(runnerName, modelName);
46865
+ const attemptMetadata = formatPromptAttemptMetadata('failed', attemptCount);
46679
46866
  const duration = moment__default["default"]().diff(promptExecutionStartedDate);
46680
46867
  const durationString = moment__default["default"].duration(duration).humanize();
46681
- file.lines[section.statusLineIndex] = line.replace(/\[\s*\]\s*!*\s*$/, `[!] failed after ${durationString} by ${runnerSignature}`);
46868
+ const failureDetails = attemptMetadata === '' ? `failed after ${durationString} by ${runnerSignature}` : `${attemptMetadata}${durationString} by ${runnerSignature}`;
46869
+ file.lines[section.statusLineIndex] = line.replace(/\[\s*\]\s*!*\s*$/, `[!] ${failureDetails}`);
46682
46870
  }
46683
46871
 
46684
46872
  /**
@@ -46795,6 +46983,20 @@
46795
46983
  await waitForEnter(colors__default["default"].bgWhite(`Press Enter to start the ${label}...`));
46796
46984
  }
46797
46985
 
46986
+ /**
46987
+ * Formats one unknown error-like value into readable text for logs and feedback.
46988
+ */
46989
+ function formatUnknownErrorDetails(error) {
46990
+ if (error instanceof Error) {
46991
+ return error.stack || error.message;
46992
+ }
46993
+ if (typeof error === 'string') {
46994
+ return error;
46995
+ }
46996
+ const serializedError = JSON.stringify(error, null, 2);
46997
+ return serializedError !== null && serializedError !== void 0 ? serializedError : String(error);
46998
+ }
46999
+
46798
47000
  /**
46799
47001
  * Writes the failure details for a single prompt run next to the prompt markdown file.
46800
47002
  */
@@ -46802,7 +47004,7 @@
46802
47004
  const logPath = buildPromptErrorLogPath(options.file.path);
46803
47005
  const label = buildPromptLabelForDisplay(options.file, options.section);
46804
47006
  const summary = buildPromptSummary(options.file, options.section);
46805
- const details = buildErrorDetails(options.error);
47007
+ const details = formatUnknownErrorDetails(options.error);
46806
47008
  const modelSuffix = options.modelName ? ` (${options.modelName})` : '';
46807
47009
  const runnerLabel = `${options.runnerName || 'unknown'}${modelSuffix}`;
46808
47010
  const log = [
@@ -46829,18 +47031,6 @@
46829
47031
  }
46830
47032
  return `${promptPath}.error.log`;
46831
47033
  }
46832
- /**
46833
- * Formats unknown error values into a readable log payload.
46834
- */
46835
- function buildErrorDetails(error) {
46836
- if (error instanceof Error) {
46837
- return error.stack || error.message;
46838
- }
46839
- if (typeof error === 'string') {
46840
- return error;
46841
- }
46842
- return JSON.stringify(error, null, 2);
46843
- }
46844
47034
 
46845
47035
  /**
46846
47036
  * Writes updated prompt file content to disk.
@@ -47899,6 +48089,647 @@
47899
48089
  }
47900
48090
  }
47901
48091
 
48092
+ /**
48093
+ * Runs the configured verification command inside the project root and returns its output.
48094
+ */
48095
+ async function runPromptTestCommand(options) {
48096
+ const projectPath = toPosixPath(options.projectPath);
48097
+ return await $runGoScriptWithOutput({
48098
+ scriptPath: options.scriptPath,
48099
+ scriptContent: spaceTrim(`
48100
+ cd "${projectPath}"
48101
+ ${options.command}
48102
+ `),
48103
+ });
48104
+ }
48105
+
48106
+ /**
48107
+ * Maximum number of coding attempts allowed for the same prompt when verification keeps failing.
48108
+ */
48109
+ const MAX_PROMPT_TEST_ATTEMPTS = 3;
48110
+ /**
48111
+ * Maximum amount of verification output sent back to the coding agent as retry feedback.
48112
+ */
48113
+ const MAX_TEST_FEEDBACK_OUTPUT_CHARS = 12000;
48114
+ /**
48115
+ * Runs one coding prompt and, when configured, verifies it with a shell command that can feed failures back.
48116
+ */
48117
+ async function runPromptWithTestFeedback(options) {
48118
+ var _a, _b, _c, _d;
48119
+ const normalizedTestCommand = (_a = options.testCommand) === null || _a === void 0 ? void 0 : _a.trim();
48120
+ if (!normalizedTestCommand) {
48121
+ (_b = options.onAttemptStarted) === null || _b === void 0 ? void 0 : _b.call(options, 1);
48122
+ const result = await options.runner.runPrompt({
48123
+ prompt: options.prompt,
48124
+ scriptPath: options.scriptPath,
48125
+ projectPath: options.projectPath,
48126
+ });
48127
+ return { ...result, attemptCount: 1 };
48128
+ }
48129
+ const runPromptTestCommandExecutor = (_c = options.runPromptTestCommandExecutor) !== null && _c !== void 0 ? _c : runPromptTestCommand;
48130
+ let promptForCurrentAttempt = options.prompt;
48131
+ for (let attemptCount = 1; attemptCount <= MAX_PROMPT_TEST_ATTEMPTS; attemptCount++) {
48132
+ (_d = options.onAttemptStarted) === null || _d === void 0 ? void 0 : _d.call(options, attemptCount);
48133
+ const result = await options.runner.runPrompt({
48134
+ prompt: promptForCurrentAttempt,
48135
+ scriptPath: options.scriptPath,
48136
+ projectPath: options.projectPath,
48137
+ });
48138
+ console.info(colors__default["default"].gray(`Running verification command after attempt #${attemptCount}: ${normalizedTestCommand}`));
48139
+ try {
48140
+ await runPromptTestCommandExecutor({
48141
+ command: normalizedTestCommand,
48142
+ projectPath: options.projectPath,
48143
+ scriptPath: buildPromptTestScriptPath(options.scriptPath),
48144
+ });
48145
+ return { ...result, attemptCount };
48146
+ }
48147
+ catch (error) {
48148
+ const fullVerificationOutput = formatUnknownErrorDetails(error);
48149
+ const feedbackVerificationOutput = limitVerificationOutputForFeedback(fullVerificationOutput);
48150
+ if (attemptCount >= MAX_PROMPT_TEST_ATTEMPTS) {
48151
+ console.error(colors__default["default"].red(`Verification failed for ${options.promptLabel} after ${attemptCount} attempts.`));
48152
+ throw new Error(buildFinalVerificationFailureMessage({
48153
+ promptLabel: options.promptLabel,
48154
+ testCommand: normalizedTestCommand,
48155
+ attemptCount,
48156
+ verificationOutput: fullVerificationOutput,
48157
+ }));
48158
+ }
48159
+ console.warn(colors__default["default"].yellow(`Verification failed for ${options.promptLabel} on attempt #${attemptCount}. Sending feedback to ${options.runner.name} and retrying...`));
48160
+ promptForCurrentAttempt = appendCoderContext(options.prompt, buildVerificationFeedback({
48161
+ testCommand: normalizedTestCommand,
48162
+ failedAttemptCount: attemptCount,
48163
+ verificationOutput: feedbackVerificationOutput,
48164
+ }));
48165
+ }
48166
+ }
48167
+ throw new Error('Unexpected prompt verification state.');
48168
+ }
48169
+ /**
48170
+ * Builds one feedback block appended to the next coding attempt after tests fail.
48171
+ */
48172
+ function buildVerificationFeedback({ testCommand, failedAttemptCount, verificationOutput, }) {
48173
+ const nextAttemptCount = failedAttemptCount + 1;
48174
+ return spaceTrim((block) => `
48175
+ The previous implementation did not pass the required verification command.
48176
+
48177
+ ## Automated verification feedback
48178
+ - Retry attempt: ${nextAttemptCount} of ${MAX_PROMPT_TEST_ATTEMPTS}
48179
+ - Verification command: \`${testCommand}\`
48180
+ - Update the current implementation so the verification command passes without breaking the original task requirements.
48181
+
48182
+ ### Verification output
48183
+ \`\`\`
48184
+ ${block(verificationOutput)}
48185
+ \`\`\`
48186
+ `);
48187
+ }
48188
+ /**
48189
+ * Builds the final error message written when verification still fails after all retries.
48190
+ */
48191
+ function buildFinalVerificationFailureMessage({ promptLabel, testCommand, attemptCount, verificationOutput, }) {
48192
+ return spaceTrim((block) => `
48193
+ Verification command \`${testCommand}\` failed for \`${promptLabel}\` after ${attemptCount} attempts.
48194
+
48195
+ ### Verification output
48196
+ \`\`\`
48197
+ ${block(verificationOutput)}
48198
+ \`\`\`
48199
+ `);
48200
+ }
48201
+ /**
48202
+ * Limits verification output before it is embedded back into the next coding prompt.
48203
+ */
48204
+ function limitVerificationOutputForFeedback(verificationOutput) {
48205
+ const normalizedVerificationOutput = verificationOutput.trim();
48206
+ if (normalizedVerificationOutput.length <= MAX_TEST_FEEDBACK_OUTPUT_CHARS) {
48207
+ return normalizedVerificationOutput;
48208
+ }
48209
+ return spaceTrim(`
48210
+ [...verification output truncated to the last ${MAX_TEST_FEEDBACK_OUTPUT_CHARS} characters...]
48211
+ ${normalizedVerificationOutput.slice(-MAX_TEST_FEEDBACK_OUTPUT_CHARS)}
48212
+ `);
48213
+ }
48214
+ /**
48215
+ * Derives a dedicated temp-script path for verification commands.
48216
+ */
48217
+ function buildPromptTestScriptPath(scriptPath) {
48218
+ if (scriptPath.toLowerCase().endsWith('.sh')) {
48219
+ return `${scriptPath.slice(0, -3)}.test.sh`;
48220
+ }
48221
+ return `${scriptPath}.test.sh`;
48222
+ }
48223
+
48224
+ /**
48225
+ * Maximum number of agent output lines kept in the scrolling output area.
48226
+ *
48227
+ * @private internal constant of coder run UI
48228
+ */
48229
+ const MAX_AGENT_OUTPUT_LINES = 12;
48230
+ /**
48231
+ * Calendar formats used when displaying the estimated completion time.
48232
+ *
48233
+ * @private internal constant of coder run UI
48234
+ */
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
+ };
48243
+ /**
48244
+ * Reactive state manager for the coder run terminal UI.
48245
+ *
48246
+ * Holds all data the Ink components need and emits `'change'` events
48247
+ * whenever any property is updated so the UI can re-render.
48248
+ *
48249
+ * @private internal utility of coder run UI
48250
+ */
48251
+ class CoderRunUiState extends events.EventEmitter {
48252
+ constructor(startTime) {
48253
+ super();
48254
+ this.config = { agentName: '', priority: 0 };
48255
+ this.currentPromptLabel = '';
48256
+ this.currentAttempt = 1;
48257
+ this.maxAttempts = 3;
48258
+ this.agentOutputLines = [];
48259
+ this.phase = 'initializing';
48260
+ this.statusMessage = 'Initializing...';
48261
+ this.errors = [];
48262
+ 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();
48270
+ }
48271
+ /**
48272
+ * Pauses the elapsed timer (e.g. while waiting for user input or paused state).
48273
+ */
48274
+ pauseTimer() {
48275
+ if (this.pausedSince === undefined) {
48276
+ this.pausedSince = moment__default["default"]();
48277
+ }
48278
+ }
48279
+ /**
48280
+ * Resumes the elapsed timer after a pause.
48281
+ */
48282
+ resumeTimer() {
48283
+ if (this.pausedSince !== undefined) {
48284
+ this.pausedMs += moment__default["default"]().diff(this.pausedSince);
48285
+ this.pausedSince = undefined;
48286
+ }
48287
+ }
48288
+ /**
48289
+ * Replaces the configuration shown in the UI header.
48290
+ */
48291
+ setConfig(config) {
48292
+ this.config = config;
48293
+ this.emitChange();
48294
+ }
48295
+ /**
48296
+ * Feeds new prompt statistics from the main loop so the progress bar updates.
48297
+ */
48298
+ updateProgress(stats) {
48299
+ if (this.initialDone === undefined && (stats.done > 0 || stats.forAgent > 0 || stats.toBeWritten > 0)) {
48300
+ this.initialDone = stats.done;
48301
+ }
48302
+ this.stats = stats;
48303
+ this.emitChange();
48304
+ }
48305
+ /**
48306
+ * Computes a progress snapshot on demand so elapsed time ticks with periodic re-renders.
48307
+ */
48308
+ getProgress() {
48309
+ 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
+ };
48339
+ }
48340
+ /**
48341
+ * Sets the label of the prompt currently being processed and resets per-prompt state.
48342
+ */
48343
+ setCurrentPrompt(label) {
48344
+ this.currentPromptLabel = label;
48345
+ this.agentOutputLines = [];
48346
+ this.currentAttempt = 1;
48347
+ this.emitChange();
48348
+ }
48349
+ /**
48350
+ * Updates the current retry attempt number.
48351
+ */
48352
+ setAttempt(attempt) {
48353
+ this.currentAttempt = attempt;
48354
+ this.emitChange();
48355
+ }
48356
+ /**
48357
+ * Appends raw agent output text, keeping only the last `MAX_AGENT_OUTPUT_LINES`.
48358
+ */
48359
+ addAgentOutput(text) {
48360
+ const lines = text.split(/\r?\n/).filter((line) => line.trim() !== '');
48361
+ if (lines.length === 0) {
48362
+ return;
48363
+ }
48364
+ this.agentOutputLines.push(...lines);
48365
+ if (this.agentOutputLines.length > MAX_AGENT_OUTPUT_LINES) {
48366
+ this.agentOutputLines = this.agentOutputLines.slice(-MAX_AGENT_OUTPUT_LINES);
48367
+ }
48368
+ this.emitChange();
48369
+ }
48370
+ /**
48371
+ * Transitions the execution phase shown in the UI.
48372
+ */
48373
+ setPhase(phase) {
48374
+ this.phase = phase;
48375
+ this.emitChange();
48376
+ }
48377
+ /**
48378
+ * Updates the status message line beneath the current prompt label.
48379
+ */
48380
+ setStatusMessage(message) {
48381
+ this.statusMessage = message;
48382
+ this.emitChange();
48383
+ }
48384
+ /**
48385
+ * Appends an error message to the error list shown in the UI.
48386
+ */
48387
+ addError(errorMessage) {
48388
+ this.errors.push(errorMessage);
48389
+ this.emitChange();
48390
+ }
48391
+ emitChange() {
48392
+ this.emit('change');
48393
+ }
48394
+ }
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
+
48421
+ /**
48422
+ * Refresh interval for the terminal UI in milliseconds.
48423
+ *
48424
+ * @private internal constant of coder run UI
48425
+ */
48426
+ 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
+ /**
48440
+ * Spinner animation frames.
48441
+ *
48442
+ * @private internal constant of coder run UI
48443
+ */
48444
+ const SPINNER_FRAMES = [
48445
+ '\u280B',
48446
+ '\u2819',
48447
+ '\u2839',
48448
+ '\u2838',
48449
+ '\u283C',
48450
+ '\u2834',
48451
+ '\u2826',
48452
+ '\u2827',
48453
+ '\u2807',
48454
+ '\u280F',
48455
+ ];
48456
+ /**
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.
48467
+ *
48468
+ * @private internal utility of coder run UI
48469
+ */
48470
+ 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}%`;
48482
+ }
48483
+ /**
48484
+ * Boots the ANSI terminal UI for `ptbk coder run`.
48485
+ *
48486
+ * The UI reserves a fixed number of terminal lines and repaints them periodically.
48487
+ * Between repaints, any console output from runners is captured and fed into the
48488
+ * scrolling agent-output area.
48489
+ *
48490
+ * On non-interactive (non-TTY) terminals the UI is skipped entirely and
48491
+ * only the state object is provided.
48492
+ *
48493
+ * @private internal entry point of coder run UI
48494
+ */
48495
+ function renderCoderRunUi(startTime) {
48496
+ const state = new CoderRunUiState(startTime);
48497
+ if (!process.stdout.isTTY) {
48498
+ return {
48499
+ state,
48500
+ startCapturingAgentOutput: () => { },
48501
+ stopCapturingAgentOutput: () => { },
48502
+ cleanup: () => { },
48503
+ };
48504
+ }
48505
+ // --- Console interception ---
48506
+ const originalConsoleInfo = console.info;
48507
+ const originalConsoleWarn = console.warn;
48508
+ const originalConsoleError = console.error;
48509
+ const originalConsoleLog = console.log;
48510
+ let isCapturing = false;
48511
+ console.info = (...args) => {
48512
+ if (isCapturing) {
48513
+ state.addAgentOutput(args.map(String).join(' '));
48514
+ }
48515
+ // In UI mode, non-captured output is intentionally suppressed
48516
+ // so it does not interfere with the repainted frame.
48517
+ };
48518
+ console.warn = (...args) => {
48519
+ if (isCapturing) {
48520
+ state.addAgentOutput(args.map(String).join(' '));
48521
+ }
48522
+ };
48523
+ console.error = (...args) => {
48524
+ if (isCapturing) {
48525
+ state.addError(args.map(String).join(' '));
48526
+ }
48527
+ };
48528
+ console.log = (...args) => {
48529
+ if (isCapturing) {
48530
+ state.addAgentOutput(args.map(String).join(' '));
48531
+ }
48532
+ };
48533
+ // --- Keyboard input (pause) ---
48534
+ const readline$1 = require('readline');
48535
+ readline$1.emitKeypressEvents(process.stdin);
48536
+ if (process.stdin.isTTY) {
48537
+ process.stdin.setRawMode(true);
48538
+ }
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
+ let spinnerFrame = 0;
48557
+ let previousFrameLineCount = 0;
48558
+ let isRendering = false;
48559
+ let renderScheduled = false;
48560
+ /**
48561
+ * Schedules a render on the next tick if one isn't already pending.
48562
+ * Prevents overlapping renders that cause cursor desync.
48563
+ */
48564
+ function scheduleRender() {
48565
+ if (renderScheduled) {
48566
+ return;
48567
+ }
48568
+ renderScheduled = true;
48569
+ setImmediate(() => {
48570
+ renderScheduled = false;
48571
+ render();
48572
+ });
48573
+ }
48574
+ /**
48575
+ * Clears previously rendered lines and writes a new frame.
48576
+ */
48577
+ function render() {
48578
+ if (isRendering) {
48579
+ return;
48580
+ }
48581
+ isRendering = true;
48582
+ try {
48583
+ const lines = buildFrame();
48584
+ // Move cursor up to clear the previous frame.
48585
+ if (previousFrameLineCount > 0) {
48586
+ process.stdout.write(`\x1b[${previousFrameLineCount}A`);
48587
+ }
48588
+ for (let i = 0; i < lines.length; i++) {
48589
+ readline.clearLine(process.stdout, 0);
48590
+ readline.cursorTo(process.stdout, 0);
48591
+ process.stdout.write(lines[i]);
48592
+ if (i < lines.length - 1) {
48593
+ process.stdout.write('\n');
48594
+ }
48595
+ }
48596
+ // Clear any leftover lines from a previous longer frame.
48597
+ if (lines.length < previousFrameLineCount) {
48598
+ for (let i = lines.length; i < previousFrameLineCount; i++) {
48599
+ process.stdout.write('\n');
48600
+ readline.clearLine(process.stdout, 0);
48601
+ readline.cursorTo(process.stdout, 0);
48602
+ }
48603
+ // Move back up to the end of the current frame.
48604
+ const overshoot = previousFrameLineCount - lines.length;
48605
+ if (overshoot > 0) {
48606
+ process.stdout.write(`\x1b[${overshoot}A`);
48607
+ }
48608
+ }
48609
+ previousFrameLineCount = lines.length;
48610
+ spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
48611
+ }
48612
+ finally {
48613
+ isRendering = false;
48614
+ }
48615
+ }
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}`));
48665
+ }
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.
48698
+ process.stdout.write('\n');
48699
+ render();
48700
+ const interval = setInterval(scheduleRender, UI_REFRESH_INTERVAL_MS);
48701
+ // Listen for state changes and schedule a re-render (debounced).
48702
+ state.on('change', scheduleRender);
48703
+ // --- Cleanup ---
48704
+ function cleanup() {
48705
+ clearInterval(interval);
48706
+ state.off('change', scheduleRender);
48707
+ process.stdin.off('keypress', keypressHandler);
48708
+ if (process.stdin.isTTY) {
48709
+ process.stdin.setRawMode(false);
48710
+ }
48711
+ isCapturing = false;
48712
+ console.info = originalConsoleInfo;
48713
+ console.warn = originalConsoleWarn;
48714
+ console.error = originalConsoleError;
48715
+ console.log = originalConsoleLog;
48716
+ // Render one final frame so the user sees the last state.
48717
+ render();
48718
+ process.stdout.write('\n');
48719
+ }
48720
+ return {
48721
+ state,
48722
+ startCapturingAgentOutput() {
48723
+ isCapturing = true;
48724
+ },
48725
+ stopCapturingAgentOutput() {
48726
+ isCapturing = false;
48727
+ },
48728
+ cleanup,
48729
+ };
48730
+ }
48731
+ // Note: [💞] Ignore a discrepancy between file name and entity name
48732
+
47902
48733
  /**
47903
48734
  * Constant for prompts dir.
47904
48735
  */
@@ -47961,8 +48792,13 @@
47961
48792
  `));
47962
48793
  }
47963
48794
  const runStartDate = moment__default["default"]();
47964
- const progressDisplay = options.dryRun ? undefined : new CliProgressDisplay(runStartDate);
47965
- listenForPause();
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;
48798
+ // When the Ink UI is active it handles keyboard input itself, so skip the raw stdin listener.
48799
+ if (!isUiMode) {
48800
+ listenForPause();
48801
+ }
47966
48802
  try {
47967
48803
  const resolvedCoderContext = await resolveCoderContext(options.context, process.cwd());
47968
48804
  if (options.dryRun) {
@@ -48061,35 +48897,78 @@
48061
48897
  }
48062
48898
  console.info(colors__default["default"].green(`Running prompts with ${runner.name}`));
48063
48899
  const runnerMetadata = getRunnerMetadata(options, actualRunnerModel);
48900
+ // Feed configuration into the terminal UI
48901
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setConfig({
48902
+ agentName: runner.name,
48903
+ modelName: actualRunnerModel,
48904
+ thinkingLevel: options.thinkingLevel,
48905
+ context: options.context,
48906
+ priority: options.priority,
48907
+ testCommand: options.testCommand,
48908
+ });
48909
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
48910
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Running prompts with ${runner.name}`);
48064
48911
  let hasShownUpcomingTasks = false;
48065
48912
  let hasWaitedForStart = false;
48066
48913
  while (just(true)) {
48067
- await checkPause();
48914
+ await checkPause({
48915
+ silent: isUiMode,
48916
+ onPaused: () => {
48917
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.pauseTimer();
48918
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('paused');
48919
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Paused');
48920
+ },
48921
+ onResumed: () => {
48922
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
48923
+ },
48924
+ });
48925
+ if (isUiMode) {
48926
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
48927
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Loading prompts...');
48928
+ }
48068
48929
  const promptFiles = await loadPromptFiles(PROMPTS_DIR$1);
48069
48930
  const stats = summarizePrompts(promptFiles, options.priority);
48070
48931
  progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.update(stats);
48071
- printStats(stats, options.priority);
48932
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.updateProgress(stats);
48933
+ if (!isUiMode) {
48934
+ printStats(stats, options.priority);
48935
+ }
48072
48936
  const nextPrompt = findNextTodoPrompt(promptFiles, options.priority);
48073
48937
  if (!hasShownUpcomingTasks) {
48074
- if (stats.toBeWritten > 0) {
48938
+ if (stats.toBeWritten > 0 && !isUiMode) {
48075
48939
  console.info(colors__default["default"].yellow('Following prompts need to be written:'));
48076
48940
  printPromptsToBeWritten(promptFiles, options.priority);
48077
48941
  console.info('');
48078
48942
  }
48079
- printUpcomingTasks(listUpcomingTasks(promptFiles, options.priority));
48943
+ if (!isUiMode) {
48944
+ printUpcomingTasks(listUpcomingTasks(promptFiles, options.priority));
48945
+ }
48080
48946
  hasShownUpcomingTasks = true;
48081
48947
  }
48082
48948
  if (!nextPrompt) {
48083
48949
  if (stats.toBeWritten > 0) {
48084
- console.info(colors__default["default"].yellow('No prompts ready for agent.'));
48950
+ const message = 'No prompts ready for agent.';
48951
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
48952
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
48953
+ if (!isUiMode) {
48954
+ console.info(colors__default["default"].yellow(message));
48955
+ }
48085
48956
  }
48086
48957
  else {
48087
- console.info(colors__default["default"].green('All prompts are done.'));
48958
+ const message = 'All prompts are done.';
48959
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
48960
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
48961
+ if (!isUiMode) {
48962
+ console.info(colors__default["default"].green(message));
48963
+ }
48088
48964
  }
48089
48965
  return;
48090
48966
  }
48091
48967
  if (options.waitForUser) {
48968
+ 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');
48092
48970
  await waitForPromptStart(nextPrompt.file, nextPrompt.section, !hasWaitedForStart);
48971
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
48093
48972
  hasWaitedForStart = true;
48094
48973
  }
48095
48974
  if (!options.ignoreGitChanges) {
@@ -48099,29 +48978,57 @@
48099
48978
  const codexPrompt = appendCoderContext(buildCodexPrompt(nextPrompt.file, nextPrompt.section), resolvedCoderContext);
48100
48979
  const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
48101
48980
  const promptLabel = buildPromptLabelForDisplay(nextPrompt.file, nextPrompt.section);
48102
- console.info(colors__default["default"].blue(`Processing ${promptLabel}`));
48981
+ if (isUiMode) {
48982
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
48983
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('running');
48984
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Running');
48985
+ }
48986
+ else {
48987
+ console.info(colors__default["default"].blue(`Processing ${promptLabel}`));
48988
+ }
48103
48989
  const promptExecutionStartedDate = moment__default["default"]();
48990
+ let attemptCount = 1;
48104
48991
  const roundChangedFilesSnapshot = options.normalizeLineEndings
48105
48992
  ? await captureChangedFilesSnapshot(process.cwd())
48106
48993
  : undefined;
48107
48994
  try {
48108
- const result = await runner.runPrompt({
48995
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
48996
+ const result = await runPromptWithTestFeedback({
48997
+ runner,
48109
48998
  prompt: codexPrompt,
48110
48999
  scriptPath,
48111
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');
49009
+ }
49010
+ },
48112
49011
  });
48113
- markPromptDone(nextPrompt.file, nextPrompt.section, result.usage, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate);
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);
48114
49015
  await writePromptFile(nextPrompt.file);
48115
49016
  await normalizeLineEndingsForCurrentRound(options, roundChangedFilesSnapshot);
48116
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');
48117
49020
  printCommitMessage(commitMessage);
48118
49021
  await waitForEnter(colors__default["default"].bgWhite('Press Enter to commit and continue...'));
49022
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.resumeTimer();
48119
49023
  }
48120
- await commitChanges(commitMessage, { noPush: options.noPush });
49024
+ await commitChanges(commitMessage, { autoPush: options.autoPush });
48121
49025
  await runPostPromptAutoMigrationIfEnabled(options);
48122
49026
  }
48123
49027
  catch (error) {
48124
- markPromptFailed(nextPrompt.file, nextPrompt.section, runnerMetadata.runnerName, runnerMetadata.modelName, promptExecutionStartedDate);
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);
48125
49032
  await writePromptFile(nextPrompt.file);
48126
49033
  await writePromptErrorLog({
48127
49034
  file: nextPrompt.file,
@@ -48137,6 +49044,7 @@
48137
49044
  }
48138
49045
  finally {
48139
49046
  progressDisplay === null || progressDisplay === void 0 ? void 0 : progressDisplay.stop();
49047
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.cleanup();
48140
49048
  if (!options.dryRun) {
48141
49049
  printAgentGitIdentityTipIfNeeded();
48142
49050
  }
@@ -49759,7 +50667,7 @@
49759
50667
  /**
49760
50668
  * Constant for default agent kit model name.
49761
50669
  */
49762
- const DEFAULT_AGENT_KIT_MODEL_NAME = 'gpt-5.4-nano';
50670
+ const DEFAULT_AGENT_KIT_MODEL_NAME = 'gpt-5.4-mini';
49763
50671
  /**
49764
50672
  * Creates one structured log entry for streamed tool-call updates.
49765
50673
  *
@@ -50809,6 +51717,7 @@
50809
51717
  * @param agentSource The agent source string that defines the agent's behavior
50810
51718
  */
50811
51719
  constructor(options) {
51720
+ var _a;
50812
51721
  this.options = options;
50813
51722
  /**
50814
51723
  * Cached model requirements to avoid re-parsing the agent source
@@ -50818,6 +51727,7 @@
50818
51727
  * Cached parsed agent information
50819
51728
  */
50820
51729
  this._cachedAgentInfo = null;
51730
+ this.precomputedModelRequirements = (_a = options.precomputedModelRequirements) !== null && _a !== void 0 ? _a : null;
50821
51731
  }
50822
51732
  /**
50823
51733
  * Updates the agent source and clears the cache
@@ -50825,9 +51735,13 @@
50825
51735
  * @param agentSource The new agent source string
50826
51736
  */
50827
51737
  updateAgentSource(agentSource) {
51738
+ if (this.options.agentSource === agentSource) {
51739
+ return;
51740
+ }
50828
51741
  this.options.agentSource = agentSource;
50829
51742
  this._cachedAgentInfo = null;
50830
51743
  this._cachedModelRequirements = null;
51744
+ this.precomputedModelRequirements = null;
50831
51745
  }
50832
51746
  /**
50833
51747
  * Get cached or parse agent information
@@ -50844,6 +51758,16 @@
50844
51758
  * Note: [🐤] This is names `getModelRequirements` *(not `getAgentModelRequirements`)* because in future these two will be united
50845
51759
  */
50846
51760
  async getModelRequirements() {
51761
+ var _a, _b;
51762
+ if (this.precomputedModelRequirements !== null) {
51763
+ if (this.options.isVerbose) {
51764
+ console.info('[🤰]', 'Using precomputed agent model requirements', {
51765
+ agent: this.title,
51766
+ toolCount: (_b = (_a = this.precomputedModelRequirements.tools) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0,
51767
+ });
51768
+ }
51769
+ return this.precomputedModelRequirements;
51770
+ }
50847
51771
  if (this._cachedModelRequirements === null) {
50848
51772
  const preparationStartedAtMs = Date.now();
50849
51773
  if (this.options.isVerbose) {
@@ -50953,6 +51877,7 @@
50953
51877
  * Resolves agent requirements, attachments, and runtime overrides into one forwarded chat prompt.
50954
51878
  */
50955
51879
  async prepareChatPrompt(prompt) {
51880
+ var _a;
50956
51881
  const chatPrompt = this.requireChatPrompt(prompt);
50957
51882
  const { sanitizedRequirements, promptSuffix } = await this.getSanitizedAgentModelRequirements();
50958
51883
  const attachments = normalizeChatAttachments(chatPrompt.attachments);
@@ -50970,7 +51895,16 @@
50970
51895
  mergedTools,
50971
51896
  knowledgeSourcesForAgent,
50972
51897
  });
50973
- console.log('!!!! promptWithAgentModelRequirements:', forwardedPrompt);
51898
+ if (this.options.isVerbose) {
51899
+ console.info('[🤰]', 'Prepared agent chat prompt', {
51900
+ agent: this.title,
51901
+ usedPrecomputedModelRequirements: this.precomputedModelRequirements !== null,
51902
+ toolNames: mergedTools.map((tool) => tool.name),
51903
+ knowledgeSourcesCount: (_a = knowledgeSourcesForAgent === null || knowledgeSourcesForAgent === void 0 ? void 0 : knowledgeSourcesForAgent.length) !== null && _a !== void 0 ? _a : 0,
51904
+ promptSuffixLength: promptSuffix.length,
51905
+ systemMessageLength: sanitizedRequirements.systemMessage.length,
51906
+ });
51907
+ }
50974
51908
  return {
50975
51909
  forwardedPrompt,
50976
51910
  sanitizedRequirements,
@@ -51157,6 +52091,7 @@
51157
52091
  * Runs one prepared prompt through the deprecated OpenAI Assistant backend.
51158
52092
  */
51159
52093
  async callOpenAiAssistantChatModelStream(options) {
52094
+ var _a, _b, _c, _d;
51160
52095
  const assistant = await this.getOrPrepareOpenAiAssistant({
51161
52096
  llmTools: options.llmTools,
51162
52097
  originalPrompt: options.originalPrompt,
@@ -51164,7 +52099,14 @@
51164
52099
  onProgress: options.onProgress,
51165
52100
  });
51166
52101
  const promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools = createOpenAiAssistantPrompt(options.preparedChatPrompt.forwardedPrompt);
51167
- console.log('!!!! promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools:', promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools);
52102
+ if (this.options.isVerbose) {
52103
+ console.info('[🤰]', 'Prepared OpenAI Assistant prompt', {
52104
+ agent: this.title,
52105
+ toolNames: (_b = (_a = promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools.modelRequirements.tools) === null || _a === void 0 ? void 0 : _a.map((tool) => tool.name)) !== null && _b !== void 0 ? _b : [],
52106
+ knowledgeSourcesCount: (_d = (_c = promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools.modelRequirements
52107
+ .knowledgeSources) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0,
52108
+ });
52109
+ }
51168
52110
  return assistant.callChatModelStream(promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools, options.onProgress, options.streamOptions);
51169
52111
  }
51170
52112
  /**
@@ -51855,7 +52797,8 @@
51855
52797
  isVerbose: options.isVerbose,
51856
52798
  llmTools: getSingleLlmExecutionTools(options.executionTools.llm),
51857
52799
  assistantPreparationMode: options.assistantPreparationMode,
51858
- agentSource: agentSource.value, // <- TODO: [🐱‍🚀] Allow to pass BehaviorSubject<string_book> OR refresh llmExecutionTools.callChat on agentSource change
52800
+ agentSource: agentSource.value,
52801
+ precomputedModelRequirements: options.precomputedModelRequirements,
51859
52802
  });
51860
52803
  this._agentName = undefined;
51861
52804
  /**