@promptbook/cli 0.112.0 → 0.113.0-1

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 (109) hide show
  1. package/README.md +45 -29
  2. package/agents/default/developer.book +2 -1
  3. package/apps/agents-server/next.config.ts +21 -0
  4. package/apps/agents-server/src/app/admin/limits/LimitsClient.tsx +1 -1
  5. package/apps/agents-server/src/app/admin/task-manager/TaskManagerTaskRow.tsx +7 -2
  6. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +52 -33
  7. package/apps/agents-server/src/app/api/agent-folders/[folderId]/visibility/route.ts +19 -7
  8. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +8 -18
  9. package/apps/agents-server/src/app/api/internal/agent-runner-limits/route.ts +1 -1
  10. package/apps/agents-server/src/app/api/page-preview/check/route.ts +5 -20
  11. package/apps/agents-server/src/app/api/page-preview/interact/route.ts +179 -0
  12. package/apps/agents-server/src/app/api/page-preview/live/route.ts +67 -0
  13. package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +13 -21
  14. package/apps/agents-server/src/app/api/v1/agents/[agentId]/route.ts +13 -8
  15. package/apps/agents-server/src/components/AgentProfile/AgentCapabilityChips.tsx +39 -5
  16. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +7 -1
  17. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +3 -0
  18. package/apps/agents-server/src/components/Homepage/AgentsListHeader.tsx +41 -0
  19. package/apps/agents-server/src/components/Homepage/hiddenFolders.ts +104 -0
  20. package/apps/agents-server/src/components/Homepage/useAgentsListQueryState.ts +35 -0
  21. package/apps/agents-server/src/components/Homepage/useAgentsListState.ts +24 -4
  22. package/apps/agents-server/src/constants/chatVisualMode.ts +1 -1
  23. package/apps/agents-server/src/constants/serverLimits.ts +19 -0
  24. package/apps/agents-server/src/constants/themeMode.ts +1 -1
  25. package/apps/agents-server/src/database/migrations/2026-05-2600-default-theme.sql +2 -2
  26. package/apps/agents-server/src/database/seedCoreAgents.ts +0 -2
  27. package/apps/agents-server/src/database/seedDefaultAgents.ts +0 -2
  28. package/apps/agents-server/src/tools/agent_progress.ts +4 -10
  29. package/apps/agents-server/src/utils/agentVisibility.ts +25 -62
  30. package/apps/agents-server/src/utils/chatTasksAdmin.ts +3 -1
  31. package/apps/agents-server/src/utils/createAgentWithDefaultVisibility.ts +3 -1
  32. package/apps/agents-server/src/utils/externalChatRunner/processExternalUserChatJob.ts +9 -17
  33. package/apps/agents-server/src/utils/getAdminChatTasksResponse/mapVpsSelfUpdateJobToAdminChatTask.ts +84 -0
  34. package/apps/agents-server/src/utils/getAdminChatTasksResponse.ts +195 -5
  35. package/apps/agents-server/src/utils/localChatRunner/LocalUserChatJobMetadata.ts +15 -5
  36. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +17 -26
  37. package/apps/agents-server/src/utils/pagePreview/livePagePreviewSessions.ts +365 -0
  38. package/apps/agents-server/src/utils/pagePreview/resolvePagePreviewRequestUrl.ts +74 -0
  39. package/apps/agents-server/src/utils/serverLimits.ts +2 -0
  40. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerCoreAgents.ts +2 -2
  41. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerDefaultAgents.ts +1 -1
  42. package/apps/agents-server/src/utils/userChat/createUserChatHarnessProgressCard.ts +0 -6
  43. package/apps/agents-server/src/utils/userChat/createUserChatRunnerProgressCard.ts +0 -6
  44. package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +69 -0
  45. package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +0 -6
  46. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +150 -24
  47. package/esm/apps/agents-server/src/constants/serverLimits.d.ts +17 -0
  48. package/esm/index.es.js +581 -93
  49. package/esm/index.es.js.map +1 -1
  50. package/esm/scripts/run-agent-messages/main/runMultipleAgentMessages.d.ts +1 -0
  51. package/esm/scripts/run-agent-messages/main/tickAgentMessages.d.ts +1 -0
  52. package/esm/src/_packages/core.index.d.ts +22 -0
  53. package/esm/src/_packages/types.index.d.ts +2 -0
  54. package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
  55. package/esm/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
  56. package/esm/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
  57. package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
  58. package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +2 -2
  59. package/esm/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +12 -0
  60. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +6 -0
  61. package/esm/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
  62. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
  63. package/esm/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
  64. package/esm/src/commitments/index.d.ts +2 -1
  65. package/esm/src/version.d.ts +1 -1
  66. package/package.json +1 -1
  67. package/src/_packages/core.index.ts +22 -0
  68. package/src/_packages/types.index.ts +2 -0
  69. package/src/book-2.0/agent-source/AgentBasicInformation.ts +2 -0
  70. package/src/book-2.0/agent-source/agentSourceVisibility.ts +214 -0
  71. package/src/book-2.0/agent-source/parseAgentSource/applyMetaCommitment.ts +17 -0
  72. package/src/book-2.0/book-language-documentation/createStandaloneBookLanguageMarkdown.ts +1 -0
  73. package/src/book-components/BookEditor/BookEditorMonacoTokenization.ts +11 -0
  74. package/src/book-components/BookEditor/useBookEditorMonacoLanguage.ts +7 -3
  75. package/src/book-components/Chat/Chat/Chat.module.css +14 -5
  76. package/src/book-components/Chat/Chat/CitationIframePreview.tsx +197 -22
  77. package/src/book-components/Chat/utils/livePagePreviewConstants.ts +17 -0
  78. package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +144 -16
  79. package/src/cli/cli-commands/agents-server/startAgentsServer.ts +22 -2
  80. package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +1 -1
  81. package/src/cli/cli-commands/coder/run.ts +30 -0
  82. package/src/collection/agent-collection/CreateAgentInput.ts +10 -5
  83. package/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.ts +7 -3
  84. package/src/collection/agent-collection/constructors/agent-collection-in-supabase/createAgentPersistenceRecords.ts +7 -4
  85. package/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.ts +44 -8
  86. package/src/commitments/META_VISIBILITY/META_VISIBILITY.ts +78 -0
  87. package/src/commitments/index.ts +2 -0
  88. package/src/other/templates/getTemplatesPipelineCollection.ts +826 -650
  89. package/src/version.ts +2 -2
  90. package/src/versions.txt +2 -0
  91. package/umd/apps/agents-server/src/constants/serverLimits.d.ts +17 -0
  92. package/umd/index.umd.js +581 -93
  93. package/umd/index.umd.js.map +1 -1
  94. package/umd/scripts/run-agent-messages/main/runMultipleAgentMessages.d.ts +1 -0
  95. package/umd/scripts/run-agent-messages/main/tickAgentMessages.d.ts +1 -0
  96. package/umd/src/_packages/core.index.d.ts +22 -0
  97. package/umd/src/_packages/types.index.d.ts +2 -0
  98. package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
  99. package/umd/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
  100. package/umd/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
  101. package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
  102. package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +2 -2
  103. package/umd/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +12 -0
  104. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +6 -0
  105. package/umd/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
  106. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
  107. package/umd/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
  108. package/umd/src/commitments/index.d.ts +2 -1
  109. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -59,7 +59,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
59
59
  * @generated
60
60
  * @see https://github.com/webgptorg/promptbook
61
61
  */
62
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0';
62
+ const PROMPTBOOK_ENGINE_VERSION = '0.113.0-1';
63
63
  /**
64
64
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
65
65
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -3299,6 +3299,28 @@ const DEFAULT_AGENTS_SERVER_NEXT_DIST_DIRECTORY_NAME = '.next';
3299
3299
  * @private internal constant of `ptbk agents-server`
3300
3300
  */
3301
3301
  const AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB = 4096;
