@promptbook/cli 0.114.0-11 → 0.114.0-13

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 (145) hide show
  1. package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +8 -1
  2. package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +193 -63
  3. package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +25 -28
  4. package/apps/agents-server/src/app/admin/messages/send-email/emailTesting.ts +153 -0
  5. package/apps/agents-server/src/app/admin/messages/send-email/emailTestingAccess.ts +30 -0
  6. package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +18 -5
  7. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +7 -4
  8. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentGoalChatPlannedMessages.tsx +23 -8
  9. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +14 -3
  10. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +2 -20
  11. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +2 -1
  12. package/apps/agents-server/src/app/agents/[agentName]/projectReferenceActions.ts +61 -0
  13. package/apps/agents-server/src/app/api/internal/agent-goal-chat-planned-messages/route.ts +34 -7
  14. package/apps/agents-server/src/components/AgentProjects/resolveLatestCompletedAgentMessageKey.ts +32 -0
  15. package/apps/agents-server/src/components/AgentProjects/useAgentProjectMarkdownReferences.ts +124 -2
  16. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +3 -0
  17. package/apps/agents-server/src/languages/translations/czech.yaml +3 -0
  18. package/apps/agents-server/src/languages/translations/english.yaml +3 -0
  19. package/apps/agents-server/src/message-providers/email/stalwart/StalwartMessageProvider.ts +2 -1
  20. package/apps/agents-server/src/tools/agentGoalChatTimeoutToolFunctions.ts +24 -2
  21. package/apps/agents-server/src/tools/agentGoalChatTimeoutTools.ts +5 -4
  22. package/apps/agents-server/src/utils/agentGoalChat/agentGoalChatPlannedMessageActions.ts +38 -9
  23. package/apps/agents-server/src/utils/agentGoalChat/createAgentGoalChatNoteContent.ts +15 -6
  24. package/apps/agents-server/src/utils/agentProjects/listAgentProjectNames.ts +35 -0
  25. package/apps/agents-server/src/utils/agentProjects/listAgentProjects.ts +6 -23
  26. package/apps/agents-server/src/utils/agentProjects/resolveAgentChatProjectReferences.ts +25 -0
  27. package/apps/agents-server/src/utils/chatMessageChips/createAnsweredMessageChipToolCalls.ts +37 -0
  28. package/apps/agents-server/src/utils/chatMessageChips/createPlannedMessageChipToolCalls.ts +39 -0
  29. package/apps/agents-server/src/utils/chatMessageChips/createTouchedProjectChipToolCalls.ts +75 -0
  30. package/apps/agents-server/src/utils/email/agentEmailAddress.ts +14 -0
  31. package/apps/agents-server/src/utils/email/sendEmailThroughStalwart.ts +2 -1
  32. package/apps/agents-server/src/utils/localChatRunner/applyLocalAgentPlannedMessageCommands.ts +212 -0
  33. package/apps/agents-server/src/utils/localChatRunner/prepareLocalAgentPlannedMessagesSidecar.ts +66 -0
  34. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +110 -22
  35. package/apps/agents-server/src/utils/userChat/persistUserChatJobTerminalState.ts +5 -0
  36. package/apps/agents-server/src/utils/userChat/userChatJobRunReport.ts +30 -0
  37. package/apps/agents-server/src/utils/userChatTimeout/createTimeoutWakeUpMessage.ts +9 -1
  38. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore/repeatFiredUserChatTimeout.ts +74 -0
  39. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore.ts +1 -0
  40. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutWorker.ts +63 -54
  41. package/esm/index.es.js +1042 -194
  42. package/esm/index.es.js.map +1 -1
  43. package/esm/scripts/run-agent-messages/messages/buildAgentGoalChatPromptSection.d.ts +22 -4
  44. package/esm/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +3 -3
  45. package/esm/scripts/run-agent-messages/messages/loadAgentPlannedMessagesSidecar.d.ts +10 -0
  46. package/esm/scripts/run-agent-messages/messages/resolveTouchedAgentProjects.d.ts +15 -0
  47. package/esm/scripts/run-codex-prompts/common/runGoScript/$spawnLoggedBashScript.d.ts +25 -0
  48. package/esm/scripts/run-codex-prompts/common/runGoScript/$terminateLoggedBashProcessTree.d.ts +11 -0
  49. package/esm/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +4 -0
  50. package/esm/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +5 -0
  51. package/esm/src/book-2.0/agent-source/ParsedUnknownCommitment.d.ts +26 -0
  52. package/esm/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.unknown.test.d.ts +1 -0
  53. package/esm/src/book-2.0/agent-source/getBookCommitmentLineType.d.ts +19 -0
  54. package/esm/src/book-2.0/agent-source/parseAgentSourceWithCommitments.unknown.test.d.ts +1 -0
  55. package/esm/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  56. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  57. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  58. package/esm/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  59. package/esm/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.d.ts +28 -0
  60. package/esm/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.test.d.ts +1 -0
  61. package/esm/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  62. package/esm/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  63. package/esm/src/cli/cli-commands/common/harness/$checkHarnessInstallation.d.ts +3 -3
  64. package/esm/src/cli/cli-commands/common/harness/$checkHarnessInstallation.test.d.ts +1 -0
  65. package/esm/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -3
  66. package/esm/src/cli/cli-commands/common/harness/HarnessInstallationStatus.d.ts +3 -2
  67. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +29 -0
  68. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts +1 -0
  69. package/esm/src/executables/platforms/locateAppOnWindows.d.ts +1 -1
  70. package/esm/src/executables/platforms/locateAppOnWindows.test.d.ts +1 -0
  71. package/esm/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  72. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  73. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  74. package/esm/src/version.d.ts +1 -1
  75. package/package.json +1 -1
  76. package/src/book-2.0/agent-source/AgentSourceParseResult.ts +6 -0
  77. package/src/book-2.0/agent-source/ParsedUnknownCommitment.ts +29 -0
  78. package/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments/augmentAgentModelRequirementsFromSource.ts +38 -1
  79. package/src/book-2.0/agent-source/getBookCommitmentLineType.ts +36 -0
  80. package/src/book-2.0/agent-source/parseAgentSourceWithCommitments.ts +128 -61
  81. package/src/book-3.0/AgentMessageRunReport.ts +76 -0
  82. package/src/book-3.0/AgentPlannedMessagesSidecar.ts +229 -0
  83. package/src/book-3.0/AgentTeamConversationWorkspace.ts +3 -5
  84. package/src/book-3.0/createAgentMessageSidecarBaseName.ts +20 -0
  85. package/src/book-components/BookEditor/BookEditorMonaco.tsx +6 -1
  86. package/src/book-components/BookEditor/BookEditorMonacoTokenization.ts +3 -1
  87. package/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.ts +50 -0
  88. package/src/book-components/Chat/Chat/Chat.module.css +6 -1
  89. package/src/book-components/Chat/Chat/renderToolCallDetails.tsx +1 -12
  90. package/src/book-components/Chat/utils/agentProjectToolCall.ts +74 -0
  91. package/src/book-components/Chat/utils/getToolCallChipletInfo.ts +28 -0
  92. package/src/book-components/Chat/utils/timeoutToolCallPresentation.ts +23 -3
  93. package/src/cli/cli-commands/coder/boilerplateTemplates.ts +3 -15
  94. package/src/cli/cli-commands/coder/init.ts +11 -2
  95. package/src/cli/cli-commands/coder/ping.ts +11 -1
  96. package/src/cli/cli-commands/coder/run.ts +11 -2
  97. package/src/cli/cli-commands/coder/server.ts +11 -2
  98. package/src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.ts +5 -0
  99. package/src/cli/cli-commands/common/harness/$checkHarnessInstallation.ts +17 -5
  100. package/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.ts +6 -3
  101. package/src/cli/cli-commands/common/harness/HarnessInstallationStatus.ts +3 -2
  102. package/src/cli/cli-commands/common/harnessUpdateCliOptions.ts +49 -0
  103. package/src/executables/platforms/locateAppOnWindows.ts +12 -9
  104. package/src/other/templates/getTemplatesPipelineCollection.ts +740 -860
  105. package/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.ts +105 -0
  106. package/src/utils/agent-message-runtime/resolveAgentMessageRuntimeActivity.ts +6 -54
  107. package/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.ts +147 -0
  108. package/src/version.ts +2 -2
  109. package/src/versions.txt +2 -1
  110. package/umd/index.umd.js +1041 -193
  111. package/umd/index.umd.js.map +1 -1
  112. package/umd/scripts/run-agent-messages/messages/buildAgentGoalChatPromptSection.d.ts +22 -4
  113. package/umd/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +3 -3
  114. package/umd/scripts/run-agent-messages/messages/loadAgentPlannedMessagesSidecar.d.ts +10 -0
  115. package/umd/scripts/run-agent-messages/messages/resolveTouchedAgentProjects.d.ts +15 -0
  116. package/umd/scripts/run-codex-prompts/common/runGoScript/$spawnLoggedBashScript.d.ts +25 -0
  117. package/umd/scripts/run-codex-prompts/common/runGoScript/$terminateLoggedBashProcessTree.d.ts +11 -0
  118. package/umd/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +4 -0
  119. package/umd/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +5 -0
  120. package/umd/src/book-2.0/agent-source/ParsedUnknownCommitment.d.ts +26 -0
  121. package/umd/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.unknown.test.d.ts +1 -0
  122. package/umd/src/book-2.0/agent-source/getBookCommitmentLineType.d.ts +19 -0
  123. package/umd/src/book-2.0/agent-source/parseAgentSourceWithCommitments.unknown.test.d.ts +1 -0
  124. package/umd/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  125. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  126. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  127. package/umd/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  128. package/umd/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.d.ts +28 -0
  129. package/umd/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.test.d.ts +1 -0
  130. package/umd/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  131. package/umd/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  132. package/umd/src/cli/cli-commands/common/harness/$checkHarnessInstallation.d.ts +3 -3
  133. package/umd/src/cli/cli-commands/common/harness/$checkHarnessInstallation.test.d.ts +1 -0
  134. package/umd/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -3
  135. package/umd/src/cli/cli-commands/common/harness/HarnessInstallationStatus.d.ts +3 -2
  136. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +29 -0
  137. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts +1 -0
  138. package/umd/src/executables/platforms/locateAppOnWindows.d.ts +1 -1
  139. package/umd/src/executables/platforms/locateAppOnWindows.test.d.ts +1 -0
  140. package/umd/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  141. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  142. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  143. package/umd/src/version.d.ts +1 -1
  144. package/esm/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
  145. package/umd/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
package/esm/index.es.js CHANGED
@@ -4,7 +4,7 @@ import _spaceTrim, { spaceTrim as spaceTrim$1 } from 'spacetrim';
4
4
  import { writeFile, stat, mkdir, readFile, readdir, rm, cp, lstat, symlink, rename, unlink, appendFile, realpath, copyFile, access, constants, watch, rmdir } from 'fs/promises';
5
5
  import { join, delimiter, relative, basename, resolve, dirname, isAbsolute, extname } from 'path';
6
6
  import { createHash, randomBytes } from 'crypto';
7
- import { spawn } from 'child_process';
7
+ import { spawn, spawnSync } from 'child_process';
8
8
  import moment from 'moment';
9
9
  import * as fs from 'fs';
10
10
  import { createWriteStream, mkdirSync, writeFileSync, statSync, readFileSync, existsSync, promises } from 'fs';
@@ -48,7 +48,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
48
48
  * @generated
49
49
  * @see https://github.com/webgptorg/promptbook
50
50
  */
51
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-11';
51
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-13';
52
52
  /**
53
53
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
54
54
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -4088,10 +4088,6 @@ const PTBK_AGENTS_SERVER_URL_ENV = 'PTBK_AGENTS_SERVER_URL';
4088
4088
  * Environment variable containing the internal worker token for local agent runners.
4089
4089
  */
4090
4090
  const PTBK_AGENTS_SERVER_USER_CHAT_WORKER_TOKEN_ENV = 'PTBK_AGENTS_SERVER_USER_CHAT_WORKER_TOKEN';
4091
- /**
4092
- * Header used by internal local-runner routes.
4093
- */
4094
- const USER_CHAT_WORKER_TOKEN_HEADER = 'x-user-chat-worker-token';
4095
4091
 