3302
+ /**
3303
+ * Conservative Next.js build worker count used by CLI-owned Agents Server production builds.
3304
+ *
3305
+ * Standalone VPS self-updates build the replacement server while the current pm2 process is
3306
+ * still serving traffic. Keeping Next's static workers serial avoids CPU-count-based memory
3307
+ * spikes that can make the OS kill the build child with no normal exit code.
3308
+ *
3309
+ * @private internal constant of `ptbk agents-server`
3310
+ */
3311
+ const AGENTS_SERVER_BUILD_WORKER_COUNT = 1;
3312
+ /**
3313
+ * Maximum attempts for a Next.js build that was killed by the operating system.
3314
+ *
3315
+ * @private internal constant of `ptbk agents-server`
3316
+ */
3317
+ const AGENTS_SERVER_BUILD_MAX_ATTEMPTS = 2;
3318
+ /**
3319
+ * Signals that indicate the Next.js build may have been terminated by resource pressure.
3320
+ *
3321
+ * @private internal constant of `ptbk agents-server`
3322
+ */
3323
+ const RETRYABLE_NEXT_BUILD_SIGNALS = new Set(['SIGKILL', null]);
3302
3324
  /**
3303
3325
  * Environment variable passed to the bundled Next app so webpack can resolve dependencies
3304
3326
  * installed beside `ptbk` even when the app sources are materialized into a project cache.
@@ -3306,6 +3328,12 @@ const AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB = 4096;
3306
3328
  * @private internal constant of `ptbk agents-server`
3307
3329
  */
3308
3330
  const PTBK_AGENTS_SERVER_NODE_MODULES_PATH_ENV = 'PTBK_AGENTS_SERVER_NODE_MODULES_PATH';
3331
+ /**
3332
+ * Environment variable consumed by `apps/agents-server/next.config.ts` to throttle build workers.
3333
+ *
3334
+ * @private internal constant of `ptbk agents-server`
3335
+ */
3336
+ const PTBK_AGENTS_SERVER_BUILD_WORKER_COUNT_ENV = 'PTBK_AGENTS_SERVER_BUILD_WORKER_COUNT';
3309
3337
  /**
3310
3338
  * Environment variable used only by the CLI-owned production build.
3311
3339
  *
@@ -3506,6 +3534,7 @@ function createAgentsServerRuntimeEnvironment(environment, nodeModulesPath, opti
3506
3534
  ...environment,
3507
3535
  NODE_PATH: mergeNodePath(nodeModulesPath, environment.NODE_PATH),
3508
3536
  [PTBK_AGENTS_SERVER_NODE_MODULES_PATH_ENV]: nodeModulesPath,
3537
+ [PTBK_AGENTS_SERVER_BUILD_WORKER_COUNT_ENV]: environment[PTBK_AGENTS_SERVER_BUILD_WORKER_COUNT_ENV] || String(AGENTS_SERVER_BUILD_WORKER_COUNT),
3509
3538
  ...(options.isNextValidationIgnored
3510
3539
  ? {
3511
3540
  [PTBK_AGENTS_SERVER_IGNORE_NEXT_VALIDATION_ENV]: 'true',
@@ -3692,14 +3721,27 @@ async function writeAgentsServerMaterializedRuntimeCache(materializedRuntimeRoot
3692
3721
  * Runs the finite Next production build used by local Agents Server commands.
3693
3722
  */
3694
3723
  async function runNextBuild(options) {
3695
- await new Promise((resolveBuild, rejectBuild) => {
3724
+ for (let attempt = 1; attempt <= AGENTS_SERVER_BUILD_MAX_ATTEMPTS; attempt++) {
3725
+ const exitStatus = await runNextBuildAttempt(options);
3726
+ if (exitStatus.code === 0) {
3727
+ return;
3728
+ }
3729
+ if (attempt < AGENTS_SERVER_BUILD_MAX_ATTEMPTS && isNextBuildTerminationRetryable(exitStatus)) {
3730
+ forwardBuildOutput(createNextBuildRetryMessage(exitStatus, attempt + 1), options.onBuildOutput);
3731
+ continue;
3732
+ }
3733
+ throw createNextBuildExitError(exitStatus, attempt);
3734
+ }
3735
+ }
3736
+ /**
3737
+ * Runs one Next.js production build attempt and returns its exit status.
3738
+ */
3739
+ async function runNextBuildAttempt(options) {
3740
+ return await new Promise((resolveBuild, rejectBuild) => {
3696
3741
  var _a, _b;
3697
3742
  const buildProcess = spawn(process.execPath, [options.nextCliPath, 'build'], {
3698
3743
  cwd: options.appPath,
3699
- env: {
3700
- ...options.environment,
3701
- NODE_OPTIONS: mergeNodeOptionsWithHeapSize(options.environment.NODE_OPTIONS, AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB),
3702
- },
3744
+ env: createNextBuildProcessEnvironment(options.environment),
3703
3745
  stdio: ['ignore', 'pipe', 'pipe'],
3704
3746
  });
3705
3747
  (_a = buildProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => {
@@ -3708,16 +3750,70 @@ async function runNextBuild(options) {
3708
3750
  (_b = buildProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (chunk) => {
3709
3751
  forwardBuildOutput(chunk.toString(), options.onBuildOutput);
3710
3752
  });
3711
- buildProcess.once('error', rejectBuild);
3712
- buildProcess.once('exit', (code) => {
3713
- if (code === 0) {
3714
- resolveBuild();
3715
- return;
3716
- }
3717
- rejectBuild(new Error(`next-build exited with code ${String(code)}.`));
3753
+ buildProcess.once('error', (error) => {
3754
+ rejectBuild(createNextBuildSpawnError(error));
3755
+ });
3756
+ buildProcess.once('close', (code, signal) => {
3757
+ resolveBuild({ code, signal });
3718
3758
  });
3719
3759
  });
3720
3760
  }
3761
+ /**
3762
+ * Creates the environment passed to the spawned Next.js build process.
3763
+ */
3764
+ function createNextBuildProcessEnvironment(environment) {
3765
+ return {
3766
+ ...environment,
3767
+ NODE_OPTIONS: mergeNodeOptionsWithHeapSize(environment.NODE_OPTIONS, AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB),
3768
+ };
3769
+ }
3770
+ /**
3771
+ * Returns true when one failed build attempt is worth retrying.
3772
+ */
3773
+ function isNextBuildTerminationRetryable(exitStatus) {
3774
+ return exitStatus.code === null && RETRYABLE_NEXT_BUILD_SIGNALS.has(exitStatus.signal);
3775
+ }
3776
+ /**
3777
+ * Creates one visible retry message for a killed Next.js build attempt.
3778
+ */
3779
+ function createNextBuildRetryMessage(exitStatus, nextAttempt) {
3780
+ return `\nAgents Server Next build was terminated by ${describeNextBuildExitStatus(exitStatus)}. Retrying attempt ${nextAttempt}/${AGENTS_SERVER_BUILD_MAX_ATTEMPTS}.\n`;
3781
+ }
3782
+ /**
3783
+ * Creates a branded error for a failed Next.js build child process.
3784
+ */
3785
+ function createNextBuildExitError(exitStatus, attemptCount) {
3786
+ const lowMemoryHint = isNextBuildTerminationRetryable(exitStatus)
3787
+ ? '\n\nThe build process was terminated by the operating system. On standalone VPS self-updates this usually means the host ran out of memory while Next.js was compiling or prerendering the app.'
3788
+ : '';
3789
+ return new NotAllowed(spaceTrim$1(`
3790
+ Agents Server Next production build failed.
3791
+
3792
+ - Exit code: \`${String(exitStatus.code)}\`
3793
+ - Signal: \`${String(exitStatus.signal)}\`
3794
+ - Attempts: \`${String(attemptCount)}\`
3795
+ ${lowMemoryHint}
3796
+ `));
3797
+ }
3798
+ /**
3799
+ * Creates a branded error for failures to start the Next.js build child process.
3800
+ */
3801
+ function createNextBuildSpawnError(error) {
3802
+ return new NotAllowed(spaceTrim$1(`
3803
+ Cannot start the Agents Server Next production build.
3804
+
3805
+ ${error.message}
3806
+ `));
3807
+ }
3808
+ /**
3809
+ * Formats one Next.js build exit status for foreground logs.
3810
+ */
3811
+ function describeNextBuildExitStatus(exitStatus) {
3812
+ if (exitStatus.signal) {
3813
+ return `signal \`${exitStatus.signal}\``;
3814
+ }
3815
+ return `exit code \`${String(exitStatus.code)}\``;
3816
+ }
3721
3817
  /**
3722
3818
  * Prepends `--max-old-space-size=<mib>` to `NODE_OPTIONS` unless the caller already set one.
3723
3819
  */
@@ -16448,6 +16544,70 @@ class MetaLinkCommitmentDefinition extends BaseCommitmentDefinition {
16448
16544
  }
16449
16545
  // Note: [💞] Ignore a discrepancy between file name and entity name
16450
16546
 
16547
+ /**
16548
+ * META VISIBILITY commitment definition.
16549
+ *
16550
+ * The `META VISIBILITY` commitment stores whether an agent is public, private, or unlisted.
16551
+ * Agents Server mirrors this value into the database for efficient filtering, but the book
16552
+ * commitment remains the editable source of truth.
16553
+ *
16554
+ * @private Metadata-only commitment used by Agents Server.
16555
+ */
16556
+ class MetaVisibilityCommitmentDefinition extends BaseCommitmentDefinition {
16557
+ constructor() {
16558
+ super('META VISIBILITY');
16559
+ }
16560
+ /**
16561
+ * Short one-line description of META VISIBILITY.
16562
+ */
16563
+ get description() {
16564
+ return 'Set whether the agent is private, unlisted, or public.';
16565
+ }
16566
+ /**
16567
+ * Icon for this commitment.
16568
+ */
16569
+ get icon() {
16570
+ return '👁️';
16571
+ }
16572
+ /**
16573
+ * Markdown documentation for META VISIBILITY commitment.
16574
+ */
16575
+ get documentation() {
16576
+ return spaceTrim$1(`
16577
+ # META VISIBILITY
16578
+
16579
+ Sets the agent visibility used by Agents Server.
16580
+
16581
+ ## Allowed values
16582
+
16583
+ - \`PRIVATE\` - accessible only to signed-in users with access.
16584
+ - \`UNLISTED\` - accessible by direct link but hidden from public listings.
16585
+ - \`PUBLIC\` - visible in public listings and accessible by anyone.
16586
+
16587
+ ## Key aspects
16588
+
16589
+ - Does not modify the agent's behavior, system message, or tools.
16590
+ - Whitespace and letter case are normalized when persisted.
16591
+ - If multiple \`META VISIBILITY\` commitments are present, persistence keeps one normalized value.
16592
+ - Agents Server mirrors the value into the database for filtering, but the book is the source of truth.
16593
+
16594
+ ## Example
16595
+
16596
+ \`\`\`book
16597
+ Helpful Assistant
16598
+
16599
+ GOAL Be helpful and friendly.
16600
+ META VISIBILITY PUBLIC
16601
+ \`\`\`
16602
+ `);
16603
+ }
16604
+ applyToAgentModelRequirements(requirements, content) {
16605
+ // META VISIBILITY is metadata only and does not alter model requirements.
16606
+ return requirements;
16607
+ }
16608
+ }
16609
+ // Note: [💞] Ignore a discrepancy between file name and entity name
16610
+
16451
16611
  /**
16452
16612
  * META VOICE commitment definition
16453
16613
  *
@@ -22943,31 +23103,65 @@ class UseSearchEngineCommitmentDefinition extends BaseCommitmentDefinition {
22943
23103
  // Note: [💞] Ignore a discrepancy between file name and entity name
22944
23104
 
22945
23105
  /**
22946
- * This error type indicates that some limit was reached
23106
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
22947
23107
  *
22948
23108
  * @public exported from `@promptbook/core`
22949
23109
  */
22950
- class LimitReachedError extends Error {
23110
+ class ParseError extends Error {
22951
23111
  constructor(message) {
22952
23112
  super(message);
22953
- this.name = 'LimitReachedError';
22954
- Object.setPrototypeOf(this, LimitReachedError.prototype);
23113
+ this.name = 'ParseError';
23114
+ Object.setPrototypeOf(this, ParseError.prototype);
22955
23115
  }
22956
23116
  }
23117
+ // TODO: Maybe split `ParseError` and `ApplyError`
22957
23118
 
22958
23119
  /**
22959
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
23120
+ * Supported visibility states for persisted agents.
22960
23121
  *
22961
23122
  * @public exported from `@promptbook/core`
22962
23123
  */
22963
- class ParseError extends Error {
23124
+ const AGENT_VISIBILITY_VALUES = ['PRIVATE', 'UNLISTED', 'PUBLIC'];
23125
+ /**
23126
+ * Returns `true` when the value is one of supported visibility states.
23127
+ *
23128
+ * @param value - Raw value to validate.
23129
+ * @returns Whether the value is a valid `AgentVisibility`.
23130
+ *
23131
+ * @public exported from `@promptbook/core`
23132
+ */
23133
+ function isAgentVisibility(value) {
23134
+ return typeof value === 'string' && AGENT_VISIBILITY_VALUES.includes(value);
23135
+ }
23136
+ /**
23137
+ * Normalizes raw visibility text into a supported value.
23138
+ *
23139
+ * @param value - Raw visibility value.
23140
+ * @returns Normalized visibility, or `null` when invalid.
23141
+ *
23142
+ * @public exported from `@promptbook/core`
23143
+ */
23144
+ function normalizeAgentVisibility(value) {
23145
+ if (typeof value !== 'string') {
23146
+ return null;
23147
+ }
23148
+ const normalized = value.trim().toUpperCase();
23149
+ return isAgentVisibility(normalized) ? normalized : null;
23150
+ }
23151
+ // Note: [💞] Ignore a discrepancy between file name and entity name
23152
+
23153
+ /**
23154
+ * This error type indicates that some limit was reached
23155
+ *
23156
+ * @public exported from `@promptbook/core`
23157
+ */
23158
+ class LimitReachedError extends Error {
22964
23159
  constructor(message) {
22965
23160
  super(message);
22966
- this.name = 'ParseError';
22967
- Object.setPrototypeOf(this, ParseError.prototype);
23161
+ this.name = 'LimitReachedError';
23162
+ Object.setPrototypeOf(this, LimitReachedError.prototype);
22968
23163
  }
22969
23164
  }
22970
- // TODO: Maybe split `ParseError` and `ApplyError`
22971
23165
 
22972
23166
  /**
22973
23167
  * Maximum allowed source length for create-agent payloads.
@@ -25302,6 +25496,7 @@ const COMMITMENT_REGISTRY = [
25302
25496
  new MetaDomainCommitmentDefinition(),
25303
25497
  new MetaDisclaimerCommitmentDefinition(),
25304
25498
  new MetaInputPlaceholderCommitmentDefinition(),
25499
+ new MetaVisibilityCommitmentDefinition(),
25305
25500
  new MetaCommitmentDefinition(),
25306
25501
  new MetaVoiceCommitmentDefinition(),
25307
25502
  new NoteCommitmentDefinition('NOTE'),
@@ -25753,6 +25948,7 @@ const META_COMMITMENT_APPLIERS = {
25753
25948
  'META COLOR': applyMetaColorContent,
25754
25949
  'META FONT': applyMetaFontContent,
25755
25950
  'META VOICE': applyMetaVoiceContent,
25951
+ 'META VISIBILITY': applyMetaVisibilityContent,
25756
25952
  };
25757
25953
  /**
25758
25954
  * Applies META-style commitments that mutate parsed profile metadata.
@@ -25782,6 +25978,10 @@ function applyGenericMetaCommitment(state, content) {
25782
25978
  applyMetaAvatarContent(state, metaValue);
25783
25979
  return;
25784
25980
  }
25981
+ if (metaTypeRaw.toUpperCase() === 'VISIBILITY') {
25982
+ applyMetaVisibilityContent(state, metaValue);
25983
+ return;
25984
+ }
25785
25985
  const metaType = normalizeTo_camelCase(metaTypeRaw);
25786
25986
  state.meta[metaType] = metaValue;
25787
25987
  }
@@ -25858,6 +26058,15 @@ function applyMetaFontContent(state, content) {
25858
26058
  function applyMetaVoiceContent(state, content) {
25859
26059
  state.meta.voice = spaceTrim$1(content);
25860
26060
  }
26061
+ /**
26062
+ * Applies META VISIBILITY content into the normalized `meta.visibility` field.
26063
+ */
26064
+ function applyMetaVisibilityContent(state, content) {
26065
+ const visibility = normalizeAgentVisibility(content);
26066
+ if (visibility) {
26067
+ state.meta.visibility = visibility;
26068
+ }
26069
+ }
25861
26070
  /**
25862
26071
  * Normalizes the separator in the content
25863
26072
  *
@@ -37430,7 +37639,7 @@ async function tickAgentMessages(options, tickOptions = {}) {
37430
37639
  validateAgentRunOptions(options);
37431
37640
  const projectPath = tickOptions.projectPath || process.cwd();
37432
37641
  let queueSnapshot = await loadAgentMessageQueueSnapshot(projectPath);
37433
- let queuedMessage = queueSnapshot.queuedMessages[0];
37642
+ let queuedMessage = resolveSelectedQueuedMessage(queueSnapshot, tickOptions.queuedMessage);
37434
37643
  if (!queuedMessage) {
37435
37644
  announceNoQueuedMessages(tickOptions);
37436
37645
  return { isMessageProcessed: false };
@@ -37445,7 +37654,7 @@ async function tickAgentMessages(options, tickOptions = {}) {
37445
37654
  });
37446
37655
  if (autoPullTimestamp !== undefined) {
37447
37656
  queueSnapshot = await loadAgentMessageQueueSnapshot(projectPath);
37448
- queuedMessage = queueSnapshot.queuedMessages[0];
37657
+ queuedMessage = resolveSelectedQueuedMessage(queueSnapshot, tickOptions.queuedMessage);
37449
37658
  if (!queuedMessage) {
37450
37659
  if (tickOptions.uiHandle && !((_b = tickOptions.uiPresentation) === null || _b === void 0 ? void 0 : _b.isSharedDashboard)) {
37451
37660
  updateAgentRunUiForWatching(tickOptions.uiHandle, queueSnapshot);
@@ -37471,7 +37680,7 @@ async function tickAgentMessages(options, tickOptions = {}) {
37471
37680
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.updateProgress(((_e = tickOptions.uiPresentation) === null || _e === void 0 ? void 0 : _e.completedProgressStats) ||
37472
37681
  createAgentQueueProgressSnapshot({
37473
37682
  finishedMessageCount: queueSnapshot.finishedMessageCount + 1,
37474
- queuedMessages: queueSnapshot.queuedMessages.slice(1),
37683
+ queuedMessages: removeQueuedMessageFromSnapshot(queueSnapshot, queuedMessage),
37475
37684
  }));
37476
37685
  if ((_f = tickOptions.uiPresentation) === null || _f === void 0 ? void 0 : _f.completedAgentStatusLines) {
37477
37686
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAgentStatusLines([...tickOptions.uiPresentation.completedAgentStatusLines]);
@@ -37483,7 +37692,9 @@ async function tickAgentMessages(options, tickOptions = {}) {
37483
37692
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setMessagePreviewLines([...tickOptions.uiPresentation.completedMessagePreviewLines]);
37484
37693
  }
37485
37694
  if ((_j = tickOptions.uiPresentation) === null || _j === void 0 ? void 0 : _j.completedMessagePreviewSections) {
37486
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setMessagePreviewSections([...tickOptions.uiPresentation.completedMessagePreviewSections]);
37695
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setMessagePreviewSections([
37696
+ ...tickOptions.uiPresentation.completedMessagePreviewSections,
37697
+ ]);
37487
37698
  }
37488
37699
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Message answered');
37489
37700
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('done');
@@ -37511,6 +37722,21 @@ async function tickAgentMessages(options, tickOptions = {}) {
37511
37722
  printAgentGitIdentityTipAtProcessExitIfNeeded();
37512
37723
  }
37513
37724
  }
37725
+ /**
37726
+ * Resolves either the explicitly selected queued message or the first available queue item.
37727
+ */
37728
+ function resolveSelectedQueuedMessage(queueSnapshot, selectedQueuedMessage) {
37729
+ if (!selectedQueuedMessage) {
37730
+ return queueSnapshot.queuedMessages[0];
37731
+ }
37732
+ return queueSnapshot.queuedMessages.find((queuedMessage) => queuedMessage.relativePath === selectedQueuedMessage.relativePath);
37733
+ }
37734
+ /**
37735
+ * Creates a progress queue list with the processed message removed from its original snapshot.
37736
+ */
37737
+ function removeQueuedMessageFromSnapshot(queueSnapshot, processedQueuedMessage) {
37738
+ return queueSnapshot.queuedMessages.filter((queuedMessage) => queuedMessage.relativePath !== processedQueuedMessage.relativePath);
37739
+ }
37514
37740
  /**
37515
37741
  * Runs the selected coding runner for one message and finalizes the answered file.
37516
37742
  */
@@ -37631,7 +37857,9 @@ function seedAgentRunUiHandle(uiHandle, options, runner, actualRunnerModel, queu
37631
37857
  return;
37632
37858
  }
37633
37859
  uiHandle.state.setCurrentPrompt(queuedMessage.relativePath);
37634
- uiHandle.state.setMessagePreviewLines([...((uiPresentation === null || uiPresentation === void 0 ? void 0 : uiPresentation.messagePreviewLines) || agentUiMetadata.latestUserMessageLines)]);
37860
+ uiHandle.state.setMessagePreviewLines([
37861
+ ...((uiPresentation === null || uiPresentation === void 0 ? void 0 : uiPresentation.messagePreviewLines) || agentUiMetadata.latestUserMessageLines),
37862
+ ]);
37635
37863
  uiHandle.state.setPhase('loading');
37636
37864
  uiHandle.state.setStatusMessage('Preparing message');
37637
37865
  }
@@ -37910,12 +38138,16 @@ const LEGACY_STATUS_WIDTH = 9;
37910
38138
  * Watches all direct child agent repositories from the current directory in one shared session.
37911
38139
  */
37912
38140
  async function runMultipleAgentMessages(options, controls = {}) {
37913
- var _a;
38141
+ var _a, _b;
37914
38142
  validateAgentRunOptions(options);
37915
38143
  validateAgentWatchOptions('ptbk agent-folder run-multiple', options);
37916
38144
  const rootPath = process.cwd();
37917
38145
  const shouldContinue = controls.shouldContinue || (() => just(true));
38146
+ const queuePollIntervalMs = (_a = controls.queuePollIntervalMs) !== null && _a !== void 0 ? _a : MULTI_AGENT_QUEUE_POLL_INTERVAL_MS;
37918
38147
  const ignoreMatcher = createAgentIgnoreMatcher(options.ignorePatterns);
38148
+ const isParallelMessageLimitConfigured = options.maxParallelMessages !== undefined;
38149
+ const maxParallelMessageCount = normalizeMaxParallelMessageCount(options.maxParallelMessages);
38150
+ const activeMessageTasksByKey = new Map();
37919
38151
  let githubSynchronizationTimestamp;
37920
38152
  let githubIgnoredRepositoryCount = 0;
37921
38153
  const autoPullTimestampsByProjectPath = new Map();
@@ -37930,7 +38162,7 @@ async function runMultipleAgentMessages(options, controls = {}) {
37930
38162
  if (!isWatchSessionInitialized) {
37931
38163
  uiHandle = await initializeMultipleAgentRunUi(options);
37932
38164
  isWatchSessionInitialized = true;
37933
- (_a = controls.onUiInitialized) === null || _a === void 0 ? void 0 : _a.call(controls, uiHandle);
38165
+ (_b = controls.onUiInitialized) === null || _b === void 0 ? void 0 : _b.call(controls, uiHandle);
37934
38166
  if (!uiHandle) {
37935
38167
  console.info(colors.green('Watching direct child agent repositories for queued messages.'));
37936
38168
  }
@@ -37953,14 +38185,19 @@ async function runMultipleAgentMessages(options, controls = {}) {
37953
38185
  let projectSummaries = projectSummariesResult.projectSummaries;
37954
38186
  lastObservedProjectCount = projectSummaries.length;
37955
38187
  let ignoredAgentCount = projectSummariesResult.ignoredProjectCount + githubIgnoredRepositoryCount;
37956
- const autoPullResult = await pullLatestChangesForLocalAgentRunnerProjectsIfNeeded({
37957
- rootPath,
37958
- runOptions: options,
37959
- uiHandle,
37960
- projectSummaries,
37961
- ignoredAgentCount,
37962
- autoPullTimestampsByProjectPath,
37963
- });
38188
+ const autoPullResult = activeMessageTasksByKey.size === 0
38189
+ ? await pullLatestChangesForLocalAgentRunnerProjectsIfNeeded({
38190
+ rootPath,
38191
+ runOptions: options,
38192
+ uiHandle,
38193
+ projectSummaries,
38194
+ ignoredAgentCount,
38195
+ autoPullTimestampsByProjectPath,
38196
+ })
38197
+ : {
38198
+ isAnyRepositoryPulled: false,
38199
+ pulledProjectPaths: new Set(),
38200
+ };
37964
38201
  if (autoPullResult.isAnyRepositoryPulled) {
37965
38202
  projectSummariesResult = await loadLocalAgentRunnerProjectSummaries(rootPath, {
37966
38203
  includeMessagePreviews: Boolean(uiHandle),
@@ -37970,61 +38207,49 @@ async function runMultipleAgentMessages(options, controls = {}) {
37970
38207
  lastObservedProjectCount = projectSummaries.length;
37971
38208
  ignoredAgentCount = projectSummariesResult.ignoredProjectCount + githubIgnoredRepositoryCount;
37972
38209
  }
37973
- const queuedProjects = projectSummaries.filter((projectSummary) => projectSummary.queuedMessageCount > 0);
37974
- if (queuedProjects.length === 0) {
38210
+ const availableParallelMessageSlots = Math.max(0, maxParallelMessageCount - activeMessageTasksByKey.size);
38211
+ const queuedWorkItems = availableParallelMessageSlots > 0
38212
+ ? selectQueuedWorkItems({
38213
+ projectSummaries,
38214
+ activeMessageTasksByKey,
38215
+ availableParallelMessageSlots,
38216
+ isParallelMessageLimitConfigured,
38217
+ })
38218
+ : [];
38219
+ const answeringProjectPaths = createAnsweringProjectPaths(activeMessageTasksByKey, queuedWorkItems);
38220
+ if (queuedWorkItems.length === 0 && activeMessageTasksByKey.size === 0) {
37975
38221
  updateMultipleAgentRunUiForWatching(uiHandle, options, rootPath, projectSummaries, ignoredAgentCount);
37976
- await wait$3(MULTI_AGENT_QUEUE_POLL_INTERVAL_MS);
38222
+ await wait$3(queuePollIntervalMs);
38223
+ continue;
38224
+ }
38225
+ if (queuedWorkItems.length === 0) {
38226
+ updateMultipleAgentRunUiForAnswering(uiHandle, options, rootPath, projectSummaries, answeringProjectPaths, ignoredAgentCount, activeMessageTasksByKey.size);
38227
+ await waitForNextSchedulerTurn(activeMessageTasksByKey, queuePollIntervalMs);
37977
38228
  continue;
37978
38229
  }
37979
38230
  if (!uiHandle) {
37980
- for (const queuedProject of queuedProjects) {
37981
- console.info(colors.blue(`Processing ${formatProjectPath(rootPath, queuedProject.project.projectPath)} with ${queuedProject.localAgentName}.`));
38231
+ for (const queuedWorkItem of queuedWorkItems) {
38232
+ console.info(colors.blue(`Processing ${formatProjectPath(rootPath, queuedWorkItem.projectSummary.project.projectPath)}/${queuedWorkItem.queuedMessage.relativePath} with ${queuedWorkItem.projectSummary.localAgentName}.`));
37982
38233
  }
37983
38234
  }
37984
- const answeringProjectPaths = new Set(queuedProjects.map((queuedProject) => queuedProject.project.projectPath));
37985
- if (uiHandle) {
37986
- updateMultipleAgentRunUiForAnswering(uiHandle, options, rootPath, projectSummaries, answeringProjectPaths, ignoredAgentCount);
37987
- }
37988
- const tickResults = await Promise.allSettled(queuedProjects.map(async (queuedProject) => {
37989
- const tickRunOptions = createAgentRunOptionsForQueuedProjectTick({
37990
- runOptions: options,
37991
- isProjectPulledInCurrentIteration: autoPullResult.pulledProjectPaths.has(queuedProject.project.projectPath),
37992
- });
37993
- const tickResult = await tickAgentMessages(tickRunOptions, {
37994
- isQuietWhenIdle: true,
37995
- projectPath: queuedProject.project.projectPath,
38235
+ updateMultipleAgentRunUiForAnswering(uiHandle, options, rootPath, projectSummaries, answeringProjectPaths, ignoredAgentCount, activeMessageTasksByKey.size + queuedWorkItems.length);
38236
+ for (const queuedWorkItem of queuedWorkItems) {
38237
+ startActiveAgentMessageTask({
38238
+ rootPath,
38239
+ options,
38240
+ queuedWorkItem,
38241
+ projectSummaries,
38242
+ answeringProjectPaths,
38243
+ ignoredAgentCount,
38244
+ autoPullResult,
38245
+ autoPullTimestampsByProjectPath,
38246
+ activeMessageTasksByKey,
38247
+ messageFailureTracker,
37996
38248
  uiHandle,
37997
- uiPresentation: uiHandle
37998
- ? buildMultiAgentTickUiPresentation({
37999
- rootPath,
38000
- projectSummaries,
38001
- answeringProjectPaths,
38002
- ignoredAgentCount,
38003
- })
38004
- : undefined,
38005
- });
38006
- return {
38007
- projectPath: queuedProject.project.projectPath,
38008
- tickResult,
38009
- };
38010
- }));
38011
- for (const tickResult of tickResults) {
38012
- if (tickResult.status === 'fulfilled') {
38013
- if (tickResult.value.tickResult.autoPullTimestamp !== undefined) {
38014
- autoPullTimestampsByProjectPath.set(tickResult.value.projectPath, tickResult.value.tickResult.autoPullTimestamp);
38015
- }
38016
- if (tickResult.value.tickResult.isMessageProcessed) {
38017
- messageFailureTracker.clearMessageFailure(tickResult.value.projectPath, tickResult.value.tickResult.queuedMessage);
38018
- }
38019
- continue;
38020
- }
38021
- await handleAgentWatchError({
38022
- commandDisplayName: 'ptbk agent-folder run-multiple',
38023
- logDirectoryPath: controls.watchErrorLogDirectoryPath || rootPath,
38024
- error: tickResult.reason,
38249
+ watchErrorLogDirectoryPath: controls.watchErrorLogDirectoryPath || rootPath,
38025
38250
  });
38026
- await messageFailureTracker.recordFailure(tickResult.reason);
38027
38251
  }
38252
+ await waitForNextSchedulerTurn(activeMessageTasksByKey, queuePollIntervalMs);
38028
38253
  }
38029
38254
  catch (error) {
38030
38255
  await handleAgentWatchError({
@@ -38033,9 +38258,167 @@ async function runMultipleAgentMessages(options, controls = {}) {
38033
38258
  error,
38034
38259
  });
38035
38260
  await messageFailureTracker.recordFailure(error);
38036
- await wait$3(MULTI_AGENT_QUEUE_POLL_INTERVAL_MS);
38261
+ await wait$3(queuePollIntervalMs);
38262
+ }
38263
+ }
38264
+ await waitForActiveMessageTasksToSettle(activeMessageTasksByKey);
38265
+ }
38266
+ /**
38267
+ * Starts one selected queued-message harness task and records it until it settles.
38268
+ */
38269
+ function startActiveAgentMessageTask(options) {
38270
+ const { queuedWorkItem } = options;
38271
+ const projectPath = queuedWorkItem.projectSummary.project.projectPath;
38272
+ const messageKey = createAgentMessageTaskKey(projectPath, queuedWorkItem.queuedMessage);
38273
+ const promise = runActiveAgentMessageTask({
38274
+ rootPath: options.rootPath,
38275
+ options: options.options,
38276
+ queuedWorkItem,
38277
+ projectSummaries: options.projectSummaries,
38278
+ answeringProjectPaths: options.answeringProjectPaths,
38279
+ ignoredAgentCount: options.ignoredAgentCount,
38280
+ autoPullResult: options.autoPullResult,
38281
+ autoPullTimestampsByProjectPath: options.autoPullTimestampsByProjectPath,
38282
+ messageFailureTracker: options.messageFailureTracker,
38283
+ uiHandle: options.uiHandle,
38284
+ watchErrorLogDirectoryPath: options.watchErrorLogDirectoryPath,
38285
+ }).finally(() => {
38286
+ options.activeMessageTasksByKey.delete(messageKey);
38287
+ });
38288
+ options.activeMessageTasksByKey.set(messageKey, {
38289
+ projectPath,
38290
+ queuedMessage: queuedWorkItem.queuedMessage,
38291
+ promise,
38292
+ });
38293
+ }
38294
+ /**
38295
+ * Runs one queued-message tick and folds its result back into shared watcher state.
38296
+ */
38297
+ async function runActiveAgentMessageTask(options) {
38298
+ const projectPath = options.queuedWorkItem.projectSummary.project.projectPath;
38299
+ try {
38300
+ const tickRunOptions = createAgentRunOptionsForQueuedProjectTick({
38301
+ runOptions: options.options,
38302
+ isProjectPulledInCurrentIteration: options.autoPullResult.pulledProjectPaths.has(projectPath),
38303
+ });
38304
+ const tickResult = await tickAgentMessages(tickRunOptions, {
38305
+ isQuietWhenIdle: true,
38306
+ projectPath,
38307
+ queuedMessage: options.queuedWorkItem.queuedMessage,
38308
+ uiHandle: options.uiHandle,
38309
+ uiPresentation: options.uiHandle
38310
+ ? buildMultiAgentTickUiPresentation({
38311
+ rootPath: options.rootPath,
38312
+ projectSummaries: options.projectSummaries,
38313
+ answeringProjectPaths: options.answeringProjectPaths,
38314
+ ignoredAgentCount: options.ignoredAgentCount,
38315
+ })
38316
+ : undefined,
38317
+ });
38318
+ if (tickResult.autoPullTimestamp !== undefined) {
38319
+ options.autoPullTimestampsByProjectPath.set(projectPath, tickResult.autoPullTimestamp);
38320
+ }
38321
+ if (tickResult.isMessageProcessed) {
38322
+ options.messageFailureTracker.clearMessageFailure(projectPath, tickResult.queuedMessage);
38323
+ }
38324
+ }
38325
+ catch (error) {
38326
+ await handleAgentWatchError({
38327
+ commandDisplayName: 'ptbk agent-folder run-multiple',
38328
+ logDirectoryPath: options.watchErrorLogDirectoryPath,
38329
+ error,
38330
+ });
38331
+ await options.messageFailureTracker.recordFailure(error);
38332
+ }
38333
+ }
38334
+ /**
38335
+ * Selects the next queued messages that can be started without exceeding active harness capacity.
38336
+ */
38337
+ function selectQueuedWorkItems(options) {
38338
+ const activeProjectPaths = new Set(Array.from(options.activeMessageTasksByKey.values()).map((activeMessageTask) => activeMessageTask.projectPath));
38339
+ const queuedProjectSummaries = options.projectSummaries.filter((projectSummary) => {
38340
+ if (projectSummary.queuedMessageCount === 0) {
38341
+ return false;
38342
+ }
38343
+ return options.isParallelMessageLimitConfigured || !activeProjectPaths.has(projectSummary.project.projectPath);
38344
+ });
38345
+ if (!options.isParallelMessageLimitConfigured) {
38346
+ return queuedProjectSummaries
38347
+ .map((projectSummary) => selectFirstInactiveQueuedWorkItem(projectSummary, options.activeMessageTasksByKey))
38348
+ .filter((workItem) => workItem !== null);
38349
+ }
38350
+ const workItems = [];
38351
+ for (let messageIndex = 0; workItems.length < options.availableParallelMessageSlots; messageIndex++) {
38352
+ let isAnyMessageAtIndex = false;
38353
+ for (const projectSummary of queuedProjectSummaries) {
38354
+ const queuedMessage = projectSummary.queuedMessages[messageIndex];
38355
+ if (!queuedMessage) {
38356
+ continue;
38357
+ }
38358
+ isAnyMessageAtIndex = true;
38359
+ if (options.activeMessageTasksByKey.has(createAgentMessageTaskKey(projectSummary.project.projectPath, queuedMessage))) {
38360
+ continue;
38361
+ }
38362
+ workItems.push({ projectSummary, queuedMessage });
38363
+ if (workItems.length >= options.availableParallelMessageSlots) {
38364
+ return workItems;
38365
+ }
38366
+ }
38367
+ if (!isAnyMessageAtIndex) {
38368
+ return workItems;
38037
38369
  }
38038
38370
  }
38371
+ return workItems;
38372
+ }
38373
+ /**
38374
+ * Selects the first inactive queued message from a project while preserving legacy one-message-per-project behavior.
38375
+ */
38376
+ function selectFirstInactiveQueuedWorkItem(projectSummary, activeMessageTasksByKey) {
38377
+ const queuedMessage = projectSummary.queuedMessages.find((message) => !activeMessageTasksByKey.has(createAgentMessageTaskKey(projectSummary.project.projectPath, message)));
38378
+ return queuedMessage ? { projectSummary, queuedMessage } : null;
38379
+ }
38380
+ /**
38381
+ * Builds the set of project paths currently represented by active or newly scheduled harness tasks.
38382
+ */
38383
+ function createAnsweringProjectPaths(activeMessageTasksByKey, queuedWorkItems) {
38384
+ return new Set([
38385
+ ...Array.from(activeMessageTasksByKey.values()).map((activeMessageTask) => activeMessageTask.projectPath),
38386
+ ...queuedWorkItems.map((queuedWorkItem) => queuedWorkItem.projectSummary.project.projectPath),
38387
+ ]);
38388
+ }
38389
+ /**
38390
+ * Creates a stable scheduler key for one queued message file.
38391
+ */
38392
+ function createAgentMessageTaskKey(projectPath, queuedMessage) {
38393
+ return `${projectPath}\0${queuedMessage.relativePath}`;
38394
+ }
38395
+ /**
38396
+ * Normalizes the maximum number of parallel queued-message tasks.
38397
+ */
38398
+ function normalizeMaxParallelMessageCount(rawValue) {
38399
+ const parsedValue = Number(rawValue);
38400
+ if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
38401
+ return Number.POSITIVE_INFINITY;
38402
+ }
38403
+ return Math.floor(parsedValue);
38404
+ }
38405
+ /**
38406
+ * Waits for either one active task to finish or the next queue polling interval.
38407
+ */
38408
+ async function waitForNextSchedulerTurn(activeMessageTasksByKey, queuePollIntervalMs) {
38409
+ if (activeMessageTasksByKey.size === 0) {
38410
+ return;
38411
+ }
38412
+ await Promise.race([
38413
+ ...Array.from(activeMessageTasksByKey.values()).map((activeMessageTask) => activeMessageTask.promise),
38414
+ wait$3(queuePollIntervalMs),
38415
+ ]);
38416
+ }
38417
+ /**
38418
+ * Preserves previous shutdown behavior by letting active harness tasks finish before returning.
38419
+ */
38420
+ async function waitForActiveMessageTasksToSettle(activeMessageTasksByKey) {
38421
+ await Promise.allSettled(Array.from(activeMessageTasksByKey.values()).map((activeMessageTask) => activeMessageTask.promise));
38039
38422
  }
38040
38423
  /**
38041
38424
  * Creates the shared multi-agent rich UI when the terminal supports it.
@@ -38181,6 +38564,7 @@ async function loadLocalAgentRunnerProjectSummaries(rootPath, options) {
38181
38564
  project,
38182
38565
  localAgentName: localAgentIdentity.localAgentName,
38183
38566
  localAgentUrl: localAgentIdentity.localAgentUrl || formatProjectPath(rootPath, project.projectPath),
38567
+ queuedMessages: queueSnapshot.queuedMessages,
38184
38568
  queuedMessageCount: queueSnapshot.queuedMessages.length,
38185
38569
  finishedMessageCount: queueSnapshot.finishedMessageCount,
38186
38570
  ...(queuedMessagePreview ? { queuedMessagePreview } : {}),
@@ -38212,14 +38596,16 @@ function updateMultipleAgentRunUiForWatching(uiHandle, options, rootPath, projec
38212
38596
  /**
38213
38597
  * Updates the shared UI while one or more child repositories are answering queued messages.
38214
38598
  */
38215
- function updateMultipleAgentRunUiForAnswering(uiHandle, options, rootPath, projectSummaries, answeringProjectPaths, ignoredAgentCount) {
38216
- const activeProjectCount = projectSummaries.filter((projectSummary) => answeringProjectPaths.has(projectSummary.project.projectPath)).length;
38599
+ function updateMultipleAgentRunUiForAnswering(uiHandle, options, rootPath, projectSummaries, answeringProjectPaths, ignoredAgentCount, activeMessageCount) {
38600
+ if (!uiHandle) {
38601
+ return;
38602
+ }
38217
38603
  setMultipleAgentRunUiConfig(uiHandle, options, projectSummaries.length, ignoredAgentCount);
38218
38604
  uiHandle.state.updateProgress(createMultiAgentQueueProgressSnapshot(projectSummaries));
38219
38605
  uiHandle.state.setCurrentPrompt('');
38220
38606
  uiHandle.state.setCurrentScriptPaths(buildMultiAgentScriptPaths(projectSummaries, answeringProjectPaths));
38221
38607
  uiHandle.state.setPhase('running');
38222
- uiHandle.state.setStatusMessage(`Answering ${activeProjectCount} queued message${activeProjectCount === 1 ? '' : 's'}`);
38608
+ uiHandle.state.setStatusMessage(`Answering ${activeMessageCount} queued message${activeMessageCount === 1 ? '' : 's'}`);
38223
38609
  uiHandle.state.setDetailLines(buildMultiAgentAnsweringDetailLines(rootPath, projectSummaries, answeringProjectPaths));
38224
38610
  uiHandle.state.setAgentStatusLines(buildMultiAgentStatusLines(rootPath, projectSummaries, answeringProjectPaths));
38225
38611
  uiHandle.state.setAgentStatusTableRows(buildMultiAgentStatusTableRows(projectSummaries, answeringProjectPaths));
@@ -38521,6 +38907,12 @@ const DEFAULT_SPAWN_AGENT_RATE_LIMIT_WINDOW_MS = 10 * 60 * 1000;
38521
38907
  * @private shared Agents Server constant
38522
38908
  */
38523
38909
  const DEFAULT_LOCAL_AGENT_RUNNER_MAX_FAILED_ATTEMPTS = 3;
38910
+ /**
38911
+ * Default maximum number of local runner harness instances allowed to answer queued messages at once.
38912
+ *
38913
+ * @private shared Agents Server constant
38914
+ */
38915
+ const DEFAULT_LOCAL_AGENT_RUNNER_MAX_PARALLEL_MESSAGES = 3;
38524
38916
  /**
38525
38917
  * Stable keys used by the dedicated server-limits table.
38526
38918
  *
@@ -38535,6 +38927,7 @@ const SERVER_LIMIT_KEYS = {
38535
38927
  SPAWN_AGENT_RATE_LIMIT_MAX: 'SPAWN_AGENT_RATE_LIMIT_MAX',
38536
38928
  SPAWN_AGENT_RATE_LIMIT_WINDOW_MS: 'SPAWN_AGENT_RATE_LIMIT_WINDOW_MS',
38537
38929
  LOCAL_AGENT_RUNNER_MAX_FAILED_ATTEMPTS: 'LOCAL_AGENT_RUNNER_MAX_FAILED_ATTEMPTS',
38930
+ LOCAL_AGENT_RUNNER_MAX_PARALLEL_MESSAGES: 'LOCAL_AGENT_RUNNER_MAX_PARALLEL_MESSAGES',
38538
38931
  };
38539
38932
  /**
38540
38933
  * Shared metadata for all server limits exposed in the admin UI and runtime loaders.
@@ -38630,6 +39023,17 @@ const SERVER_LIMIT_DEFINITIONS = [
38630
39023
  step: 1,
38631
39024
  legacyMetadataKeys: [],
38632
39025
  },
39026
+ {
39027
+ key: SERVER_LIMIT_KEYS.LOCAL_AGENT_RUNNER_MAX_PARALLEL_MESSAGES,
39028
+ category: 'Local agent runner',
39029
+ title: 'Max parallel harness instances',
39030
+ description: 'Caps how many queued chat messages the local coding-agent harness may answer at the same time.',
39031
+ unit: 'count',
39032
+ defaultValue: DEFAULT_LOCAL_AGENT_RUNNER_MAX_PARALLEL_MESSAGES,
39033
+ minimumValue: 1,
39034
+ step: 1,
39035
+ legacyMetadataKeys: [],
39036
+ },
38633
39037
  ];
38634
39038
  /**
38635
39039
  * Fast lookup map used by server-limit validators and UI helpers.
@@ -38991,6 +39395,7 @@ function createLocalAgentRunOptions(options, localAgentRunnerLimits) {
38991
39395
  autoPull: false,
38992
39396
  autoClone: false,
38993
39397
  maxMessageProcessingFailures: localAgentRunnerLimits.maxFailedAttempts,
39398
+ maxParallelMessages: localAgentRunnerLimits.maxParallelMessages,
38994
39399
  };
38995
39400
  }
38996
39401
  /**
@@ -39002,7 +39407,7 @@ async function waitForLocalAgentRunnerLimits(options) {
39002
39407
  while (options.state.isContinuing && Date.now() - startedAt < INTERNAL_SERVER_LIMITS_STARTUP_TIMEOUT_MS) {
39003
39408
  try {
39004
39409
  const limits = await fetchLocalAgentRunnerLimits(options);
39005
- logRunnerEvent(options.logStreams.runner, `Local agent runner max failed attempts: ${limits.maxFailedAttempts}.`);
39410
+ logRunnerEvent(options.logStreams.runner, `Local agent runner limits: ${limits.maxFailedAttempts} failed attempt(s), ${limits.maxParallelMessages} parallel message(s).`);
39006
39411
  return limits;
39007
39412
  }
39008
39413
  catch (error) {
@@ -39013,6 +39418,7 @@ async function waitForLocalAgentRunnerLimits(options) {
39013
39418
  if (!options.state.isContinuing) {
39014
39419
  return {
39015
39420
  maxFailedAttempts: DEFAULT_LOCAL_AGENT_RUNNER_MAX_FAILED_ATTEMPTS,
39421
+ maxParallelMessages: DEFAULT_LOCAL_AGENT_RUNNER_MAX_PARALLEL_MESSAGES,
39016
39422
  };
39017
39423
  }
39018
39424
  throw new NotAllowed(spaceTrim$1(`
@@ -39039,6 +39445,7 @@ async function fetchLocalAgentRunnerLimits(options) {
39039
39445
  const payload = (await response.json());
39040
39446
  return {
39041
39447
  maxFailedAttempts: normalizeLocalAgentRunnerMaxFailedAttempts(payload.maxFailedAttempts),
39448
+ maxParallelMessages: normalizeLocalAgentRunnerMaxParallelMessages(payload.maxParallelMessages),
39042
39449
  };
39043
39450
  }
39044
39451
  /**
@@ -39051,6 +39458,16 @@ function normalizeLocalAgentRunnerMaxFailedAttempts(rawValue) {
39051
39458
  }
39052
39459
  return Math.floor(parsedValue);
39053
39460
  }
39461
+ /**
39462
+ * Normalizes the local runner parallel message cap returned by the internal server route.
39463
+ */
39464
+ function normalizeLocalAgentRunnerMaxParallelMessages(rawValue) {
39465
+ const parsedValue = Number(rawValue);
39466
+ if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
39467
+ return DEFAULT_LOCAL_AGENT_RUNNER_MAX_PARALLEL_MESSAGES;
39468
+ }
39469
+ return Math.floor(parsedValue);
39470
+ }
39054
39471
  /**
39055
39472
  * Starts periodic internal worker calls that queue and reconcile local message-folder jobs.
39056
39473
  */
@@ -40305,7 +40722,7 @@ async function ensureCoderGitignoreFile(projectPath) {
40305
40722
  */
40306
40723
  const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
40307
40724
  'coder:generate-boilerplates': 'ptbk coder generate-boilerplates --template ./prompts/templates/common.md',
40308
- 'coder:run': 'ptbk coder run --harness openai-codex --model gpt-5.4 --thinking-level xhigh --agent agents/developer.book --context AGENTS.md',
40725
+ 'coder:run': 'ptbk coder run --harness openai-codex --model gpt-5.5 --thinking-level xhigh --agent agents/developer.book --context AGENTS.md',
40309
40726
  // 'coder:find-refactor-candidates': 'ptbk coder find-refactor-candidates',
40310
40727
  'coder:verify': 'ptbk coder verify',
40311
40728
  };
@@ -40785,6 +41202,7 @@ function $initializeCoderRunCommand(program) {
40785
41202
  command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
40786
41203
  addPromptRunnerExecutionOptions(command);
40787
41204
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption$1, 0);
41205
+ command.option('--limit <run-count>', 'Stop after processing this many prompt runs', parsePositiveIntegerOption);
40788
41206
  command.option('--wait-after-prompt <duration>', spaceTrim$1(`
40789
41207
  Wait this long after each prompt has been implemented, verified and committed before starting the next prompt.
40790
41208
  Accepts durations like 1h, 30m, 5s. Defaults to 0 (no wait).
@@ -40803,7 +41221,7 @@ function $initializeCoderRunCommand(program) {
40803
41221
  command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
40804
41222
  command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
40805
41223
  command.action(handleActionErrors(async (cliOptions) => {
40806
- const { dryRun, agent, context, test, preserveLogs, priority, waitAfterPrompt: waitAfterPromptValue, waitBetweenPrompts: waitBetweenPromptsValue, waitAfterError: waitAfterErrorValue, auto, autoMigrate, allowDestructiveAutoMigrate, } = cliOptions;
41224
+ const { dryRun, agent, context, test, preserveLogs, priority, limit, waitAfterPrompt: waitAfterPromptValue, waitBetweenPrompts: waitBetweenPromptsValue, waitAfterError: waitAfterErrorValue, auto, autoMigrate, allowDestructiveAutoMigrate, } = cliOptions;
40807
41225
  const testCommand = normalizeCommandOptionValue$1(test);
40808
41226
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
40809
41227
  isAgentRequired: !dryRun,
@@ -40836,6 +41254,7 @@ function $initializeCoderRunCommand(program) {
40836
41254
  noUi: runnerOptions.noUi,
40837
41255
  thinkingLevel: runnerOptions.thinkingLevel,
40838
41256
  priority,
41257
+ limit,
40839
41258
  normalizeLineEndings: runnerOptions.normalizeLineEndings,
40840
41259
  allowCredits: runnerOptions.allowCredits,
40841
41260
  autoMigrate,
@@ -40870,6 +41289,22 @@ function parseIntOption$1(value) {
40870
41289
  }
40871
41290
  return parsed;
40872
41291
  }
41292
+ /**
41293
+ * Parses a positive integer option value.
41294
+ *
41295
+ * @private internal utility of `coder run` command
41296
+ */
41297
+ function parsePositiveIntegerOption(value) {
41298
+ const parsed = Number(value);
41299
+ if (!Number.isInteger(parsed) || parsed <= 0) {
41300
+ throw new NotAllowed(spaceTrim$1(`
41301
+ Invalid value for \`--limit\`: \`${value}\`.
41302
+
41303
+ Use a positive integer.
41304
+ `));
41305
+ }
41306
+ return parsed;
41307
+ }
40873
41308
  /**
40874
41309
  * Joins one Commander option that may be parsed either as a single string or a variadic token array.
40875
41310
  *
@@ -70285,7 +70720,7 @@ const DEFAULT_WAIT_AFTER_ERROR_MS = 10 * 60 * 1000;
70285
70720
  /**
70286
70721
  * CLI usage text for this script.
70287
70722
  */
70288
- const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--preserve-logs] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--wait-after-prompt <duration>] [--wait-between-prompts <duration>] [--wait-after-error <duration>] [--no-auto] [--no-commit] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push] [--auto-pull]';
70723
+ const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--preserve-logs] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--limit <run-count>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--wait-after-prompt <duration>] [--wait-between-prompts <duration>] [--wait-after-error <duration>] [--no-auto] [--no-commit] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push] [--auto-pull]';
70289
70724
  /**
70290
70725
  * Top-level flags supported by this command.
70291
70726
  */
@@ -70299,6 +70734,7 @@ const KNOWN_OPTION_FLAGS = new Set([
70299
70734
  '--no-ui',
70300
70735
  '--thinking-level',
70301
70736
  '--priority',
70737
+ '--limit',
70302
70738
  '--allow-credits',
70303
70739
  '--auto-migrate',
70304
70740
  '--allow-destructive-auto-migrate',
@@ -70335,6 +70771,8 @@ function parseRunOptions(args) {
70335
70771
  const thinkingLevelValue = readOptionValue(args, '--thinking-level');
70336
70772
  const hasPriorityFlag = args.includes('--priority');
70337
70773
  const priority = parsePriority(readOptionValue(args, '--priority'), hasPriorityFlag);
70774
+ const hasLimitFlag = args.includes('--limit');
70775
+ const limit = parseLimit(readOptionValue(args, '--limit'), hasLimitFlag);
70338
70776
  const noCommit = args.includes('--no-commit');
70339
70777
  const ignoreGitChanges = args.includes('--ignore-git-changes');
70340
70778
  const normalizeLineEndings = !args.includes('--no-normalize-line-endings');
@@ -70389,6 +70827,7 @@ function parseRunOptions(args) {
70389
70827
  testCommand,
70390
70828
  thinkingLevel,
70391
70829
  priority,
70830
+ limit,
70392
70831
  };
70393
70832
  }
70394
70833
  /**
@@ -70452,6 +70891,22 @@ function parsePriority(priorityValue, hasPriorityFlag) {
70452
70891
  }
70453
70892
  return priority;
70454
70893
  }
70894
+ /**
70895
+ * Parses and validates the optional successful prompt-run limit.
70896
+ */
70897
+ function parseLimit(limitValue, hasLimitFlag) {
70898
+ if (limitValue === undefined) {
70899
+ if (hasLimitFlag) {
70900
+ exitWithUsageError('Missing value for --limit. Use a positive integer.');
70901
+ }
70902
+ return undefined;
70903
+ }
70904
+ const limit = Number(limitValue);
70905
+ if (!Number.isInteger(limit) || limit <= 0) {
70906
+ exitWithUsageError(`Invalid value for --limit: "${limitValue}". Use a positive integer.`);
70907
+ }
70908
+ return limit;
70909
+ }
70455
70910
  /**
70456
70911
  * Prints an argument error with usage and exits the process.
70457
70912
  */
@@ -72561,11 +73016,12 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
72561
73016
  // Prepend agent system message before the task so the harness sees agent instructions first
72562
73017
  const promptWithAgent = resolvedAgentSystemMessage
72563
73018
  ? spaceTrim$1((block) => `
72564
- ${block(resolvedAgentSystemMessage.trim())}
72565
-
73019
+
72566
73020
  ## Your Task
72567
73021
 
72568
73022
  ${block(taskPrompt)}
73023
+
73024
+ ${block(resolvedAgentSystemMessage.trim())}
72569
73025
  `)
72570
73026
  : taskPrompt;
72571
73027
  const codexPrompt = appendCoderContext(promptWithAgent, resolvedCoderContext);
@@ -72888,6 +73344,7 @@ async function runCodexPrompts(providedOptions) {
72888
73344
  let hasWaitedForStart = false;
72889
73345
  let previousRoundStartTime;
72890
73346
  let previousRoundEndTime;
73347
+ let completedRunCount = 0;
72891
73348
  while (just(true)) {
72892
73349
  if (options.autoPull && !options.dryRun) {
72893
73350
  await waitForRequestedPause({
@@ -72973,6 +73430,15 @@ async function runCodexPrompts(providedOptions) {
72973
73430
  });
72974
73431
  previousRoundStartTime = currentRoundStartTime;
72975
73432
  previousRoundEndTime = Date.now();
73433
+ completedRunCount += 1;
73434
+ if (isRunLimitReached({ completedRunCount, limit: options.limit })) {
73435
+ finishWhenRunLimitIsReached({
73436
+ completedRunCount,
73437
+ isRichUiEnabled,
73438
+ uiHandle,
73439
+ });
73440
+ return;
73441
+ }
72976
73442
  }
72977
73443
  }
72978
73444
  finally {
@@ -73002,6 +73468,13 @@ function validateRunCodexPromptOptions(options) {
73002
73468
  Auto-pull keeps the repository up to date between prompt rounds, which requires each successful round to end with a clean committed working tree.
73003
73469
  `));
73004
73470
  }
73471
+ if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit <= 0)) {
73472
+ throw new NotAllowed(spaceTrim$1(`
73473
+ Flag \`--limit\` expects a positive integer.
73474
+
73475
+ Received: \`${options.limit}\`
73476
+ `));
73477
+ }
73005
73478
  }
73006
73479
  /**
73007
73480
  * Pulls the latest repository state before loading prompts when the feature is enabled.
@@ -73148,6 +73621,21 @@ function finishWhenNoPromptIsAvailable(promptQueueSnapshot, isRichUiEnabled, uiH
73148
73621
  }
73149
73622
  return true;
73150
73623
  }
73624
+ /**
73625
+ * Checks whether the configured successful prompt-run limit has been reached.
73626
+ */
73627
+ function isRunLimitReached(options) {
73628
+ const { completedRunCount, limit } = options;
73629
+ return limit !== undefined && completedRunCount >= limit;
73630
+ }
73631
+ /**
73632
+ * Updates UI and console output when a user-configured run limit stops the loop.
73633
+ */
73634
+ function finishWhenRunLimitIsReached(options) {
73635
+ const { completedRunCount, isRichUiEnabled, uiHandle } = options;
73636
+ const runCountLabel = completedRunCount === 1 ? '1 prompt run' : `${completedRunCount} prompt runs`;
73637
+ announceRunCompletion(`Run limit reached after ${runCountLabel}.`, colors.green, isRichUiEnabled, uiHandle);
73638
+ }
73151
73639
  /**
73152
73640
  * Updates the UI status message while waiting for new prompts in keepAlive server mode.
73153
73641
  */