4096
4092
  /**
4097
4093
  * Format either small or big number
@@ -20307,6 +20303,30 @@ const COMMITMENT_REGISTRY = [
20307
20303
  // TODO: [🧠] Maybe create through standardized $register
20308
20304
  // Note: [💞] Ignore a discrepancy between file name and entity name
20309
20305
 
20306
+ /**
20307
+ * Pattern shared by Book parsing and editor highlighting for a commitment-like line.
20308
+ *
20309
+ * A Book commitment keyword consists of one or more all-uppercase words with at least
20310
+ * two characters each. This deliberately mirrors the Book editor syntax rule so an
20311
+ * unknown keyword starts a new source block everywhere it is interpreted.
20312
+ *
20313
+ * @private internal constant of `getBookCommitmentLineType`
20314
+ */
20315
+ const BOOK_COMMITMENT_LINE_TYPE_REGEX = /^\s*(?<type>[A-Z][A-Z0-9]+(?:\s+[A-Z][A-Z0-9]+)*)(?=\s|$)/;
20316
+ /**
20317
+ * Extracts the uppercase commitment type from a Book line, including types not registered by Promptbook.
20318
+ *
20319
+ * @param line - One raw Book source line.
20320
+ * @returns Matched commitment type or `null` when the line does not begin with commitment syntax.
20321
+ *
20322
+ * @private internal utility of Book parsing and `<BookEditor/>`
20323
+ */
20324
+ function getBookCommitmentLineType(line) {
20325
+ var _a;
20326
+ const match = BOOK_COMMITMENT_LINE_TYPE_REGEX.exec(line);
20327
+ return ((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.type) || null;
20328
+ }
20329
+
20310
20330
  /**
20311
20331
  * Consumes the agent-name prelude from raw source.
20312
20332
  *
@@ -20364,13 +20384,15 @@ function parseAgentSourceWithCommitments(agentSource) {
20364
20384
  return {
20365
20385
  agentName: null,
20366
20386
  commitments: [],
20387
+ unknownCommitments: [],
20367
20388
  nonCommitmentLines: [],
20368
20389
  };
20369
20390
  }
20370
20391
  const { lines, agentName, agentNameLineIndex, agentNameLineNumber } = parseAgentSourcePrelude(agentSource);
20371
20392
  const commitments = [];
20393
+ const unknownCommitments = [];
20372
20394
  const nonCommitmentLines = agentNameLineIndex >= 0 ? [lines[agentNameLineIndex]] : [];
20373
- // Parse commitments with multiline support
20395
+ // Parse commitments with multiline support.
20374
20396
  let currentCommitment = null;
20375
20397
  // Process lines starting from after the agent name line
20376
20398
  const startIndex = agentNameLineIndex >= 0 ? agentNameLineIndex + 1 : 0;
@@ -20384,25 +20406,11 @@ function parseAgentSourceWithCommitments(agentSource) {
20384
20406
  // Check if this line starts or ends a code block
20385
20407
  if (trimmedLine.startsWith('```')) {
20386
20408
  isInsideCodeBlock = !isInsideCodeBlock;
20387
- if (currentCommitment) {
20388
- // If we are inside a commitment, the code block is part of it
20389
- currentCommitment.contentLines.push(line);
20390
- }
20391
- else {
20392
- // If we are not inside a commitment, the code block is non-commitment
20393
- nonCommitmentLines.push(line);
20394
- }
20409
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
20395
20410
  continue;
20396
20411
  }
20397
20412
  if (isInsideCodeBlock) {
20398
- if (currentCommitment) {
20399
- // If we are inside a commitment and a code block, the line is part of the commitment
20400
- currentCommitment.contentLines.push(line);
20401
- }
20402
- else {
20403
- // If we are inside a code block but not a commitment, the line is non-commitment
20404
- nonCommitmentLines.push(line);
20405
- }
20413
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
20406
20414
  continue;
20407
20415
  }
20408
20416
  // Check if this line starts a new commitment
@@ -20411,22 +20419,14 @@ function parseAgentSourceWithCommitments(agentSource) {
20411
20419
  const typeRegex = definition.createTypeRegex();
20412
20420
  const match = typeRegex.exec(line.trim());
20413
20421
  if (match && ((_a = match.groups) === null || _a === void 0 ? void 0 : _a.type)) {
20414
- // Save the previous commitment if it exists
20415
- if (currentCommitment) {
20416
- const fullContent = currentCommitment.contentLines.join('\n');
20417
- commitments.push({
20418
- type: currentCommitment.type,
20419
- content: spaceTrim$1(fullContent),
20420
- originalLine: currentCommitment.originalStartLine,
20421
- lineNumber: currentCommitment.startLineNumber,
20422
- });
20423
- }
20422
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
20424
20423
  // Extract the initial content from the commitment line
20425
20424
  const fullRegex = definition.createRegex();
20426
20425
  const fullMatch = fullRegex.exec(line.trim());
20427
20426
  const initialContent = ((_b = fullMatch === null || fullMatch === void 0 ? void 0 : fullMatch.groups) === null || _b === void 0 ? void 0 : _b.contents) || '';
20428
20427
  // Start a new commitment
20429
20428
  currentCommitment = {
20429
+ kind: 'known',
20430
20430
  type: definition.type,
20431
20431
  startLineNumber: i + 1,
20432
20432
  originalStartLine: line,
@@ -20436,52 +20436,90 @@ function parseAgentSourceWithCommitments(agentSource) {
20436
20436
  break;
20437
20437
  }
20438
20438
  }
20439
+ if (foundNewCommitment) {
20440
+ continue;
20441
+ }
20442
+ const unknownCommitmentType = getBookCommitmentLineType(line);
20443
+ if (unknownCommitmentType) {
20444
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
20445
+ currentCommitment = {
20446
+ kind: 'unknown',
20447
+ type: unknownCommitmentType,
20448
+ startLineNumber: i + 1,
20449
+ originalStartLine: line,
20450
+ sourceLines: [line],
20451
+ };
20452
+ continue;
20453
+ }
20439
20454
  // Check if this is a horizontal line (ends any current commitment)
20440
20455
  const isHorizontalLine = HORIZONTAL_LINE_PATTERN$1.test(line);
20441
20456
  if (isHorizontalLine) {
20442
- // Save the current commitment if it exists
20443
- if (currentCommitment) {
20444
- const fullContent = currentCommitment.contentLines.join('\n');
20445
- commitments.push({
20446
- type: currentCommitment.type,
20447
- content: spaceTrim$1(fullContent),
20448
- originalLine: currentCommitment.originalStartLine,
20449
- lineNumber: currentCommitment.startLineNumber,
20450
- });
20451
- currentCommitment = null;
20452
- }
20457
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
20458
+ currentCommitment = null;
20453
20459
  // Add horizontal line to non-commitment lines
20454
20460
  nonCommitmentLines.push(line);
20455
20461
  continue;
20456
20462
  }
20457
- if (!foundNewCommitment) {
20458
- if (currentCommitment) {
20459
- // This line belongs to the current commitment
20460
- currentCommitment.contentLines.push(line);
20461
- }
20462
- else {
20463
- // This line is not part of any commitment
20464
- nonCommitmentLines.push(line);
20465
- }
20466
- }
20467
- }
20468
- // Don't forget to save the last commitment if it exists
20469
- if (currentCommitment) {
20470
- const fullContent = currentCommitment.contentLines.join('\n');
20471
- commitments.push({
20472
- type: currentCommitment.type,
20473
- content: spaceTrim$1(fullContent),
20474
- originalLine: currentCommitment.originalStartLine,
20475
- lineNumber: currentCommitment.startLineNumber,
20476
- });
20463
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
20477
20464
  }
20465
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
20478
20466
  return {
20479
20467
  agentName,
20480
20468
  agentNameLineNumber,
20481
20469
  commitments,
20470
+ unknownCommitments,
20482
20471
  nonCommitmentLines,
20483
20472
  };
20484
20473
  }
20474
+ /**
20475
+ * Stores the current known or unknown commitment in its appropriate parsed collection.
20476
+ *
20477
+ * @param currentCommitment - Commitment-like block collected so far.
20478
+ * @param commitments - Parsed registered commitments to append to.
20479
+ * @param unknownCommitments - Parsed unregistered commitments to append to.
20480
+ *
20481
+ * @private internal utility of `parseAgentSourceWithCommitments`
20482
+ */
20483
+ function appendCurrentCommitment(currentCommitment, commitments, unknownCommitments) {
20484
+ if (!currentCommitment) {
20485
+ return;
20486
+ }
20487
+ if (currentCommitment.kind === 'unknown') {
20488
+ unknownCommitments.push({
20489
+ type: currentCommitment.type,
20490
+ originalLine: currentCommitment.originalStartLine,
20491
+ lineNumber: currentCommitment.startLineNumber,
20492
+ source: spaceTrim$1(currentCommitment.sourceLines.join('\n')),
20493
+ });
20494
+ return;
20495
+ }
20496
+ commitments.push({
20497
+ type: currentCommitment.type,
20498
+ content: spaceTrim$1(currentCommitment.contentLines.join('\n')),
20499
+ originalLine: currentCommitment.originalStartLine,
20500
+ lineNumber: currentCommitment.startLineNumber,
20501
+ });
20502
+ }
20503
+ /**
20504
+ * Adds one source line to the active commitment-like block, or to plain source content when none is active.
20505
+ *
20506
+ * @param currentCommitment - Commitment-like block currently being collected.
20507
+ * @param line - Source line to append.
20508
+ * @param nonCommitmentLines - Plain source lines to append to when no block is active.
20509
+ *
20510
+ * @private internal utility of `parseAgentSourceWithCommitments`
20511
+ */
20512
+ function appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines) {
20513
+ if (!currentCommitment) {
20514
+ nonCommitmentLines.push(line);
20515
+ return;
20516
+ }
20517
+ if (currentCommitment.kind === 'unknown') {
20518
+ currentCommitment.sourceLines.push(line);
20519
+ return;
20520
+ }
20521
+ currentCommitment.contentLines.push(line);
20522
+ }
20485
20523
 
20486
20524
  /**
20487
20525
  * Parses parameters from text using both supported notations:
@@ -21840,7 +21878,7 @@ async function isAgentRunnerProject(projectPath) {
21840
21878
  return agentBookStats.isFile();
21841
21879
  }
21842
21880
  catch (error) {
21843
- if (isFileNotFoundError$6(error)) {
21881
+ if (isFileNotFoundError$7(error)) {
21844
21882
  return false;
21845
21883
  }
21846
21884
  throw error;
@@ -21849,7 +21887,7 @@ async function isAgentRunnerProject(projectPath) {
21849
21887
  /**
21850
21888
  * Returns true when one filesystem error indicates a missing path.
21851
21889
  */
21852
- function isFileNotFoundError$6(error) {
21890
+ function isFileNotFoundError$7(error) {
21853
21891
  return Boolean(error &&
21854
21892
  typeof error === 'object' &&
21855
21893
  'code' in error &&
@@ -21879,7 +21917,7 @@ async function readQueuedMessageDirectoryEntries(queuedMessagesDirectoryPath) {
21879
21917
  return await readdir(queuedMessagesDirectoryPath, { withFileTypes: true });
21880
21918
  }
21881
21919
  catch (error) {
21882
- if (isFileNotFoundError$5(error)) {
21920
+ if (isFileNotFoundError$6(error)) {
21883
21921
  return [];
21884
21922
  }
21885
21923
  throw error;
@@ -21900,7 +21938,7 @@ function normalizeRelativePath$2(relativePath) {
21900
21938
  /**
21901
21939
  * Returns true when an error is a missing-path filesystem error.
21902
21940
  */
21903
- function isFileNotFoundError$5(error) {
21941
+ function isFileNotFoundError$6(error) {
21904
21942
  return Boolean(error &&
21905
21943
  typeof error === 'object' &&
21906
21944
  'code' in error &&
@@ -22378,7 +22416,7 @@ async function runGitCommand(options) {
22378
22416
  lastIndexLockState = await readGitIndexLockState(cwd, options.env);
22379
22417
  if ((lastIndexLockState === null || lastIndexLockState === void 0 ? void 0 : lastIndexLockState.isStale) && !isStaleIndexLockRemoved) {
22380
22418
  await unlink(lastIndexLockState.path).catch((unlinkError) => {
22381
- if (isFileNotFoundError$4(unlinkError)) {
22419
+ if (isFileNotFoundError$5(unlinkError)) {
22382
22420
  return;
22383
22421
  }
22384
22422
  throw unlinkError;
@@ -22439,7 +22477,7 @@ async function readGitIndexLockState(cwd, env) {
22439
22477
  };
22440
22478
  }
22441
22479
  catch (error) {
22442
- if (isFileNotFoundError$4(error)) {
22480
+ if (isFileNotFoundError$5(error)) {
22443
22481
  return undefined;
22444
22482
  }
22445
22483
  throw error;
@@ -22496,7 +22534,7 @@ function formatDelay$1(delayMs) {
22496
22534
  /**
22497
22535
  * Detects missing-file errors while inspecting the lock file.
22498
22536
  */
22499
- function isFileNotFoundError$4(error) {
22537
+ function isFileNotFoundError$5(error) {
22500
22538
  return Boolean(error &&
22501
22539
  typeof error === 'object' &&
22502
22540
  'code' in error &&
@@ -24751,14 +24789,90 @@ const PTBK_CODER_LOG_FILE_ENV_NAME = 'PTBK_CODER_LOG_FILE';
24751
24789
  * without the generated script and the prompt it embeds.
24752
24790
  */
24753
24791
  const SCRIPT_EXECUTION_LOG_RAW_OUTPUT_MARKER = '--- raw output ---';
24792
+ /**
24793
+ * Command which terminates the Bash process tree rooted at the running harness.
24794
+ *
24795
+ * Bash's POSIX process IDs differ from Windows process IDs in Git Bash, so the Windows branch resolves the native PID
24796
+ * before delegating to `taskkill`. Unix harnesses run in a dedicated Bash job process group, which can be terminated
24797
+ * with its negative process ID.
24798
+ */
24799
+ const TERMINATE_BASH_PROCESS_TREE_COMMAND = process.platform === 'win32'
24800
+ ? spaceTrim$1(`
24801
+ HARNESS_WINDOWS_PROCESS_ID="$(ps -l -p "$HARNESS_PROCESS_ID" | awk 'NR == 2 { print $4 }')"
24802
+ if [ -n "$HARNESS_WINDOWS_PROCESS_ID" ]; then
24803
+ MSYS_NO_PATHCONV=1 taskkill.exe /PID "$HARNESS_WINDOWS_PROCESS_ID" /T /F > /dev/null 2>&1 || true
24804
+ fi
24805
+ `)
24806
+ : 'kill -TERM -- "-$HARNESS_PROCESS_ID" 2>/dev/null || true';
24807
+ /**
24808
+ * Shell condition that detects whether the Node process which owns the harness is still running.
24809
+ *
24810
+ * Git Bash translates process IDs and command switches, so querying the native Windows PID needs both `tasklist` and
24811
+ * disabled MSYS path conversion there. Unix can use the native `kill -0` process existence check.
24812
+ */
24813
+ const IS_PARENT_PROCESS_RUNNING_CONDITION = process.platform === 'win32'
24814
+ ? 'MSYS_NO_PATHCONV=1 tasklist.exe /FI "PID eq $PARENT_CODER_PROCESS_ID" /NH | awk -v processId="$PARENT_CODER_PROCESS_ID" \'$2 == processId { isFound = 1 } END { exit isFound ? 0 : 1 }\''
24815
+ : 'kill -0 "$PARENT_CODER_PROCESS_ID" 2>/dev/null';
24816
+ /**
24817
+ * Environment variable that identifies the Node process responsible for a temporary harness shell.
24818
+ */
24819
+ const PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME = 'PTBK_CODER_PARENT_PROCESS_ID';
24754
24820
  /**
24755
24821
  * Small bash wrapper that preserves stdout/stderr streams while teeing both into the runtime log file.
24822
+ *
24823
+ * A watcher polls the owning Node process by its PID. If that process exits abruptly, the watcher stops the whole
24824
+ * Bash and harness process tree instead of letting it continue as an orphan.
24756
24825
  */
24757
24826
  const LOGGED_BASH_WRAPPER_COMMAND = spaceTrim$1(`
24827
+ PARENT_CODER_PROCESS_ID="\${${PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME}}"
24828
+ unset ${PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME}
24829
+
24830
+ terminate_harness_process_tree() {
24831
+ # Keep the EXIT cleanup intact so the wrapper can also stop its parent-process watcher.
24832
+ trap - HUP INT TERM
24833
+ ${TERMINATE_BASH_PROCESS_TREE_COMMAND}
24834
+ }
24835
+
24836
+ is_parent_process_running() {
24837
+ ${IS_PARENT_PROCESS_RUNNING_CONDITION}
24838
+ }
24839
+
24758
24840
  if [ -n "\${${PTBK_CODER_LOG_FILE_ENV_NAME}:-}" ]; then
24759
- exec > >(tee -a "\$${PTBK_CODER_LOG_FILE_ENV_NAME}") 2> >(tee -a "\$${PTBK_CODER_LOG_FILE_ENV_NAME}" >&2)
24841
+ exec > >(tee -a "$${PTBK_CODER_LOG_FILE_ENV_NAME}") 2> >(tee -a "$${PTBK_CODER_LOG_FILE_ENV_NAME}" >&2)
24842
+ fi
24843
+
24844
+ watch_parent_process() {
24845
+ trap 'exit 0' HUP INT TERM
24846
+
24847
+ while is_parent_process_running; do
24848
+ sleep 1
24849
+ done
24850
+
24851
+ terminate_harness_process_tree
24852
+ }
24853
+
24854
+ # Background harness jobs need their own process group on Unix so termination cannot reach the parent coder.
24855
+ set -m
24856
+ if ! is_parent_process_running; then
24857
+ exit 1
24760
24858
  fi
24761
- bash "$1"
24859
+ bash "$1" &
24860
+ HARNESS_PROCESS_ID=$!
24861
+
24862
+ cleanup_parent_process_watcher() {
24863
+ kill "$PARENT_PROCESS_WATCHER_PID" 2>/dev/null || true
24864
+ wait "$PARENT_PROCESS_WATCHER_PID" 2>/dev/null || true
24865
+ }
24866
+
24867
+ watch_parent_process &
24868
+ PARENT_PROCESS_WATCHER_PID=$!
24869
+
24870
+ trap cleanup_parent_process_watcher EXIT
24871
+ trap terminate_harness_process_tree HUP INT TERM
24872
+
24873
+ wait "$HARNESS_PROCESS_ID"
24874
+ SCRIPT_EXIT_CODE=$?
24875
+ exit "$SCRIPT_EXIT_CODE"
24762
24876
  `);
24763
24877
  /**
24764
24878
  * Shapes one bash invocation that optionally mirrors live script output into a temporary log file.
@@ -24814,6 +24928,38 @@ function describeTempScriptKind(scriptPath) {
24814
24928
  return scriptPath.toLowerCase().endsWith('.test.sh') ? 'test shell' : 'runner shell';
24815
24929
  }
24816
24930
 
24931
+ /**
24932
+ * Standard streams used by every temporary Bash runner.
24933
+ */
24934
+ const BASH_PROCESS_STDIO = ['pipe', 'pipe', 'pipe'];
24935
+ /**
24936
+ * Whether the current Node process is running on Windows.
24937
+ */
24938
+ const IS_WINDOWS$1 = process.platform === 'win32';
24939
+ /**
24940
+ * Starts one temporary Bash script in a process tree owned by the current Node process.
24941
+ *
24942
+ * The wrapper watches the supplied owning process ID. When that process exits abruptly, the wrapper terminates every
24943
+ * nested shell and harness process instead of leaving it orphaned.
24944
+ *
24945
+ * @private internal utility of the coding prompt runner
24946
+ */
24947
+ function $spawnLoggedBashScript(options) {
24948
+ var _a;
24949
+ const bashExecution = buildLoggedBashExecution(options.scriptPath, options.logPath);
24950
+ const parentProcessId = (_a = options.parentProcessId) !== null && _a !== void 0 ? _a : process.pid;
24951
+ return spawn('bash', bashExecution.args, {
24952
+ detached: !IS_WINDOWS$1,
24953
+ env: {
24954
+ ...process.env,
24955
+ ...bashExecution.env,
24956
+ [PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME]: parentProcessId.toString(),
24957
+ },
24958
+ stdio: BASH_PROCESS_STDIO,
24959
+ windowsHide: true,
24960
+ });
24961
+ }
24962
+
24817
24963
  /**
24818
24964
  * Prints one live shell output chunk to the terminal when console mirroring is enabled.
24819
24965
  */
@@ -24836,12 +24982,12 @@ function printLiveScriptChunk(chunk, source, shouldPrintLiveOutput) {
24836
24982
  async function runBashScriptWithOutput(options) {
24837
24983
  var _a;
24838
24984
  await appendScriptExecutionLogStart(options);
24839
- const bashExecution = buildLoggedBashExecution(options.scriptPath, options.logPath);
24840
24985
  const scriptPathPosix = toPosixPath(options.scriptPath);
24841
24986
  const shouldPrintLiveOutput = (_a = options.shouldPrintLiveOutput) !== null && _a !== void 0 ? _a : true;
24842
24987
  return await new Promise((resolve, reject) => {
24843
- const commandProcess = spawn('bash', bashExecution.args, {
24844
- env: bashExecution.env ? { ...process.env, ...bashExecution.env } : process.env,
24988
+ const commandProcess = $spawnLoggedBashScript({
24989
+ scriptPath: options.scriptPath,
24990
+ logPath: options.logPath,
24845
24991
  });
24846
24992
  let output = '';
24847
24993
  let settled = false;
@@ -24900,8 +25046,9 @@ async function runBashScriptWithOutput(options) {
24900
25046
  const failure = new Error(spaceTrim$1(output) || `Command "bash ${scriptPathPosix}" exited with code ${code}`);
24901
25047
  settleWithLog(`failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}`, () => reject(failure), failure);
24902
25048
  };
25049
+ // Wait for `close`, not only `exit`, because the Bash wrapper can still be flushing its tee process
25050
+ // substitutions after the direct shell exits.
24903
25051
  commandProcess.on('close', handleExit);
24904
- commandProcess.on('exit', handleExit);
24905
25052
  commandProcess.on('disconnect', () => {
24906
25053
  const failure = new Error(`Command "bash ${scriptPathPosix}" disconnected`);
24907
25054
  settleWithLog('failed after disconnect', () => reject(failure), failure);
@@ -25762,6 +25909,35 @@ function isGitHubCopilotArgumentLimitError(error) {
25762
25909
  error.message.includes('bin/node'));
25763
25910
  }
25764
25911
 
25912
+ /**
25913
+ * Whether the current coder process runs on Windows.
25914
+ */
25915
+ const IS_WINDOWS = process.platform === 'win32';
25916
+ /**
25917
+ * Stops one active temporary Bash shell together with the harness process tree it owns.
25918
+ *
25919
+ * Unix sends `SIGTERM` to the wrapper so its shell trap terminates the separately grouped harness job. Windows does
25920
+ * not reliably deliver that signal to Bash, so the native `taskkill` command terminates the direct shell tree instead.
25921
+ *
25922
+ * @private internal utility of the coding prompt runner
25923
+ */
25924
+ function $terminateLoggedBashProcessTree(commandProcess) {
25925
+ if (commandProcess.exitCode !== null || commandProcess.signalCode !== null) {
25926
+ return;
25927
+ }
25928
+ if (!IS_WINDOWS) {
25929
+ commandProcess.kill('SIGTERM');
25930
+ return;
25931
+ }
25932
+ if (!commandProcess.pid) {
25933
+ return;
25934
+ }
25935
+ spawnSync('taskkill.exe', ['/PID', commandProcess.pid.toString(), '/T', '/F'], {
25936
+ stdio: 'ignore',
25937
+ windowsHide: true,
25938
+ });
25939
+ }
25940
+
25765
25941
  /**
25766
25942
  * Maximum number of output characters included in thrown error messages.
25767
25943
  */
@@ -25804,15 +25980,16 @@ async function runScriptUntilMarkerIdle(options) {
25804
25980
  const scriptPathPosix = toPosixPath(scriptPath);
25805
25981
  const shouldPrintLiveOutput = (_a = options.shouldPrintLiveOutput) !== null && _a !== void 0 ? _a : true;
25806
25982
  await appendScriptExecutionLogStart(options);
25807
- const bashExecution = buildLoggedBashExecution(scriptPath, options.logPath);
25808
25983
  return await new Promise((resolve, reject) => {
25809
- const commandProcess = spawn('bash', bashExecution.args, {
25810
- env: bashExecution.env ? { ...process.env, ...bashExecution.env } : process.env,
25984
+ const commandProcess = $spawnLoggedBashScript({
25985
+ scriptPath,
25986
+ logPath: options.logPath,
25811
25987
  });
25812
25988
  let stdoutBuffer = '';
25813
25989
  let stderrBuffer = '';
25814
25990
  let fullOutput = '';
25815
25991
  let markerSeen = false;
25992
+ let isCompletedAfterIdleTimeout = false;
25816
25993
  let idleTimer;
25817
25994
  let settled = false;
25818
25995
  let isSettling = false;
@@ -25854,15 +26031,16 @@ async function runScriptUntilMarkerIdle(options) {
25854
26031
  });
25855
26032
  };
25856
26033
  /**
25857
- * Finishes a marker-completed run and then asks bash to stop.
26034
+ * Stops a marker-completed process tree after its trailing output becomes idle.
25858
26035
  */
25859
26036
  const finishAfterIdleTimeout = () => {
25860
- settleWithLog('completed after idle timeout', () => resolve(fullOutput));
26037
+ isCompletedAfterIdleTimeout = true;
26038
+ idleTimer = undefined;
25861
26039
  try {
25862
- commandProcess.kill();
26040
+ $terminateLoggedBashProcessTree(commandProcess);
25863
26041
  }
25864
26042
  catch (_a) {
25865
- // Windows can report EPERM when bash exits before the idle timer fires.
26043
+ // The close handler below still settles normally when Bash already exited before the idle timer fired.
25866
26044
  }
25867
26045
  };
25868
26046
  /**
@@ -25921,7 +26099,11 @@ async function runScriptUntilMarkerIdle(options) {
25921
26099
  const failure = code === 0 || markerSeen
25922
26100
  ? undefined
25923
26101
  : new Error(buildCommandFailureMessage(scriptPathPosix, code, fullOutput));
25924
- const status = failure ? `failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}` : 'succeeded';
26102
+ const status = isCompletedAfterIdleTimeout
26103
+ ? 'completed after idle timeout'
26104
+ : failure
26105
+ ? `failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}`
26106
+ : 'succeeded';
25925
26107
  settleWithLog(status, () => {
25926
26108
  if (!failure) {
25927
26109
  resolve(fullOutput);
@@ -25930,8 +26112,9 @@ async function runScriptUntilMarkerIdle(options) {
25930
26112
  reject(failure);
25931
26113
  });
25932
26114
  };
26115
+ // Wait for `close`, not only `exit`, because the Bash wrapper can still be flushing its tee process
26116
+ // substitutions after the direct shell exits.
25933
26117
  commandProcess.on('close', handleExit);
25934
- commandProcess.on('exit', handleExit);
25935
26118
  commandProcess.on('disconnect', () => {
25936
26119
  const failure = new Error(buildCommandFailureMessage(scriptPathPosix, null, fullOutput));
25937
26120
  settleWithLog('failed after disconnect', () => reject(failure), failure);
@@ -29427,7 +29610,7 @@ async function readWorkingTreeFileHash(projectPath, relativePath) {
29427
29610
  return createHash('sha1').update(content).digest('hex');
29428
29611
  }
29429
29612
  catch (error) {
29430
- if (isFileNotFoundError$3(error)) {
29613
+ if (isFileNotFoundError$4(error)) {
29431
29614
  return null;
29432
29615
  }
29433
29616
  throw error;
@@ -29442,7 +29625,7 @@ function normalizeGitFilePath(filePath) {
29442
29625
  /**
29443
29626
  * Returns true when an error is a missing-file filesystem error.
29444
29627
  */
29445
- function isFileNotFoundError$3(error) {
29628
+ function isFileNotFoundError$4(error) {
29446
29629
  return Boolean(error &&
29447
29630
  typeof error === 'object' &&
29448
29631
  'code' in error &&
@@ -30435,7 +30618,7 @@ function buildAgentProjectsPromptSection(options = {}) {
30435
30618
  - You have 100% control over every project folder: create, read, modify, and delete files there, run scripts and commands inside it, and keep any persistent data you need between conversations.
30436
30619
  - When the user asks you to build something (for example a website) or to do longer-term work, create a new project folder \`${AGENT_PROJECTS_DIRECTORY_PATH}/<project-name>/\` (use a short kebab-case name) and do the work inside it. Modify an existing project when the user refers to work you already did.
30437
30620
  - A project can be a git repository — you can run \`git init\` inside a project folder and commit your work there.
30438
- - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory except the queued message file you are answering.
30621
+ - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory beyond the file changes allowed at the top of this prompt.
30439
30622
 
30440
30623
  ${block(buildProjectReferenceInstructions(projectsUrlPath))}
30441
30624
  ${block(buildProjectRuntimeInstructions(options.projectRuntimeApi))}
@@ -30491,55 +30674,166 @@ function buildProjectRuntimeInstructions(projectRuntimeApi) {
30491
30674
  }
30492
30675
 
30493
30676
  /**
30494
- * Title stored on every agent goal chat.
30677
+ * Milliseconds in one second.
30678
+ *
30679
+ * @private internal timeout-chat constant
30680
+ */
30681
+ const SECOND_IN_MILLISECONDS = 1000;
30682
+ /**
30683
+ * Seconds in one minute.
30684
+ *
30685
+ * @private internal timeout-chat constant
30686
+ */
30687
+ const MINUTE_IN_SECONDS = 60;
30688
+ /**
30689
+ * Seconds in one hour.
30690
+ *
30691
+ * @private internal timeout-chat constant
30692
+ */
30693
+ const HOUR_IN_SECONDS = 60 * MINUTE_IN_SECONDS;
30694
+ /**
30695
+ * Seconds in one day.
30696
+ *
30697
+ * @private internal timeout-chat constant
30698
+ */
30699
+ const HOURS_IN_DAY = 24;
30700
+ /**
30701
+ * Seconds in one day.
30702
+ *
30703
+ * @private internal timeout-chat constant
30495
30704
  */
30705
+ const DAY_IN_SECONDS = HOURS_IN_DAY * HOUR_IN_SECONDS;
30496
30706
  /**
30497
- * Internal endpoint used by managed coding agents to plan goal-chat messages.
30707
+ * Converts timeout duration into natural language sentence fragments.
30708
+ *
30709
+ * Shared by every timeout surface: the chat chips, the goal-chat planned messages of the Agents
30710
+ * Server, and the planned-message prompt section of the agent-folder runner.
30711
+ *
30712
+ * @param milliseconds - Duration or repeat interval in milliseconds.
30713
+ * @returns Label such as `5 minutes` or `1 hour 30 minutes`.
30714
+ *
30715
+ * @private internal utility of `<Chat/>` and the Agents Server planned messages
30498
30716
  */
30499
- const AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH = '/api/internal/agent-goal-chat-planned-messages';
30717
+ function formatTimeoutDurationHuman(milliseconds) {
30718
+ const normalizedMilliseconds = Math.max(0, Math.floor(milliseconds));
30719
+ const totalSeconds = Math.max(1, Math.floor(normalizedMilliseconds / SECOND_IN_MILLISECONDS));
30720
+ if (totalSeconds < MINUTE_IN_SECONDS) {
30721
+ return formatTimeoutUnit(totalSeconds, 'second');
30722
+ }
30723
+ if (totalSeconds < HOUR_IN_SECONDS) {
30724
+ const roundedMinutes = Math.max(1, Math.round(totalSeconds / MINUTE_IN_SECONDS));
30725
+ return formatTimeoutUnit(roundedMinutes, 'minute');
30726
+ }
30727
+ if (totalSeconds < DAY_IN_SECONDS) {
30728
+ const hours = Math.floor(totalSeconds / HOUR_IN_SECONDS);
30729
+ const minutes = Math.floor((totalSeconds % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS);
30730
+ if (minutes === 0) {
30731
+ return formatTimeoutUnit(hours, 'hour');
30732
+ }
30733
+ return `${formatTimeoutUnit(hours, 'hour')} ${formatTimeoutUnit(minutes, 'minute')}`;
30734
+ }
30735
+ const days = Math.floor(totalSeconds / DAY_IN_SECONDS);
30736
+ const hours = Math.floor((totalSeconds % DAY_IN_SECONDS) / HOUR_IN_SECONDS);
30737
+ if (hours === 0) {
30738
+ return formatTimeoutUnit(days, 'day');
30739
+ }
30740
+ return `${formatTimeoutUnit(days, 'day')} ${formatTimeoutUnit(hours, 'hour')}`;
30741
+ }
30742
+ /**
30743
+ * Formats one pluralized timeout unit.
30744
+ *
30745
+ * @private internal timeout-chat helper
30746
+ */
30747
+ function formatTimeoutUnit(value, unit) {
30748
+ return `${value} ${unit}${value === 1 ? '' : 's'}`;
30749
+ }
30750
+ // Note: [💞] Ignore a discrepancy between file name and entity name
30500
30751
 
30501
30752
  /**
30502
30753
  * Builds the planned-message instructions available to an Agents Server-managed coding agent.
30503
30754
  *
30504
- * Planned messages always target the agent's singleton goal chat, regardless of the chat in which
30505
- * the command is invoked.
30755
+ * Planned messages always target the agent's singleton goal chat, regardless of the chat in which the
30756
+ * wake-up is planned, and they repeat like `setInterval` until they are cancelled. The sidecar file is
30757
+ * the only channel that changes them, so an answer that merely claims a follow-up was planned changes
30758
+ * nothing — and an answer that changes nothing keeps the current plan running.
30759
+ *
30760
+ * @param sidecar - Sidecar prepared by the Agents Server for the answered message.
30761
+ * @returns Prompt section, or an empty string outside Agents Server-managed runs.
30506
30762
  */
30507
- function buildAgentGoalChatPromptSection(runtimeApi) {
30508
- if (!runtimeApi) {
30763
+ function buildAgentGoalChatPromptSection(sidecar) {
30764
+ if (!sidecar) {
30509
30765
  return '';
30510
30766
  }
30511
- return spaceTrim$1(`
30512
- ## Planned goal-chat messages
30767
+ return spaceTrim$1((block) => `
30768
+ ## Planned goal-chat messages
30513
30769
 
30514
- You can plan a future message that wakes you in your singleton goal chat. These commands are available during every Agents Server-managed chat invocation, not only while answering inside the goal chat.
30770
+ You can plan a message that repeatedly wakes you in your singleton goal chat. A planned message works like \`setInterval\`, not like \`setTimeout\`: once planned, it keeps waking you at its interval until you cancel it. This works during every Agents Server-managed chat invocation, not only while answering inside the goal chat.
30515
30771
 
30516
- - Use \`list_timeouts\` before planning follow-up work so you do not create duplicates.
30517
- - Use \`set_timeout\` when useful work should resume later. The delay is a positive number of milliseconds and the message should say exactly what your future invocation needs to do.
30518
- - Use \`cancel_timeout\` with the returned \`timeoutId\` when a planned message is no longer useful.
30519
- - A fired message appears in the goal chat and invokes you there. If more future work remains, plan the next useful message during that invocation.
30772
+ ${block(buildCurrentPlannedMessagesLines(sidecar.currentPlannedMessages))}
30520
30773
 
30521
- Run the corresponding authenticated command:
30774
+ Editing \`${sidecar.relativeSidecarPath}\` is the **only** way to change what wakes you. Writing in your answer that you scheduled something does not schedule anything.
30522
30775
 
30523
- \`\`\`bash
30524
- # list_timeouts
30525
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30526
- -H "Content-Type: application/json" \\
30527
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30528
- -d '{"action":"list","agentPermanentId":"${runtimeApi.agentPermanentId}"}'
30776
+ - **Keeping your planned messages as they are is the default.** When the list above already matches your goal, leave \`commands\` empty — every listed message keeps repeating on its own, and re-planning it would only duplicate it.
30777
+ - When your goal needs a repeating wake-up that is not listed above, append \`{"action":"set","milliseconds":<repeat interval>,"message":"<what each future invocation must do>"}\` to the \`commands\` array of that file.
30778
+ - When a listed message no longer matches your goal (for example your goal now says every 10 minutes while the message repeats every 5 minutes), append \`{"action":"cancel","timeoutId":"<timeout id>"}\` and one \`set\` command with the interval your goal really requires.
30779
+ - When your goal no longer needs a listed message at all, cancel it without planning a replacement.
30780
+ - Never edit \`version\`, \`agentPermanentId\`, or \`currentPlannedMessages\`.
30781
+ - The Agents Server applies every command once your answer is finished, and each repetition appears in the goal chat and invokes you there.
30782
+ - Only mention a planned follow-up in your answer when it is really planned: either you appended the matching \`set\` command, or you kept one of the messages listed above.
30783
+ `);
30784
+ }
30785
+ /**
30786
+ * Renders the already planned messages so the agent can compare them with its goal.
30787
+ *
30788
+ * @param currentPlannedMessages - Planned messages prepared by the Agents Server.
30789
+ * @returns Markdown list, or a sentence stating that nothing is planned yet.
30790
+ *
30791
+ * @private function of `buildAgentGoalChatPromptSection`
30792
+ */
30793
+ function buildCurrentPlannedMessagesLines(currentPlannedMessages) {
30794
+ if (currentPlannedMessages.length === 0) {
30795
+ return 'You currently have no planned messages waiting.';
30796
+ }
30797
+ const plannedMessageLines = currentPlannedMessages.map(createCurrentPlannedMessageLine);
30798
+ return spaceTrim$1((block) => `
30799
+ These planned messages are already waiting for you, so keep them unless they stopped matching your goal:
30529
30800
 
30530
- # set_timeout
30531
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30532
- -H "Content-Type: application/json" \\
30533
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30534
- -d '{"action":"set","agentPermanentId":"${runtimeApi.agentPermanentId}","milliseconds":3600000,"message":"Continue the current goal and decide the next concrete action."}'
30801
+ ${block(plannedMessageLines.join('\n'))}
30802
+ `);
30803
+ }
30804
+ /**
30805
+ * Renders one already planned message with the schedule the agent has to compare with its goal.
30806
+ *
30807
+ * @param plannedMessage - One planned message prepared by the Agents Server.
30808
+ * @returns Markdown list item.
30809
+ *
30810
+ * @private function of `buildAgentGoalChatPromptSection`
30811
+ */
30812
+ function createCurrentPlannedMessageLine(plannedMessage) {
30813
+ var _a;
30814
+ const message = ((_a = plannedMessage.message) === null || _a === void 0 ? void 0 : _a.trim()) || 'Continue working towards the current goal.';
30815
+ const schedule = plannedMessage.intervalMs
30816
+ ? `repeats every ${formatTimeoutDurationHuman(plannedMessage.intervalMs)}`
30817
+ : `wakes you once at ${plannedMessage.dueAt}`;
30818
+ return `- \`${plannedMessage.timeoutId}\` ${schedule}: ${message}`;
30819
+ }
30535
30820
 
30536
- # cancel_timeout
30537
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30538
- -H "Content-Type: application/json" \\
30539
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30540
- -d '{"action":"cancel","agentPermanentId":"${runtimeApi.agentPermanentId}","timeoutId":"<timeout-id>"}'
30541
- \`\`\`
30542
- `);
30821
+ /**
30822
+ * Creates the stable file-system safe base name that belongs to one queued `.book` message.
30823
+ *
30824
+ * Every sidecar the Agents Server prepares next to a queued message (TEAM workspaces, planned-message
30825
+ * files, ...) derives its own name from this one normalization, so a sidecar can always be found again
30826
+ * from the message file name alone.
30827
+ *
30828
+ * @param messageFileName - File name of the queued message, with or without directories.
30829
+ * @returns Normalized base name usable as a directory or file name.
30830
+ *
30831
+ * @private internal convention shared by the Agents Server and agent-folder runner
30832
+ */
30833
+ function createAgentMessageSidecarBaseName(messageFileName) {
30834
+ const rawBaseName = basename(messageFileName).replace(/\.book$/iu, '');
30835
+ const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
30836
+ return normalizedBaseName || 'message';
30543
30837
  }
30544
30838
 
30545
30839
  // Note: [💞] This file defines the shared TEAM workspace convention rather than one standalone entity.
@@ -30573,9 +30867,7 @@ const AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME = 'teammates';
30573
30867
  * @private internal convention shared by the Agents Server and agent-folder runner
30574
30868
  */
30575
30869
  function createAgentTeamConversationWorkspaceDirectoryName(messageFileName) {
30576
- const rawBaseName = basename(messageFileName).replace(/\.book$/iu, '');
30577
- const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
30578
- return normalizedBaseName || 'message';
30870
+ return createAgentMessageSidecarBaseName(messageFileName);
30579
30871
  }
30580
30872
  /**
30581
30873
  * Creates the relative active workspace path for one queued message.
@@ -30645,8 +30937,8 @@ function buildAgentTeamPromptSection(workspace) {
30645
30937
  return '';
30646
30938
  }
30647
30939
  const teammateSections = workspace.manifest.teammates.map((teammate) => {
30648
- const sourcePath = toPromptPath(join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
30649
- const transcriptPath = toPromptPath(join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
30940
+ const sourcePath = toPromptPath$1(join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
30941
+ const transcriptPath = toPromptPath$1(join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
30650
30942
  const instructions = teammate.instructions.trim() || 'No additional TEAM instructions were provided.';
30651
30943
  return spaceTrim$1(`
30652
30944
  - ${teammate.agentName}
@@ -30664,7 +30956,7 @@ function buildAgentTeamPromptSection(workspace) {
30664
30956
 
30665
30957
  - The underlying coding harness is already executing this turn **exactly once**. Never start, queue, or invoke another coding harness, agent runner, or \`ptbk\` command to consult a teammate.
30666
30958
  - The primary agent remains responsible for the final user-facing \`MESSAGE @Agent\` answer.
30667
- - Teammate source files are read-only context. When a teammate consultation occurs, record the actual exchange in a new top-level \`.book\` file in \`${toPromptPath(workspace.relativeWorkspacePath)}\`.
30959
+ - Teammate source files are read-only context. When a teammate consultation occurs, record the actual exchange in a new top-level \`.book\` file in \`${toPromptPath$1(workspace.relativeWorkspacePath)}\`.
30668
30960
  - Name each transcript \`<teammate permanent id>--<sequence>.book\`, for example \`${workspace.manifest.teammates[0].permanentId}--01.book\`.
30669
30961
  - Every transcript must contain alternating \`MESSAGE @${workspace.manifest.primaryAgent.agentName}\` and \`MESSAGE @<teammate name>\` blocks. Do not create a transcript for a teammate that was not consulted.
30670
30962
  `);
@@ -30672,7 +30964,7 @@ function buildAgentTeamPromptSection(workspace) {
30672
30964
  /**
30673
30965
  * Converts a filesystem-relative path into the portable path notation used in prompts.
30674
30966
  */
30675
- function toPromptPath(path) {
30967
+ function toPromptPath$1(path) {
30676
30968
  return path.replace(/\\/gu, '/');
30677
30969
  }
30678
30970
 
@@ -30685,7 +30977,7 @@ function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage, option
30685
30977
 
30686
30978
  - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE\` block. In a normal chat it is from \`@User\`; in your internal goal chat it can be from \`@Agent\`.
30687
30979
  - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
30688
- ${block(buildAllowedFileChangesPromptLine(options.teamWorkspace))}
30980
+ ${block(buildAllowedFileChangesPromptLine(options))}
30689
30981
 
30690
30982
  ## Rules for the answering
30691
30983
 
@@ -30721,7 +31013,7 @@ function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage, option
30721
31013
 
30722
31014
  - You can use \`message\` for message that will be sent immediately after clicking the button, or \`messageDraft\` for message that will be prefilled in the input field for editing before sending.
30723
31015
 
30724
- ${block(buildAgentGoalChatPromptSection(options.goalChatRuntimeApi))}
31016
+ ${block(buildAgentGoalChatPromptSection(options.plannedMessagesSidecar))}
30725
31017
 
30726
31018
  ${block(buildAgentProjectsPromptSection(options))}
30727
31019
 
@@ -30735,11 +31027,30 @@ function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage, option
30735
31027
  /**
30736
31028
  * Explains the exact files that one coding harness may change for a user turn.
30737
31029
  */
30738
- function buildAllowedFileChangesPromptLine(teamWorkspace) {
30739
- if (!teamWorkspace) {
30740
- return `- Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`;
31030
+ function buildAllowedFileChangesPromptLine(options) {
31031
+ const allowedFileChanges = [`files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`];
31032
+ if (options.teamWorkspace) {
31033
+ allowedFileChanges.push(`new consultation transcripts inside \`${toPromptPath(options.teamWorkspace.relativeWorkspacePath)}\``);
31034
+ }
31035
+ if (options.plannedMessagesSidecar) {
31036
+ allowedFileChanges.push(`the \`commands\` array of \`${toPromptPath(options.plannedMessagesSidecar.relativeSidecarPath)}\``);
31037
+ }
31038
+ return `- Do not modify any other file in the repository, except ${formatAllowedFileChanges(allowedFileChanges)}`;
31039
+ }
31040
+ /**
31041
+ * Joins the allowed file-change phrases into one readable enumeration.
31042
+ */
31043
+ function formatAllowedFileChanges(allowedFileChanges) {
31044
+ if (allowedFileChanges.length === 1) {
31045
+ return allowedFileChanges[0];
30741
31046
  }
30742
- return `- Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory and new consultation transcripts inside \`${teamWorkspace.relativeWorkspacePath.replace(/\\/gu, '/')}\``;
31047
+ return `${allowedFileChanges.slice(0, -1).join(', ')} and ${allowedFileChanges[allowedFileChanges.length - 1]}`;
31048
+ }
31049
+ /**
31050
+ * Converts a filesystem-relative path into the portable path notation used in prompts.
31051
+ */
31052
+ function toPromptPath(path) {
31053
+ return path.replace(/\\/gu, '/');
30743
31054
  }
30744
31055
 
30745
31056
  var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModels",description:"List of available model names together with their descriptions as JSON",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelsRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n```json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n```\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n```json\n{availableModels}\n```\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelsRequirements",format:"JSON",dependentParameterNames:["availableModels","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModels}` List of available model names together with their descriptions as JSON\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelsRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n\\`\\`\\`json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n\\`\\`\\`json\n{availableModels}\n\\`\\`\\`\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelsRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
@@ -36801,7 +37112,8 @@ async function augmentAgentModelRequirementsFromSource(requirements, parseResult
36801
37112
  requirements = await importReferencedFiles(requirements);
36802
37113
  requirements = appendMcpServers(requirements, agentSource);
36803
37114
  requirements = appendNonCommitmentContent(requirements, parseResult);
36804
- return appendExampleInteractions(requirements, parseResult);
37115
+ requirements = appendExampleInteractions(requirements, parseResult);
37116
+ return appendUnknownCommitmentContent(requirements, parseResult);
36805
37117
  }
36806
37118
  /**
36807
37119
  * Imports text files referenced by IMPORT commitments and appends their transformed content to the system message.
@@ -36923,6 +37235,36 @@ function appendNonCommitmentContent(requirements, parseResult) {
36923
37235
  }
36924
37236
  return appendSystemMessageSection(requirements, nonCommitmentContent);
36925
37237
  }
37238
+ /**
37239
+ * Appends unknown commitment blocks as final extra context without applying commitment side effects.
37240
+ *
37241
+ * @param requirements - Current requirements snapshot.
37242
+ * @param parseResult - Parsed source including unknown commitment blocks.
37243
+ * @returns Requirements with unknown commitment context appended at the end of the system message.
37244
+ *
37245
+ * @private internal utility of `augmentAgentModelRequirementsFromSource`
37246
+ */
37247
+ function appendUnknownCommitmentContent(requirements, parseResult) {
37248
+ const unknownCommitmentContent = getUnknownCommitmentContent(parseResult);
37249
+ if (!unknownCommitmentContent) {
37250
+ return requirements;
37251
+ }
37252
+ return appendSystemMessageSection(requirements, unknownCommitmentContent);
37253
+ }
37254
+ /**
37255
+ * Joins unknown commitment blocks into their final extra-context representation.
37256
+ *
37257
+ * @param parseResult - Parsed source including unknown commitment blocks.
37258
+ * @returns Unknown commitment source blocks in their original order.
37259
+ *
37260
+ * @private internal utility of `appendUnknownCommitmentContent`
37261
+ */
37262
+ function getUnknownCommitmentContent(parseResult) {
37263
+ return parseResult.unknownCommitments
37264
+ .map((commitment) => commitment.source)
37265
+ .filter(Boolean)
37266
+ .join('\n\n');
37267
+ }
36926
37268
  /**
36927
37269
  * Collects plain-text lines that were not parsed as commitments and should still become part of the final system message.
36928
37270
  *
@@ -37462,12 +37804,12 @@ async function finalizeAgentTeamConversationWorkspace(options) {
37462
37804
  await rm(finishedWorkspacePath, { recursive: true, force: true });
37463
37805
  await mkdir(finishedWorkspacePath, { recursive: true });
37464
37806
  const relativePaths = [
37465
- toPortablePath$1(join(finishedRelativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME)),
37807
+ toPortablePath$2(join(finishedRelativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME)),
37466
37808
  ];
37467
37809
  await rename(activeManifestPath, join(finishedWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME));
37468
37810
  for (const transcriptFileName of transcriptFileNames) {
37469
37811
  await rename(join(activeWorkspacePath, transcriptFileName), join(finishedWorkspacePath, transcriptFileName));
37470
- relativePaths.push(toPortablePath$1(join(finishedRelativeWorkspacePath, transcriptFileName)));
37812
+ relativePaths.push(toPortablePath$2(join(finishedRelativeWorkspacePath, transcriptFileName)));
37471
37813
  }
37472
37814
  await rm(activeWorkspacePath, { recursive: true, force: true });
37473
37815
  return { relativePaths };
@@ -37484,7 +37826,7 @@ async function listAgentTeamTranscriptFileNames(activeWorkspacePath) {
37484
37826
  .sort((firstFileName, secondFileName) => firstFileName.localeCompare(secondFileName));
37485
37827
  }
37486
37828
  catch (error) {
37487
- if (isFileNotFoundError$2(error)) {
37829
+ if (isFileNotFoundError$3(error)) {
37488
37830
  return [];
37489
37831
  }
37490
37832
  throw error;
@@ -37499,7 +37841,7 @@ async function isExistingPath$1(path) {
37499
37841
  return true;
37500
37842
  }
37501
37843
  catch (error) {
37502
- if (isFileNotFoundError$2(error)) {
37844
+ if (isFileNotFoundError$3(error)) {
37503
37845
  return false;
37504
37846
  }
37505
37847
  throw error;
@@ -37508,11 +37850,204 @@ async function isExistingPath$1(path) {
37508
37850
  /**
37509
37851
  * Converts a filesystem-relative path into a portable Git path.
37510
37852
  */
37853
+ function toPortablePath$2(path) {
37854
+ return path.replace(/\\/gu, '/');
37855
+ }
37856
+ /**
37857
+ * Returns true when one filesystem error indicates a missing path.
37858
+ */
37859
+ function isFileNotFoundError$3(error) {
37860
+ return Boolean(error &&
37861
+ typeof error === 'object' &&
37862
+ 'code' in error &&
37863
+ (error.code === 'ENOENT' || error.code === 'ENOTDIR'));
37864
+ }
37865
+
37866
+ // Note: [💞] This file defines the shared planned-message sidecar convention rather than one standalone entity.
37867
+ /**
37868
+ * Relative directory holding the planned-message sidecar of each queued message.
37869
+ *
37870
+ * @private internal convention shared by the Agents Server and agent-folder runner
37871
+ */
37872
+ const AGENT_PLANNED_MESSAGES_DIRECTORY_PATH = join(AGENT_MESSAGES_DIRECTORY_PATH, 'planned');
37873
+ /**
37874
+ * Command actions an agent can request from its planned-message sidecar.
37875
+ *
37876
+ * @private internal convention shared by the Agents Server and agent-folder runner
37877
+ */
37878
+ const AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS = ['set', 'cancel'];
37879
+ /**
37880
+ * Creates the relative planned-message sidecar path of one queued message.
37881
+ *
37882
+ * @param messageFileName - File name of the queued `.book` message.
37883
+ * @returns Relative path of the sidecar inside the agent folder.
37884
+ *
37885
+ * @private internal convention shared by the Agents Server and agent-folder runner
37886
+ */
37887
+ function createAgentPlannedMessagesSidecarPath(messageFileName) {
37888
+ return join(AGENT_PLANNED_MESSAGES_DIRECTORY_PATH, `${createAgentMessageSidecarBaseName(messageFileName)}.json`);
37889
+ }
37890
+ /**
37891
+ * Parses one untrusted planned-message sidecar written back by a coding harness.
37892
+ *
37893
+ * A malformed sidecar never fails the already answered chat, so this returns `null` instead of throwing.
37894
+ *
37895
+ * @param sidecarContent - Raw file content read from the agent folder.
37896
+ * @returns Normalized sidecar, or `null` when it cannot be used.
37897
+ *
37898
+ * @private internal convention shared by the Agents Server and agent-folder runner
37899
+ */
37900
+ function parseAgentPlannedMessagesSidecar(sidecarContent) {
37901
+ let parsedSidecar;
37902
+ try {
37903
+ parsedSidecar = JSON.parse(sidecarContent);
37904
+ }
37905
+ catch (_a) {
37906
+ return null;
37907
+ }
37908
+ if (!parsedSidecar || typeof parsedSidecar !== 'object' || Array.isArray(parsedSidecar)) {
37909
+ return null;
37910
+ }
37911
+ const sidecar = parsedSidecar;
37912
+ if (sidecar.version !== 1 || typeof sidecar.agentPermanentId !== 'string') {
37913
+ return null;
37914
+ }
37915
+ return {
37916
+ version: 1,
37917
+ agentPermanentId: sidecar.agentPermanentId,
37918
+ currentPlannedMessages: normalizeAgentPlannedMessageSnapshots(sidecar.currentPlannedMessages),
37919
+ commands: normalizeAgentPlannedMessageCommands(sidecar.commands),
37920
+ };
37921
+ }
37922
+ /**
37923
+ * Keeps only the commands whose action is understood by the planned-message actions.
37924
+ *
37925
+ * @param rawCommands - Untrusted `commands` value read from one sidecar.
37926
+ * @returns Commands that can be dispatched, in their original order.
37927
+ *
37928
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37929
+ */
37930
+ function normalizeAgentPlannedMessageCommands(rawCommands) {
37931
+ if (!Array.isArray(rawCommands)) {
37932
+ return [];
37933
+ }
37934
+ return rawCommands.filter(isAgentPlannedMessageCommand);
37935
+ }
37936
+ /**
37937
+ * Checks whether one untrusted sidecar entry names a supported planned-message action.
37938
+ *
37939
+ * @param value - Untrusted command entry.
37940
+ * @returns `true` when the entry can be dispatched.
37941
+ *
37942
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37943
+ */
37944
+ function isAgentPlannedMessageCommand(value) {
37945
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
37946
+ return false;
37947
+ }
37948
+ const action = value.action;
37949
+ return (typeof action === 'string' &&
37950
+ AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS.includes(action));
37951
+ }
37952
+ /**
37953
+ * Keeps only the already planned messages that can be shown to the agent.
37954
+ *
37955
+ * @param rawSnapshots - Untrusted `currentPlannedMessages` value read from one sidecar.
37956
+ * @returns Planned-message snapshots with the fields required for display and cancellation.
37957
+ *
37958
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37959
+ */
37960
+ function normalizeAgentPlannedMessageSnapshots(rawSnapshots) {
37961
+ if (!Array.isArray(rawSnapshots)) {
37962
+ return [];
37963
+ }
37964
+ return rawSnapshots.filter(isAgentPlannedMessageSnapshot).map(createNormalizedAgentPlannedMessageSnapshot);
37965
+ }
37966
+ /**
37967
+ * Normalizes the optional fields of one already planned message.
37968
+ *
37969
+ * @param snapshot - Planned-message entry with the required identity fields.
37970
+ * @returns Snapshot with a usable repeat interval and message.
37971
+ *
37972
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37973
+ */
37974
+ function createNormalizedAgentPlannedMessageSnapshot(snapshot) {
37975
+ const intervalMs = Number(snapshot.intervalMs);
37976
+ return {
37977
+ timeoutId: snapshot.timeoutId,
37978
+ dueAt: snapshot.dueAt,
37979
+ message: typeof snapshot.message === 'string' ? snapshot.message : null,
37980
+ intervalMs: Number.isFinite(intervalMs) && intervalMs > 0 ? Math.floor(intervalMs) : null,
37981
+ };
37982
+ }
37983
+ /**
37984
+ * Checks whether one untrusted sidecar entry describes an already planned message.
37985
+ *
37986
+ * @param value - Untrusted planned-message entry.
37987
+ * @returns `true` when the entry can be shown and cancelled.
37988
+ *
37989
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37990
+ */
37991
+ function isAgentPlannedMessageSnapshot(value) {
37992
+ return Boolean(value &&
37993
+ typeof value === 'object' &&
37994
+ typeof value.timeoutId === 'string' &&
37995
+ typeof value.dueAt === 'string');
37996
+ }
37997
+
37998
+ /**
37999
+ * Loads the planned-message sidecar the Agents Server prepared for one queued message.
38000
+ *
38001
+ * @param projectPath - Absolute path of the current agent runner folder.
38002
+ * @param queuedMessage - Queued message currently being answered.
38003
+ * @returns Sidecar details for the prompt, or `null` outside Agents Server-managed runs.
38004
+ */
38005
+ async function loadAgentPlannedMessagesSidecar(projectPath, queuedMessage) {
38006
+ const relativeSidecarPath = createAgentPlannedMessagesSidecarPath(queuedMessage.fileName);
38007
+ const sidecarContent = await readOptionalTextFile$2(join(projectPath, relativeSidecarPath));
38008
+ if (sidecarContent === null) {
38009
+ return null;
38010
+ }
38011
+ const sidecar = parseAgentPlannedMessagesSidecar(sidecarContent);
38012
+ if (!sidecar) {
38013
+ return null;
38014
+ }
38015
+ return {
38016
+ relativeSidecarPath: toPortablePath$1(relativeSidecarPath),
38017
+ currentPlannedMessages: sidecar.currentPlannedMessages,
38018
+ };
38019
+ }
38020
+ /**
38021
+ * Reads one text file and treats a missing planned-message sidecar as absent.
38022
+ *
38023
+ * @param path - Absolute sidecar path.
38024
+ * @returns File content, or `null` when the sidecar does not exist.
38025
+ */
38026
+ async function readOptionalTextFile$2(path) {
38027
+ try {
38028
+ return await readFile(path, 'utf-8');
38029
+ }
38030
+ catch (error) {
38031
+ if (isFileNotFoundError$2(error)) {
38032
+ return null;
38033
+ }
38034
+ throw error;
38035
+ }
38036
+ }
38037
+ /**
38038
+ * Converts a filesystem-relative path to the portable form used in prompts.
38039
+ *
38040
+ * @param path - Relative path inside the agent folder.
38041
+ * @returns Path with forward slashes.
38042
+ */
37511
38043
  function toPortablePath$1(path) {
37512
38044
  return path.replace(/\\/gu, '/');
37513
38045
  }
37514
38046
  /**
37515
38047
  * Returns true when one filesystem error indicates a missing path.
38048
+ *
38049
+ * @param error - Caught filesystem failure.
38050
+ * @returns `true` for missing-path errors.
37516
38051
  */
37517
38052
  function isFileNotFoundError$2(error) {
37518
38053
  return Boolean(error &&
@@ -37527,7 +38062,7 @@ function isFileNotFoundError$2(error) {
37527
38062
  async function loadAgentTeamConversationWorkspace(projectPath, queuedMessage) {
37528
38063
  const relativeWorkspacePath = createAgentTeamConversationWorkspacePath(queuedMessage.fileName);
37529
38064
  const manifestPath = join(projectPath, relativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME);
37530
- const manifestContent = await readOptionalTextFile(manifestPath);
38065
+ const manifestContent = await readOptionalTextFile$1(manifestPath);
37531
38066
  if (manifestContent === null) {
37532
38067
  return null;
37533
38068
  }
@@ -37555,7 +38090,7 @@ function parseAgentTeamConversationWorkspaceManifest(manifestContent) {
37555
38090
  /**
37556
38091
  * Reads one text file and treats a missing team workspace as absent.
37557
38092
  */
37558
- async function readOptionalTextFile(path) {
38093
+ async function readOptionalTextFile$1(path) {
37559
38094
  try {
37560
38095
  return await readFile(path, 'utf-8');
37561
38096
  }
@@ -37622,6 +38157,233 @@ function resolveAgentProjectsUrlPath(projectPath) {
37622
38157
  return `/agents/${encodeURIComponent(agentId)}/projects`;
37623
38158
  }
37624
38159
 
38160
+ /**
38161
+ * Parses every structured event out of one coding-harness runtime log.
38162
+ *
38163
+ * The local agent runner streams the answering harness output into a live runtime log file, one
38164
+ * JSON event per line, optionally prefixed by shell noise. Lines without a usable JSON object are
38165
+ * skipped so a partially written or interleaved log never breaks its consumers.
38166
+ *
38167
+ * @param logText - Raw runtime log content.
38168
+ * @returns Structured events in the order they were streamed.
38169
+ * @private internal utility of the agent-message runtime
38170
+ */
38171
+ function parseAgentMessageRuntimeLogEvents(logText) {
38172
+ if (!logText) {
38173
+ return [];
38174
+ }
38175
+ const events = [];
38176
+ for (const line of logText.split(/\r?\n/u)) {
38177
+ const event = parseAgentMessageRuntimeLogEvent(line);
38178
+ if (event) {
38179
+ events.push(event);
38180
+ }
38181
+ }
38182
+ return events;
38183
+ }
38184
+ /**
38185
+ * Parses one runtime log line into a structured event when it embeds JSON.
38186
+ *
38187
+ * @param line - One raw runtime log line.
38188
+ * @returns Structured event, or `null` when the line carries no JSON object.
38189
+ * @private internal helper of `parseAgentMessageRuntimeLogEvents`
38190
+ */
38191
+ function parseAgentMessageRuntimeLogEvent(line) {
38192
+ const jsonStartIndex = line.indexOf('{');
38193
+ if (jsonStartIndex === -1) {
38194
+ return null;
38195
+ }
38196
+ try {
38197
+ const parsedEvent = JSON.parse(line.slice(jsonStartIndex).trim());
38198
+ if (typeof parsedEvent !== 'object' || parsedEvent === null || Array.isArray(parsedEvent)) {
38199
+ return null;
38200
+ }
38201
+ return parsedEvent;
38202
+ }
38203
+ catch (_a) {
38204
+ return null;
38205
+ }
38206
+ }
38207
+
38208
+ /**
38209
+ * Matches one project directory name following the agent `projects/` folder in a file path or command.
38210
+ *
38211
+ * Both path separators are accepted because the local runner answers messages on Unix and Windows,
38212
+ * and JSON-encoded payloads escape backslashes.
38213
+ *
38214
+ * @private internal constant of agent-message touched projects
38215
+ */
38216
+ const TOUCHED_PROJECT_PATH_REGEX = new RegExp(`${AGENT_PROJECTS_DIRECTORY_PATH}[\\\\/]+([^\\\\/"'\\s,;:]+)`, 'giu');
38217
+ /**
38218
+ * Resolves which projects one coding harness touched while answering a single message.
38219
+ *
38220
+ * The runtime log streams what the harness really did, so a project counts as touched when the
38221
+ * harness named it in a tool invocation — reading, searching, running, or editing files inside
38222
+ * `projects/<project-name>/`. Only tool payloads are inspected: tool results and assistant
38223
+ * narration are ignored so a project merely mentioned in the conversation is never reported.
38224
+ *
38225
+ * Every candidate is matched against the projects that really exist, which both filters prompt
38226
+ * placeholders such as `projects/<project-name>/` and keeps the reported names canonical.
38227
+ *
38228
+ * @param options - Raw runtime log content and the project directory names of the agent.
38229
+ * @returns Touched project names in the canonical spelling, ordered by first appearance.
38230
+ * @private internal utility of the agent-message runtime
38231
+ */
38232
+ function resolveAgentMessageTouchedProjectNames(options) {
38233
+ const canonicalProjectNames = createCanonicalProjectNameLookup(options.knownProjectNames);
38234
+ if (canonicalProjectNames.size === 0) {
38235
+ return [];
38236
+ }
38237
+ const touchedProjectNames = new Set();
38238
+ for (const event of parseAgentMessageRuntimeLogEvents(options.logText)) {
38239
+ for (const toolInvocationText of resolveRuntimeLogEventToolInvocationTexts(event)) {
38240
+ for (const candidateProjectName of resolveCandidateProjectNames(toolInvocationText)) {
38241
+ const canonicalProjectName = canonicalProjectNames.get(candidateProjectName.toLowerCase());
38242
+ if (canonicalProjectName !== undefined) {
38243
+ touchedProjectNames.add(canonicalProjectName);
38244
+ }
38245
+ }
38246
+ }
38247
+ }
38248
+ return Array.from(touchedProjectNames);
38249
+ }
38250
+ /**
38251
+ * Indexes the existing project directory names by their lower-cased form.
38252
+ *
38253
+ * @param knownProjectNames - Project directory names of the agent.
38254
+ * @returns Lookup from the lower-cased name to its canonical spelling.
38255
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38256
+ */
38257
+ function createCanonicalProjectNameLookup(knownProjectNames) {
38258
+ const canonicalProjectNames = new Map();
38259
+ for (const knownProjectName of knownProjectNames) {
38260
+ const normalizedProjectName = knownProjectName.trim();
38261
+ if (normalizedProjectName.length > 0) {
38262
+ canonicalProjectNames.set(normalizedProjectName.toLowerCase(), normalizedProjectName);
38263
+ }
38264
+ }
38265
+ return canonicalProjectNames;
38266
+ }
38267
+ /**
38268
+ * Collects the texts of every tool invocation described by one runtime log event.
38269
+ *
38270
+ * Claude Code reports tool invocations as `tool_use` content blocks, while Codex reports executed
38271
+ * commands and file changes as items — both carry the paths the harness worked with.
38272
+ *
38273
+ * @param event - One structured runtime log event.
38274
+ * @returns Texts that may contain project paths.
38275
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38276
+ */
38277
+ function resolveRuntimeLogEventToolInvocationTexts(event) {
38278
+ var _a, _b, _c;
38279
+ const toolInvocationTexts = [];
38280
+ for (const contentBlock of ((_a = event.message) === null || _a === void 0 ? void 0 : _a.content) || []) {
38281
+ if (contentBlock.type === 'tool_use' && contentBlock.input) {
38282
+ toolInvocationTexts.push(serializeToolInput(contentBlock.input));
38283
+ }
38284
+ }
38285
+ if (typeof ((_b = event.item) === null || _b === void 0 ? void 0 : _b.command) === 'string') {
38286
+ toolInvocationTexts.push(event.item.command);
38287
+ }
38288
+ for (const fileChange of ((_c = event.item) === null || _c === void 0 ? void 0 : _c.changes) || []) {
38289
+ if (typeof fileChange.path === 'string') {
38290
+ toolInvocationTexts.push(fileChange.path);
38291
+ }
38292
+ }
38293
+ return toolInvocationTexts;
38294
+ }
38295
+ /**
38296
+ * Serializes one tool input payload into scannable text.
38297
+ *
38298
+ * @param toolInput - Tool arguments of one harness tool invocation.
38299
+ * @returns Serialized payload, or an empty string when it cannot be serialized.
38300
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38301
+ */
38302
+ function serializeToolInput(toolInput) {
38303
+ try {
38304
+ return JSON.stringify(toolInput);
38305
+ }
38306
+ catch (_a) {
38307
+ return '';
38308
+ }
38309
+ }
38310
+ /**
38311
+ * Extracts every project directory name mentioned right after the agent `projects/` folder.
38312
+ *
38313
+ * @param toolInvocationText - One tool payload, command, or changed path.
38314
+ * @returns Candidate project directory names.
38315
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38316
+ */
38317
+ function resolveCandidateProjectNames(toolInvocationText) {
38318
+ const candidateProjectNames = [];
38319
+ const projectPathRegex = new RegExp(TOUCHED_PROJECT_PATH_REGEX.source, TOUCHED_PROJECT_PATH_REGEX.flags);
38320
+ let projectPathMatch = projectPathRegex.exec(toolInvocationText);
38321
+ while (projectPathMatch !== null) {
38322
+ const candidateProjectName = projectPathMatch[1];
38323
+ if (candidateProjectName) {
38324
+ candidateProjectNames.push(candidateProjectName);
38325
+ }
38326
+ projectPathMatch = projectPathRegex.exec(toolInvocationText);
38327
+ }
38328
+ return candidateProjectNames;
38329
+ }
38330
+
38331
+ /**
38332
+ * Resolves which agent projects one answered message viewed or edited.
38333
+ *
38334
+ * The runtime log is deleted as soon as the harness run finishes, so this must be called while
38335
+ * the log still exists — the Agents Server later shows the reported projects as chips below the
38336
+ * answer. Reporting touched projects is best-effort telemetry: an unreadable log or projects
38337
+ * folder simply yields no projects instead of failing the already answered message.
38338
+ *
38339
+ * @param options - Agent folder path and the live runtime log path of the answered message.
38340
+ * @returns Touched project directory names, ordered by first appearance in the run.
38341
+ */
38342
+ async function resolveTouchedAgentProjects(options) {
38343
+ const [logText, knownProjectNames] = await Promise.all([
38344
+ readOptionalTextFile(options.runtimeLogPath),
38345
+ listAgentProjectDirectoryNames(options.projectPath),
38346
+ ]);
38347
+ return resolveAgentMessageTouchedProjectNames({ logText, knownProjectNames });
38348
+ }
38349
+ /**
38350
+ * Lists the direct project directories of one agent folder.
38351
+ *
38352
+ * @param projectPath - Absolute path of the local agent folder.
38353
+ * @returns Project directory names, or an empty list when the agent has no projects folder.
38354
+ *
38355
+ * @private helper of `resolveTouchedAgentProjects`
38356
+ */
38357
+ async function listAgentProjectDirectoryNames(projectPath) {
38358
+ try {
38359
+ const projectsRootEntries = await readdir(join(projectPath, AGENT_PROJECTS_DIRECTORY_PATH), {
38360
+ withFileTypes: true,
38361
+ });
38362
+ return projectsRootEntries
38363
+ .filter((projectsRootEntry) => projectsRootEntry.isDirectory())
38364
+ .map((projectDirectoryEntry) => projectDirectoryEntry.name);
38365
+ }
38366
+ catch (_a) {
38367
+ return [];
38368
+ }
38369
+ }
38370
+ /**
38371
+ * Reads one text file and treats a missing runtime log as "nothing was recorded".
38372
+ *
38373
+ * @param filePath - Absolute path of the file to read.
38374
+ * @returns File content, or `null` when it cannot be read.
38375
+ *
38376
+ * @private helper of `resolveTouchedAgentProjects`
38377
+ */
38378
+ async function readOptionalTextFile(filePath) {
38379
+ try {
38380
+ return await readFile(filePath, 'utf-8');
38381
+ }
38382
+ catch (_a) {
38383
+ return null;
38384
+ }
38385
+ }
38386
+
37625
38387
  // Note: [💞] Ignore a discrepancy between file name and entity name
37626
38388
  /**
37627
38389
  * File suffix appended to one answered message book to store its run report sidecar.
@@ -37901,10 +38663,11 @@ async function runQueuedAgentMessage(options) {
37901
38663
  const { projectPath, options: runOptions, runner, actualRunnerModel, queuedMessage, uiHandle, isSharedDashboard, } = options;
37902
38664
  const agentSystemMessage = await loadLocalAgentSystemMessage(projectPath);
37903
38665
  const teamWorkspace = await loadAgentTeamConversationWorkspace(projectPath, queuedMessage);
38666
+ const plannedMessagesSidecar = await loadAgentPlannedMessagesSidecar(projectPath, queuedMessage);
37904
38667
  const agentServerRuntimeApi = resolveAgentProjectRuntimePromptApi(projectPath);
37905
38668
  const prompt = buildAgentMessagePrompt(queuedMessage.relativePath, agentSystemMessage, {
37906
38669
  projectRuntimeApi: agentServerRuntimeApi,
37907
- goalChatRuntimeApi: agentServerRuntimeApi,
38670
+ plannedMessagesSidecar: plannedMessagesSidecar || undefined,
37908
38671
  projectsUrlPath: resolveAgentProjectsUrlPath(projectPath),
37909
38672
  teamWorkspace: teamWorkspace || undefined,
37910
38673
  });
@@ -37925,21 +38688,29 @@ async function runQueuedAgentMessage(options) {
37925
38688
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentScriptPath(scriptPath);
37926
38689
  }
37927
38690
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
38691
+ const executionStartedAt = new Date().toISOString();
37928
38692
  let promptRunResult;
38693
+ let touchedProjectNames = [];
37929
38694
  try {
37930
38695
  try {
37931
- promptRunResult = await withPromptRuntimeLog(scriptPath, async (logPath) => await runPromptWithTestFeedback({
37932
- runner,
37933
- prompt,
37934
- scriptPath,
37935
- projectPath,
37936
- promptLabel: queuedMessage.relativePath,
37937
- logPath,
37938
- preserveArtifactsOnSuccess: false,
37939
- onAttemptStarted: (attemptCount) => {
37940
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(attemptCount);
37941
- },
37942
- }), { preserveArtifactsOnSuccess: false });
38696
+ promptRunResult = await withPromptRuntimeLog(scriptPath, async (logPath) => {
38697
+ const runResult = await runPromptWithTestFeedback({
38698
+ runner,
38699
+ prompt,
38700
+ scriptPath,
38701
+ projectPath,
38702
+ promptLabel: queuedMessage.relativePath,
38703
+ logPath,
38704
+ preserveArtifactsOnSuccess: false,
38705
+ onAttemptStarted: (attemptCount) => {
38706
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(attemptCount);
38707
+ },
38708
+ });
38709
+ // Note: The runtime log is deleted right after this handler, so the projects this
38710
+ // answer worked with must be resolved while the log still exists.
38711
+ touchedProjectNames = await resolveTouchedAgentProjects({ projectPath, runtimeLogPath: logPath });
38712
+ return runResult;
38713
+ }, { preserveArtifactsOnSuccess: false });
37943
38714
  }
37944
38715
  catch (error) {
37945
38716
  throw withAgentWatchErrorContext(error, {
@@ -37953,6 +38724,7 @@ async function runQueuedAgentMessage(options) {
37953
38724
  finally {
37954
38725
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
37955
38726
  }
38727
+ const executionFinishedAt = new Date().toISOString();
37956
38728
  await normalizeLineEndingsForAgentRound(projectPath, runOptions, roundChangedFilesSnapshot);
37957
38729
  const finishedTeamWorkspace = await finalizeAgentTeamConversationWorkspace({
37958
38730
  projectPath,
@@ -37971,6 +38743,11 @@ async function runQueuedAgentMessage(options) {
37971
38743
  modelName: actualRunnerModel,
37972
38744
  loginMethod: promptRunResult.loginMethod,
37973
38745
  usage: promptRunResult.usage,
38746
+ executionTiming: {
38747
+ startedAt: executionStartedAt,
38748
+ finishedAt: executionFinishedAt,
38749
+ },
38750
+ ...(touchedProjectNames.length === 0 ? {} : { touchedProjectNames }),
37974
38751
  },
37975
38752
  });
37976
38753
  await commitAnsweredMessageIfEnabled({
@@ -39663,24 +40440,12 @@ const DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS = [
39663
40440
  - @@@
39664
40441
  - Keep in mind the DRY _(don't repeat yourself)_ principle.
39665
40442
  - Do a proper analysis of the current functionality before you start implementing.
39666
- - Add the changes into the [changelog](changelog/_current-preversion.md)
40443
+ - Add the changes into the [changelog](CHANGELOG.md)
40444
+ - Update the [README](README.md) if needed.
40445
+ - Update the [AGENTS.md](AGENTS.md) for the next job to be done if it makes sense.
39667
40446
  `),
39668
40447
  isDefaultProjectTemplate: true,
39669
40448
  },
39670
- {
39671
- id: 'agents-server',
39672
- relativeFilePath: join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'agents-server.md'),
39673
- slugPrefix: 'agents-server',
39674
- content: spaceTrim$1(`
39675
- - @@@
39676
- - Keep in mind the DRY _(don't repeat yourself)_ principle.
39677
- - Do a proper analysis of the current functionality before you start implementing.
39678
- - You are working with the [Agents Server](apps/agents-server)
39679
- - If you need to do the database migration, do it
39680
- - Add the changes into the [changelog](changelog/_current-preversion.md)
39681
- `),
39682
- isDefaultProjectTemplate: false,
39683
- },
39684
40449
  ];
39685
40450
  /**
39686
40451
  * Project-agnostic coder templates that `ptbk coder init` should materialize in any repository.
@@ -40772,6 +41537,10 @@ async function $applyHarnessInstallationStatus(status) {
40772
41537
  console.info(colors.gray(`✔ ${definition.label} ${installedVersion} is up to date.`));
40773
41538
  return;
40774
41539
  }
41540
+ if (installationState === 'installed') {
41541
+ console.info(colors.gray(`✔ ${definition.label} ${installedVersion} is installed. Skipped checking for updates.`));
41542
+ return;
41543
+ }
40775
41544
  if (installationState === 'unknown') {
40776
41545
  console.info(colors.gray(`✔ ${definition.label} ${installedVersion} is installed, the newest version could not be checked.`));
40777
41546
  return;
@@ -40985,20 +41754,20 @@ function comparePrereleaseIdentifier(firstIdentifier, secondIdentifier) {
40985
41754
  // Note: [🟡] Code for CLI npm package version comparison [isNpmPackageVersionOutdated](src/cli/cli-commands/common/npm/isNpmPackageVersionOutdated.ts) should never be published outside of `@promptbook/cli`
40986
41755
 
40987
41756
  /**
40988
- * Detects whether one CLI coding harness is installed globally and whether it is up to date.
41757
+ * Detects whether one CLI coding harness is installed globally and, when enabled, whether it is up to date.
40989
41758
  *
40990
- * Note: `$` is used to indicate that this function is not a pure function - it runs the harness command and queries npm
41759
+ * Note: `$` is used to indicate that this function is not a pure function - it runs the harness command and may query npm
40991
41760
  *
40992
41761
  * @private internal utility of `promptbookCli`
40993
41762
  */
40994
- async function $checkHarnessInstallation(definition) {
41763
+ async function $checkHarnessInstallation(definition, isHarnessUpdateCheckEnabled = true) {
40995
41764
  const [installedVersion, latestVersion] = await Promise.all([
40996
41765
  $resolveInstalledHarnessVersion(definition),
40997
- $resolveLatestNpmPackageVersion(definition.npmPackageName),
41766
+ isHarnessUpdateCheckEnabled ? $resolveLatestNpmPackageVersion(definition.npmPackageName) : Promise.resolve(null),
40998
41767
  ]);
40999
41768
  return {
41000
41769
  definition,
41001
- installationState: resolveHarnessInstallationState(installedVersion, latestVersion),
41770
+ installationState: resolveHarnessInstallationState(installedVersion, latestVersion, isHarnessUpdateCheckEnabled),
41002
41771
  installedVersion,
41003
41772
  latestVersion,
41004
41773
  };
@@ -41006,10 +41775,13 @@ async function $checkHarnessInstallation(definition) {
41006
41775
  /**
41007
41776
  * Derives the installation state from the detected versions.
41008
41777
  */
41009
- function resolveHarnessInstallationState(installedVersion, latestVersion) {
41778
+ function resolveHarnessInstallationState(installedVersion, latestVersion, isHarnessUpdateCheckEnabled) {
41010
41779
  if (installedVersion === null) {
41011
41780
  return 'not-installed';
41012
41781
  }
41782
+ if (!isHarnessUpdateCheckEnabled) {
41783
+ return 'installed';
41784
+ }
41013
41785
  if (latestVersion === null) {
41014
41786
  return 'unknown';
41015
41787
  }
@@ -41020,8 +41792,8 @@ function resolveHarnessInstallationState(installedVersion, latestVersion) {
41020
41792
  // Note: [🟡] Code for CLI harness installation check [$checkHarnessInstallation](src/cli/cli-commands/common/harness/$checkHarnessInstallation.ts) should never be published outside of `@promptbook/cli`
41021
41793
 
41022
41794
  /**
41023
- * Checks that every given CLI coding harness is installed globally and up to date and offers to
41024
- * install or update the ones which are not.
41795
+ * Checks that every given CLI coding harness is installed globally and, when enabled, up to date.
41796
+ * Offers to install missing harnesses and update outdated ones.
41025
41797
  *
41026
41798
  * Harness names which are `undefined`, for example when no `--harness` is selected in a dry run,
41027
41799
  * are ignored so that every call site can pass its raw selection.
@@ -41030,13 +41802,13 @@ function resolveHarnessInstallationState(installedVersion, latestVersion) {
41030
41802
  *
41031
41803
  * @private internal utility of `promptbookCli`
41032
41804
  */
41033
- async function $ensureHarnessInstallations(harnessNames) {
41805
+ async function $ensureHarnessInstallations(harnessNames, isHarnessUpdateCheckEnabled = true) {
41034
41806
  const definitions = resolveCheckedHarnessDefinitions(harnessNames);
41035
41807
  if (definitions.length === 0) {
41036
41808
  return;
41037
41809
  }
41038
41810
  // Note: Detection of all harnesses runs in parallel, the questions have to be asked one by one
41039
- const statuses = await Promise.all(definitions.map((definition) => $checkHarnessInstallation(definition)));
41811
+ const statuses = await Promise.all(definitions.map((definition) => $checkHarnessInstallation(definition, isHarnessUpdateCheckEnabled)));
41040
41812
  for (const status of statuses) {
41041
41813
  await $applyHarnessInstallationStatus(status);
41042
41814
  }
@@ -41050,6 +41822,27 @@ function resolveCheckedHarnessDefinitions(harnessNames) {
41050
41822
  }
41051
41823
  // Note: [🟡] Code for CLI harness installation orchestration [$ensureHarnessInstallations](src/cli/cli-commands/common/harness/$ensureHarnessInstallations.ts) should never be published outside of `@promptbook/cli`
41052
41824
 
41825
+ /**
41826
+ * Registers the shared `--no-harness-update` option on a `ptbk coder` command that uses a coding harness.
41827
+ *
41828
+ * @private internal utility of `promptbookCli`
41829
+ */
41830
+ function addHarnessUpdateOption(command) {
41831
+ command.option('--no-harness-update', 'Skip checking whether globally installed coding harnesses are up to date');
41832
+ }
41833
+ /**
41834
+ * Converts the Commander harness-update flag into the normalized harness-check option.
41835
+ *
41836
+ * @private internal utility of `promptbookCli`
41837
+ */
41838
+ function normalizeHarnessUpdateCliOptions(cliOptions) {
41839
+ return {
41840
+ isHarnessUpdateCheckEnabled: cliOptions.harnessUpdate,
41841
+ };
41842
+ }
41843
+ // Note: [🟡] Code for CLI harness update options [harnessUpdateCliOptions](src/cli/cli-commands/common/harnessUpdateCliOptions.ts) should never be published outside of `@promptbook/cli`
41844
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
41845
+
41053
41846
  /**
41054
41847
  * Relative path to the Promptbook Coder quick-reference file initialized in project roots.
41055
41848
  *
@@ -41612,14 +42405,16 @@ function $initializeCoderInitCommand(program) {
41612
42405
  - CODING_AGENT_GIT_EMAIL
41613
42406
  - CODING_AGENT_GIT_SIGNING_KEY
41614
42407
 
41615
- Checks that the coding harnesses are installed globally and up to date:
42408
+ Checks that the coding harnesses are installed globally and up to date unless \`--no-harness-update\` is used:
41616
42409
  ${block(listCheckedHarnessLabels())}
41617
42410
 
41618
42411
  ${block(CODER_GIT_SYNC_DESCRIPTION)}
41619
42412
  `));
41620
42413
  addCoderGitSyncOptions(command);
42414
+ addHarnessUpdateOption(command);
41621
42415
  command.action(handleActionErrors(async (cliOptions) => {
41622
42416
  const gitSync = normalizeCoderGitSyncCliOptions(cliOptions);
42417
+ const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
41623
42418
  const projectPath = process.cwd();
41624
42419
  // Note: Import the git synchronization dynamically to keep the CLI fast for runs without `--commit`
41625
42420
  const { $commitCoderChanges, $startCoderGitSync } = await Promise.resolve().then(function () { return coderGitSync; });
@@ -41632,7 +42427,7 @@ function $initializeCoderInitCommand(program) {
41632
42427
  commitScope,
41633
42428
  commitMessage: 'Initialize Promptbook Coder',
41634
42429
  });
41635
- await $ensureHarnessInstallations(CODER_INIT_CHECKED_HARNESS_NAMES);
42430
+ await $ensureHarnessInstallations(CODER_INIT_CHECKED_HARNESS_NAMES, isHarnessUpdateCheckEnabled);
41636
42431
  }));
41637
42432
  }
41638
42433
  /**
@@ -41671,13 +42466,16 @@ function $initializeCoderPingCommand(program) {
41671
42466
  - Reports the answer of the harness, the response time and the reported usage
41672
42467
  - Starts the hourly/weekly quota window before you need it, so it is already refreshing when you do
41673
42468
  - Leaves the project exactly as it was — nothing is read, written, changed or committed
42469
+ - Checks that the selected harness is installed globally and up to date unless --no-harness-update is used
41674
42470
  - Use --no-ui to stream the raw harness output instead of only the compact result
41675
42471
  `));
41676
42472
  addPromptRunnerSelectionOptions(command);
42473
+ addHarnessUpdateOption(command);
41677
42474
  addPromptRunnerRuntimeOptions(command);
41678
42475
  command.action(handleActionErrors(async (cliOptions) => {
41679
42476
  const runnerOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, { isAgentRequired: true });
41680
- await $ensureHarnessInstallations([runnerOptions.agentName]);
42477
+ const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
42478
+ await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
41681
42479
  // Note: Import the ping dynamically to avoid loading heavy dependencies until needed
41682
42480
  const { pingCoderHarness } = await Promise.resolve().then(function () { return pingCoderHarness$1; });
41683
42481
  const { printCoderPingResult } = await Promise.resolve().then(function () { return printCoderPingResult$1; });
@@ -42168,7 +42966,7 @@ function $initializeCoderRunCommand(program) {
42168
42966
  - Optional --isolate runs every prompt in its own temporary git worktree and merges it back when verified
42169
42967
  - Optional --preserve-logs keeps temp prompt/log artifacts after successful rounds
42170
42968
  - Optional --no-ui keeps plain streaming console output for logging and debugging
42171
- - Checks that the selected harness is installed globally and up to date before the first prompt
42969
+ - Checks that the selected harness is installed globally and up to date before the first prompt unless --no-harness-update is used
42172
42970
  - In interactive mode, checks local and global Promptbook CLI installations and offers to update them
42173
42971
  - Supports GPG signing of commits
42174
42972
  - Optional pre-coding test run that can stop or repair pre-existing failures
@@ -42178,6 +42976,7 @@ function $initializeCoderRunCommand(program) {
42178
42976
  `));
42179
42977
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
42180
42978
  addPromptRunnerSelectionOptions(command);
42979
+ addHarnessUpdateOption(command);
42181
42980
  command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
42182
42981
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
42183
42982
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
@@ -42219,6 +43018,7 @@ function $initializeCoderRunCommand(program) {
42219
43018
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
42220
43019
  isAgentRequired: !dryRun,
42221
43020
  });
43021
+ const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
42222
43022
  // [1] Parse the wait options and --no-auto:
42223
43023
  // default: run automatically through the queue (no waiting between prompts)
42224
43024
  // --no-auto: wait for user confirmation before each prompt (interactive mode)
@@ -42229,7 +43029,7 @@ function $initializeCoderRunCommand(program) {
42229
43029
  if (await $ensurePromptbookCliInstallations()) {
42230
43030
  return process.exit(0);
42231
43031
  }
42232
- await $ensureHarnessInstallations([runnerOptions.agentName]);
43032
+ await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
42233
43033
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
42234
43034
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
42235
43035
  const waitAfterError = parseOptionalWaitDuration(waitAfterErrorValue, DEFAULT_WAIT_AFTER_ERROR_MS$1);
@@ -42323,7 +43123,7 @@ function $initializeCoderServerCommand(program) {
42323
43123
 
42324
43124
  Features:
42325
43125
  - Runs the same prompt processing as \`ptbk coder run\`
42326
- - Checks that the selected harness is installed globally and up to date on startup
43126
+ - Checks that the selected harness is installed globally and up to date on startup unless --no-harness-update is used
42327
43127
  - Does not exit when all prompts are done; polls for new prompt files instead
42328
43128
  - Serves a kanban board at http://localhost:<port> for visual progress tracking
42329
43129
  - Allows editing prompt files directly from the browser (Trello-style)
@@ -42335,6 +43135,7 @@ function $initializeCoderServerCommand(program) {
42335
43135
  .default(DEFAULT_CODER_SERVER_PORT));
42336
43136
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
42337
43137
  addPromptRunnerSelectionOptions(command);
43138
+ addHarnessUpdateOption(command);
42338
43139
  command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
42339
43140
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
42340
43141
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
@@ -42366,7 +43167,8 @@ function $initializeCoderServerCommand(program) {
42366
43167
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
42367
43168
  isAgentRequired: !dryRun,
42368
43169
  });
42369
- await $ensureHarnessInstallations([runnerOptions.agentName]);
43170
+ const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
43171
+ await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
42370
43172
  // [1] Parse the wait options and --no-auto (same logic as `coder run`)
42371
43173
  const waitForUser = !auto;
42372
43174
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
@@ -44290,23 +45092,27 @@ async function locateAppOnMacOs({ macOsName, }) {
44290
45092
  // TODO: [🧠][♿] Maybe export through `@promptbook/node`
44291
45093
 
44292
45094
  /**
44293
- * Attempts to locate the specified application on a Windows system by searching common installation directories.
45095
+ * Attempts to locate the specified application on a Windows system by searching PATH and common installation directories.
44294
45096
  * Returns the path to the executable if found, or null otherwise.
44295
45097
  *
44296
45098
  * @private within the repository
44297
45099
  */
44298
45100
  async function locateAppOnWindows({ appName, windowsSuffix, }) {
44299
45101
  try {
44300
- const prefixes = [
45102
+ const EXECUTABLE_FILENAME = windowsSuffix.replace(/^.*[\\/]/, '');
45103
+ const PATH_EXECUTABLE_PATHS = (process.env.PATH || '')
45104
+ .split(delimiter)
45105
+ .filter(Boolean)
45106
+ .map((pathPrefix) => join(pathPrefix, EXECUTABLE_FILENAME));
45107
+ const INSTALLATION_EXECUTABLE_PATHS = [
44301
45108
  process.env.LOCALAPPDATA,
44302
45109
  join(process.env.LOCALAPPDATA || '', 'Programs'),
44303
45110
  process.env.PROGRAMFILES,
44304
45111
  process.env['PROGRAMFILES(X86)'],
44305
- ];
44306
- for (const prefix of prefixes) {
44307
- const path = prefix + windowsSuffix;
44308
- if (await isExecutable(path, $provideFilesystemForNode())) {
44309
- return path;
45112
+ ].map((pathPrefix) => pathPrefix + windowsSuffix);
45113
+ for (const executablePath of [...PATH_EXECUTABLE_PATHS, ...INSTALLATION_EXECUTABLE_PATHS]) {
45114
+ if (await isExecutable(executablePath, $provideFilesystemForNode())) {
45115
+ return executablePath;
44310
45116
  }
44311
45117
  }
44312
45118
  throw new Error(`Can not locate app ${appName} on Windows.`);
@@ -75699,6 +76505,10 @@ async function runTestBefore(options) {
75699
76505
  * Constant for prompts dir.
75700
76506
  */
75701
76507
  const PROMPTS_DIR$1 = join(process.cwd(), 'prompts');
76508
+ /**
76509
+ * Commit message for files changed by a successful or failed pre-coding test in repair mode.
76510
+ */
76511
+ const PRE_CODING_TEST_CHANGES_COMMIT_MESSAGE = 'test: Apply changes made by pre-coding tests';
75702
76512
  /**
75703
76513
  * Main entry point for running prompts with the selected agent.
75704
76514
  *
@@ -76022,6 +76832,7 @@ async function runTestBeforeIfNeeded(options) {
76022
76832
  });
76023
76833
  await ensureWorkingTreeClean();
76024
76834
  }
76835
+ const testBeforeCommitScope = await captureTestBeforeCommitScopeIfNeeded(runOptions);
76025
76836
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
76026
76837
  const testBeforeResult = await runTestBefore({
76027
76838
  testCommand: runOptions.testCommand,
@@ -76030,6 +76841,11 @@ async function runTestBeforeIfNeeded(options) {
76030
76841
  }).finally(() => {
76031
76842
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
76032
76843
  });
76844
+ await commitTestBeforeChangesIfNeeded({
76845
+ runOptions,
76846
+ testBeforeCommitScope,
76847
+ waitForRequestedPause,
76848
+ });
76033
76849
  if (testBeforeResult.isPassed) {
76034
76850
  return hasWaitedForStart;
76035
76851
  }
@@ -76078,6 +76894,38 @@ async function runTestBeforeIfNeeded(options) {
76078
76894
  });
76079
76895
  return updatedHasWaitedForStart;
76080
76896
  }
76897
+ /**
76898
+ * Captures the files present before a `yes-and-fix` pre-coding test, so only changes made by that test can be committed.
76899
+ */
76900
+ async function captureTestBeforeCommitScopeIfNeeded(runOptions) {
76901
+ if (runOptions.testBefore !== 'yes-and-fix' || runOptions.noCommit) {
76902
+ return undefined;
76903
+ }
76904
+ return captureCoderCommitScope(process.cwd());
76905
+ }
76906
+ /**
76907
+ * Commits files changed by a `yes-and-fix` pre-coding test before the coder continues to a queued or repair prompt.
76908
+ */
76909
+ async function commitTestBeforeChangesIfNeeded(options) {
76910
+ const { runOptions, testBeforeCommitScope, waitForRequestedPause } = options;
76911
+ if (!testBeforeCommitScope) {
76912
+ return;
76913
+ }
76914
+ const relevantPaths = await resolveCoderCommitScopePaths(testBeforeCommitScope);
76915
+ if (relevantPaths.length === 0) {
76916
+ return;
76917
+ }
76918
+ await waitForRequestedPause({
76919
+ checkpointLabel: 'committing changes made by pre-coding tests',
76920
+ phase: 'verifying',
76921
+ statusMessage: 'Committing changes made by pre-coding tests...',
76922
+ });
76923
+ await commitChanges(PRE_CODING_TEST_CHANGES_COMMIT_MESSAGE, {
76924
+ autoPush: runOptions.autoPush,
76925
+ projectPath: testBeforeCommitScope.projectPath,
76926
+ relevantPaths,
76927
+ });
76928
+ }
76081
76929
  /**
76082
76930
  * Creates a pause waiter that keeps the progress display and rich UI in sync.
76083
76931
  */