@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/umd/index.umd.js CHANGED
@@ -58,7 +58,7 @@
58
58
  * @generated
59
59
  * @see https://github.com/webgptorg/promptbook
60
60
  */
61
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-11';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-13';
62
62
  /**
63
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
64
64
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -4098,10 +4098,6 @@
4098
4098
  * Environment variable containing the internal worker token for local agent runners.
4099
4099
  */
4100
4100
  const PTBK_AGENTS_SERVER_USER_CHAT_WORKER_TOKEN_ENV = 'PTBK_AGENTS_SERVER_USER_CHAT_WORKER_TOKEN';
4101
- /**
4102
- * Header used by internal local-runner routes.
4103
- */
4104
- const USER_CHAT_WORKER_TOKEN_HEADER = 'x-user-chat-worker-token';
4105
4101
 
4106
4102
  /**
4107
4103
  * Format either small or big number
@@ -20317,6 +20313,30 @@
20317
20313
  // TODO: [🧠] Maybe create through standardized $register
20318
20314
  // Note: [💞] Ignore a discrepancy between file name and entity name
20319
20315
 
20316
+ /**
20317
+ * Pattern shared by Book parsing and editor highlighting for a commitment-like line.
20318
+ *
20319
+ * A Book commitment keyword consists of one or more all-uppercase words with at least
20320
+ * two characters each. This deliberately mirrors the Book editor syntax rule so an
20321
+ * unknown keyword starts a new source block everywhere it is interpreted.
20322
+ *
20323
+ * @private internal constant of `getBookCommitmentLineType`
20324
+ */
20325
+ const BOOK_COMMITMENT_LINE_TYPE_REGEX = /^\s*(?<type>[A-Z][A-Z0-9]+(?:\s+[A-Z][A-Z0-9]+)*)(?=\s|$)/;
20326
+ /**
20327
+ * Extracts the uppercase commitment type from a Book line, including types not registered by Promptbook.
20328
+ *
20329
+ * @param line - One raw Book source line.
20330
+ * @returns Matched commitment type or `null` when the line does not begin with commitment syntax.
20331
+ *
20332
+ * @private internal utility of Book parsing and `<BookEditor/>`
20333
+ */
20334
+ function getBookCommitmentLineType(line) {
20335
+ var _a;
20336
+ const match = BOOK_COMMITMENT_LINE_TYPE_REGEX.exec(line);
20337
+ return ((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.type) || null;
20338
+ }
20339
+
20320
20340
  /**
20321
20341
  * Consumes the agent-name prelude from raw source.
20322
20342
  *
@@ -20374,13 +20394,15 @@
20374
20394
  return {
20375
20395
  agentName: null,
20376
20396
  commitments: [],
20397
+ unknownCommitments: [],
20377
20398
  nonCommitmentLines: [],
20378
20399
  };
20379
20400
  }
20380
20401
  const { lines, agentName, agentNameLineIndex, agentNameLineNumber } = parseAgentSourcePrelude(agentSource);
20381
20402
  const commitments = [];
20403
+ const unknownCommitments = [];
20382
20404
  const nonCommitmentLines = agentNameLineIndex >= 0 ? [lines[agentNameLineIndex]] : [];
20383
- // Parse commitments with multiline support
20405
+ // Parse commitments with multiline support.
20384
20406
  let currentCommitment = null;
20385
20407
  // Process lines starting from after the agent name line
20386
20408
  const startIndex = agentNameLineIndex >= 0 ? agentNameLineIndex + 1 : 0;
@@ -20394,25 +20416,11 @@
20394
20416
  // Check if this line starts or ends a code block
20395
20417
  if (trimmedLine.startsWith('```')) {
20396
20418
  isInsideCodeBlock = !isInsideCodeBlock;
20397
- if (currentCommitment) {
20398
- // If we are inside a commitment, the code block is part of it
20399
- currentCommitment.contentLines.push(line);
20400
- }
20401
- else {
20402
- // If we are not inside a commitment, the code block is non-commitment
20403
- nonCommitmentLines.push(line);
20404
- }
20419
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
20405
20420
  continue;
20406
20421
  }
20407
20422
  if (isInsideCodeBlock) {
20408
- if (currentCommitment) {
20409
- // If we are inside a commitment and a code block, the line is part of the commitment
20410
- currentCommitment.contentLines.push(line);
20411
- }
20412
- else {
20413
- // If we are inside a code block but not a commitment, the line is non-commitment
20414
- nonCommitmentLines.push(line);
20415
- }
20423
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
20416
20424
  continue;
20417
20425
  }
20418
20426
  // Check if this line starts a new commitment
@@ -20421,22 +20429,14 @@
20421
20429
  const typeRegex = definition.createTypeRegex();
20422
20430
  const match = typeRegex.exec(line.trim());
20423
20431
  if (match && ((_a = match.groups) === null || _a === void 0 ? void 0 : _a.type)) {
20424
- // Save the previous commitment if it exists
20425
- if (currentCommitment) {
20426
- const fullContent = currentCommitment.contentLines.join('\n');
20427
- commitments.push({
20428
- type: currentCommitment.type,
20429
- content: _spaceTrim.spaceTrim(fullContent),
20430
- originalLine: currentCommitment.originalStartLine,
20431
- lineNumber: currentCommitment.startLineNumber,
20432
- });
20433
- }
20432
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
20434
20433
  // Extract the initial content from the commitment line
20435
20434
  const fullRegex = definition.createRegex();
20436
20435
  const fullMatch = fullRegex.exec(line.trim());
20437
20436
  const initialContent = ((_b = fullMatch === null || fullMatch === void 0 ? void 0 : fullMatch.groups) === null || _b === void 0 ? void 0 : _b.contents) || '';
20438
20437
  // Start a new commitment
20439
20438
  currentCommitment = {
20439
+ kind: 'known',
20440
20440
  type: definition.type,
20441
20441
  startLineNumber: i + 1,
20442
20442
  originalStartLine: line,
@@ -20446,52 +20446,90 @@
20446
20446
  break;
20447
20447
  }
20448
20448
  }
20449
+ if (foundNewCommitment) {
20450
+ continue;
20451
+ }
20452
+ const unknownCommitmentType = getBookCommitmentLineType(line);
20453
+ if (unknownCommitmentType) {
20454
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
20455
+ currentCommitment = {
20456
+ kind: 'unknown',
20457
+ type: unknownCommitmentType,
20458
+ startLineNumber: i + 1,
20459
+ originalStartLine: line,
20460
+ sourceLines: [line],
20461
+ };
20462
+ continue;
20463
+ }
20449
20464
  // Check if this is a horizontal line (ends any current commitment)
20450
20465
  const isHorizontalLine = HORIZONTAL_LINE_PATTERN$1.test(line);
20451
20466
  if (isHorizontalLine) {
20452
- // Save the current commitment if it exists
20453
- if (currentCommitment) {
20454
- const fullContent = currentCommitment.contentLines.join('\n');
20455
- commitments.push({
20456
- type: currentCommitment.type,
20457
- content: _spaceTrim.spaceTrim(fullContent),
20458
- originalLine: currentCommitment.originalStartLine,
20459
- lineNumber: currentCommitment.startLineNumber,
20460
- });
20461
- currentCommitment = null;
20462
- }
20467
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
20468
+ currentCommitment = null;
20463
20469
  // Add horizontal line to non-commitment lines
20464
20470
  nonCommitmentLines.push(line);
20465
20471
  continue;
20466
20472
  }
20467
- if (!foundNewCommitment) {
20468
- if (currentCommitment) {
20469
- // This line belongs to the current commitment
20470
- currentCommitment.contentLines.push(line);
20471
- }
20472
- else {
20473
- // This line is not part of any commitment
20474
- nonCommitmentLines.push(line);
20475
- }
20476
- }
20477
- }
20478
- // Don't forget to save the last commitment if it exists
20479
- if (currentCommitment) {
20480
- const fullContent = currentCommitment.contentLines.join('\n');
20481
- commitments.push({
20482
- type: currentCommitment.type,
20483
- content: _spaceTrim.spaceTrim(fullContent),
20484
- originalLine: currentCommitment.originalStartLine,
20485
- lineNumber: currentCommitment.startLineNumber,
20486
- });
20473
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
20487
20474
  }
20475
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
20488
20476
  return {
20489
20477
  agentName,
20490
20478
  agentNameLineNumber,
20491
20479
  commitments,
20480
+ unknownCommitments,
20492
20481
  nonCommitmentLines,
20493
20482
  };
20494
20483
  }
20484
+ /**
20485
+ * Stores the current known or unknown commitment in its appropriate parsed collection.
20486
+ *
20487
+ * @param currentCommitment - Commitment-like block collected so far.
20488
+ * @param commitments - Parsed registered commitments to append to.
20489
+ * @param unknownCommitments - Parsed unregistered commitments to append to.
20490
+ *
20491
+ * @private internal utility of `parseAgentSourceWithCommitments`
20492
+ */
20493
+ function appendCurrentCommitment(currentCommitment, commitments, unknownCommitments) {
20494
+ if (!currentCommitment) {
20495
+ return;
20496
+ }
20497
+ if (currentCommitment.kind === 'unknown') {
20498
+ unknownCommitments.push({
20499
+ type: currentCommitment.type,
20500
+ originalLine: currentCommitment.originalStartLine,
20501
+ lineNumber: currentCommitment.startLineNumber,
20502
+ source: _spaceTrim.spaceTrim(currentCommitment.sourceLines.join('\n')),
20503
+ });
20504
+ return;
20505
+ }
20506
+ commitments.push({
20507
+ type: currentCommitment.type,
20508
+ content: _spaceTrim.spaceTrim(currentCommitment.contentLines.join('\n')),
20509
+ originalLine: currentCommitment.originalStartLine,
20510
+ lineNumber: currentCommitment.startLineNumber,
20511
+ });
20512
+ }
20513
+ /**
20514
+ * Adds one source line to the active commitment-like block, or to plain source content when none is active.
20515
+ *
20516
+ * @param currentCommitment - Commitment-like block currently being collected.
20517
+ * @param line - Source line to append.
20518
+ * @param nonCommitmentLines - Plain source lines to append to when no block is active.
20519
+ *
20520
+ * @private internal utility of `parseAgentSourceWithCommitments`
20521
+ */
20522
+ function appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines) {
20523
+ if (!currentCommitment) {
20524
+ nonCommitmentLines.push(line);
20525
+ return;
20526
+ }
20527
+ if (currentCommitment.kind === 'unknown') {
20528
+ currentCommitment.sourceLines.push(line);
20529
+ return;
20530
+ }
20531
+ currentCommitment.contentLines.push(line);
20532
+ }
20495
20533
 
20496
20534
  /**
20497
20535
  * Parses parameters from text using both supported notations:
@@ -21850,7 +21888,7 @@
21850
21888
  return agentBookStats.isFile();
21851
21889
  }
21852
21890
  catch (error) {
21853
- if (isFileNotFoundError$6(error)) {
21891
+ if (isFileNotFoundError$7(error)) {
21854
21892
  return false;
21855
21893
  }
21856
21894
  throw error;
@@ -21859,7 +21897,7 @@
21859
21897
  /**
21860
21898
  * Returns true when one filesystem error indicates a missing path.
21861
21899
  */
21862
- function isFileNotFoundError$6(error) {
21900
+ function isFileNotFoundError$7(error) {
21863
21901
  return Boolean(error &&
21864
21902
  typeof error === 'object' &&
21865
21903
  'code' in error &&
@@ -21889,7 +21927,7 @@
21889
21927
  return await promises.readdir(queuedMessagesDirectoryPath, { withFileTypes: true });
21890
21928
  }
21891
21929
  catch (error) {
21892
- if (isFileNotFoundError$5(error)) {
21930
+ if (isFileNotFoundError$6(error)) {
21893
21931
  return [];
21894
21932
  }
21895
21933
  throw error;
@@ -21910,7 +21948,7 @@
21910
21948
  /**
21911
21949
  * Returns true when an error is a missing-path filesystem error.
21912
21950
  */
21913
- function isFileNotFoundError$5(error) {
21951
+ function isFileNotFoundError$6(error) {
21914
21952
  return Boolean(error &&
21915
21953
  typeof error === 'object' &&
21916
21954
  'code' in error &&
@@ -22388,7 +22426,7 @@
22388
22426
  lastIndexLockState = await readGitIndexLockState(cwd, options.env);
22389
22427
  if ((lastIndexLockState === null || lastIndexLockState === void 0 ? void 0 : lastIndexLockState.isStale) && !isStaleIndexLockRemoved) {
22390
22428
  await promises.unlink(lastIndexLockState.path).catch((unlinkError) => {
22391
- if (isFileNotFoundError$4(unlinkError)) {
22429
+ if (isFileNotFoundError$5(unlinkError)) {
22392
22430
  return;
22393
22431
  }
22394
22432
  throw unlinkError;
@@ -22449,7 +22487,7 @@
22449
22487
  };
22450
22488
  }
22451
22489
  catch (error) {
22452
- if (isFileNotFoundError$4(error)) {
22490
+ if (isFileNotFoundError$5(error)) {
22453
22491
  return undefined;
22454
22492
  }
22455
22493
  throw error;
@@ -22506,7 +22544,7 @@
22506
22544
  /**
22507
22545
  * Detects missing-file errors while inspecting the lock file.
22508
22546
  */
22509
- function isFileNotFoundError$4(error) {
22547
+ function isFileNotFoundError$5(error) {
22510
22548
  return Boolean(error &&
22511
22549
  typeof error === 'object' &&
22512
22550
  'code' in error &&
@@ -24761,14 +24799,90 @@
24761
24799
  * without the generated script and the prompt it embeds.
24762
24800
  */
24763
24801
  const SCRIPT_EXECUTION_LOG_RAW_OUTPUT_MARKER = '--- raw output ---';
24802
+ /**
24803
+ * Command which terminates the Bash process tree rooted at the running harness.
24804
+ *
24805
+ * Bash's POSIX process IDs differ from Windows process IDs in Git Bash, so the Windows branch resolves the native PID
24806
+ * before delegating to `taskkill`. Unix harnesses run in a dedicated Bash job process group, which can be terminated
24807
+ * with its negative process ID.
24808
+ */
24809
+ const TERMINATE_BASH_PROCESS_TREE_COMMAND = process.platform === 'win32'
24810
+ ? _spaceTrim.spaceTrim(`
24811
+ HARNESS_WINDOWS_PROCESS_ID="$(ps -l -p "$HARNESS_PROCESS_ID" | awk 'NR == 2 { print $4 }')"
24812
+ if [ -n "$HARNESS_WINDOWS_PROCESS_ID" ]; then
24813
+ MSYS_NO_PATHCONV=1 taskkill.exe /PID "$HARNESS_WINDOWS_PROCESS_ID" /T /F > /dev/null 2>&1 || true
24814
+ fi
24815
+ `)
24816
+ : 'kill -TERM -- "-$HARNESS_PROCESS_ID" 2>/dev/null || true';
24817
+ /**
24818
+ * Shell condition that detects whether the Node process which owns the harness is still running.
24819
+ *
24820
+ * Git Bash translates process IDs and command switches, so querying the native Windows PID needs both `tasklist` and
24821
+ * disabled MSYS path conversion there. Unix can use the native `kill -0` process existence check.
24822
+ */
24823
+ const IS_PARENT_PROCESS_RUNNING_CONDITION = process.platform === 'win32'
24824
+ ? '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 }\''
24825
+ : 'kill -0 "$PARENT_CODER_PROCESS_ID" 2>/dev/null';
24826
+ /**
24827
+ * Environment variable that identifies the Node process responsible for a temporary harness shell.
24828
+ */
24829
+ const PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME = 'PTBK_CODER_PARENT_PROCESS_ID';
24764
24830
  /**
24765
24831
  * Small bash wrapper that preserves stdout/stderr streams while teeing both into the runtime log file.
24832
+ *
24833
+ * A watcher polls the owning Node process by its PID. If that process exits abruptly, the watcher stops the whole
24834
+ * Bash and harness process tree instead of letting it continue as an orphan.
24766
24835
  */
24767
24836
  const LOGGED_BASH_WRAPPER_COMMAND = _spaceTrim.spaceTrim(`
24837
+ PARENT_CODER_PROCESS_ID="\${${PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME}}"
24838
+ unset ${PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME}
24839
+
24840
+ terminate_harness_process_tree() {
24841
+ # Keep the EXIT cleanup intact so the wrapper can also stop its parent-process watcher.
24842
+ trap - HUP INT TERM
24843
+ ${TERMINATE_BASH_PROCESS_TREE_COMMAND}
24844
+ }
24845
+
24846
+ is_parent_process_running() {
24847
+ ${IS_PARENT_PROCESS_RUNNING_CONDITION}
24848
+ }
24849
+
24768
24850
  if [ -n "\${${PTBK_CODER_LOG_FILE_ENV_NAME}:-}" ]; then
24769
- exec > >(tee -a "\$${PTBK_CODER_LOG_FILE_ENV_NAME}") 2> >(tee -a "\$${PTBK_CODER_LOG_FILE_ENV_NAME}" >&2)
24851
+ exec > >(tee -a "$${PTBK_CODER_LOG_FILE_ENV_NAME}") 2> >(tee -a "$${PTBK_CODER_LOG_FILE_ENV_NAME}" >&2)
24770
24852
  fi
24771
- bash "$1"
24853
+
24854
+ watch_parent_process() {
24855
+ trap 'exit 0' HUP INT TERM
24856
+
24857
+ while is_parent_process_running; do
24858
+ sleep 1
24859
+ done
24860
+
24861
+ terminate_harness_process_tree
24862
+ }
24863
+
24864
+ # Background harness jobs need their own process group on Unix so termination cannot reach the parent coder.
24865
+ set -m
24866
+ if ! is_parent_process_running; then
24867
+ exit 1
24868
+ fi
24869
+ bash "$1" &
24870
+ HARNESS_PROCESS_ID=$!
24871
+
24872
+ cleanup_parent_process_watcher() {
24873
+ kill "$PARENT_PROCESS_WATCHER_PID" 2>/dev/null || true
24874
+ wait "$PARENT_PROCESS_WATCHER_PID" 2>/dev/null || true
24875
+ }
24876
+
24877
+ watch_parent_process &
24878
+ PARENT_PROCESS_WATCHER_PID=$!
24879
+
24880
+ trap cleanup_parent_process_watcher EXIT
24881
+ trap terminate_harness_process_tree HUP INT TERM
24882
+
24883
+ wait "$HARNESS_PROCESS_ID"
24884
+ SCRIPT_EXIT_CODE=$?
24885
+ exit "$SCRIPT_EXIT_CODE"
24772
24886
  `);
24773
24887
  /**
24774
24888
  * Shapes one bash invocation that optionally mirrors live script output into a temporary log file.
@@ -24824,6 +24938,38 @@
24824
24938
  return scriptPath.toLowerCase().endsWith('.test.sh') ? 'test shell' : 'runner shell';
24825
24939
  }
24826
24940
 
24941
+ /**
24942
+ * Standard streams used by every temporary Bash runner.
24943
+ */
24944
+ const BASH_PROCESS_STDIO = ['pipe', 'pipe', 'pipe'];
24945
+ /**
24946
+ * Whether the current Node process is running on Windows.
24947
+ */
24948
+ const IS_WINDOWS$1 = process.platform === 'win32';
24949
+ /**
24950
+ * Starts one temporary Bash script in a process tree owned by the current Node process.
24951
+ *
24952
+ * The wrapper watches the supplied owning process ID. When that process exits abruptly, the wrapper terminates every
24953
+ * nested shell and harness process instead of leaving it orphaned.
24954
+ *
24955
+ * @private internal utility of the coding prompt runner
24956
+ */
24957
+ function $spawnLoggedBashScript(options) {
24958
+ var _a;
24959
+ const bashExecution = buildLoggedBashExecution(options.scriptPath, options.logPath);
24960
+ const parentProcessId = (_a = options.parentProcessId) !== null && _a !== void 0 ? _a : process.pid;
24961
+ return child_process.spawn('bash', bashExecution.args, {
24962
+ detached: !IS_WINDOWS$1,
24963
+ env: {
24964
+ ...process.env,
24965
+ ...bashExecution.env,
24966
+ [PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME]: parentProcessId.toString(),
24967
+ },
24968
+ stdio: BASH_PROCESS_STDIO,
24969
+ windowsHide: true,
24970
+ });
24971
+ }
24972
+
24827
24973
  /**
24828
24974
  * Prints one live shell output chunk to the terminal when console mirroring is enabled.
24829
24975
  */
@@ -24846,12 +24992,12 @@
24846
24992
  async function runBashScriptWithOutput(options) {
24847
24993
  var _a;
24848
24994
  await appendScriptExecutionLogStart(options);
24849
- const bashExecution = buildLoggedBashExecution(options.scriptPath, options.logPath);
24850
24995
  const scriptPathPosix = toPosixPath(options.scriptPath);
24851
24996
  const shouldPrintLiveOutput = (_a = options.shouldPrintLiveOutput) !== null && _a !== void 0 ? _a : true;
24852
24997
  return await new Promise((resolve, reject) => {
24853
- const commandProcess = child_process.spawn('bash', bashExecution.args, {
24854
- env: bashExecution.env ? { ...process.env, ...bashExecution.env } : process.env,
24998
+ const commandProcess = $spawnLoggedBashScript({
24999
+ scriptPath: options.scriptPath,
25000
+ logPath: options.logPath,
24855
25001
  });
24856
25002
  let output = '';
24857
25003
  let settled = false;
@@ -24910,8 +25056,9 @@
24910
25056
  const failure = new Error(_spaceTrim.spaceTrim(output) || `Command "bash ${scriptPathPosix}" exited with code ${code}`);
24911
25057
  settleWithLog(`failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}`, () => reject(failure), failure);
24912
25058
  };
25059
+ // Wait for `close`, not only `exit`, because the Bash wrapper can still be flushing its tee process
25060
+ // substitutions after the direct shell exits.
24913
25061
  commandProcess.on('close', handleExit);
24914
- commandProcess.on('exit', handleExit);
24915
25062
  commandProcess.on('disconnect', () => {
24916
25063
  const failure = new Error(`Command "bash ${scriptPathPosix}" disconnected`);
24917
25064
  settleWithLog('failed after disconnect', () => reject(failure), failure);
@@ -25772,6 +25919,35 @@
25772
25919
  error.message.includes('bin/node'));
25773
25920
  }
25774
25921
 
25922
+ /**
25923
+ * Whether the current coder process runs on Windows.
25924
+ */
25925
+ const IS_WINDOWS = process.platform === 'win32';
25926
+ /**
25927
+ * Stops one active temporary Bash shell together with the harness process tree it owns.
25928
+ *
25929
+ * Unix sends `SIGTERM` to the wrapper so its shell trap terminates the separately grouped harness job. Windows does
25930
+ * not reliably deliver that signal to Bash, so the native `taskkill` command terminates the direct shell tree instead.
25931
+ *
25932
+ * @private internal utility of the coding prompt runner
25933
+ */
25934
+ function $terminateLoggedBashProcessTree(commandProcess) {
25935
+ if (commandProcess.exitCode !== null || commandProcess.signalCode !== null) {
25936
+ return;
25937
+ }
25938
+ if (!IS_WINDOWS) {
25939
+ commandProcess.kill('SIGTERM');
25940
+ return;
25941
+ }
25942
+ if (!commandProcess.pid) {
25943
+ return;
25944
+ }
25945
+ child_process.spawnSync('taskkill.exe', ['/PID', commandProcess.pid.toString(), '/T', '/F'], {
25946
+ stdio: 'ignore',
25947
+ windowsHide: true,
25948
+ });
25949
+ }
25950
+
25775
25951
  /**
25776
25952
  * Maximum number of output characters included in thrown error messages.
25777
25953
  */
@@ -25814,15 +25990,16 @@
25814
25990
  const scriptPathPosix = toPosixPath(scriptPath);
25815
25991
  const shouldPrintLiveOutput = (_a = options.shouldPrintLiveOutput) !== null && _a !== void 0 ? _a : true;
25816
25992
  await appendScriptExecutionLogStart(options);
25817
- const bashExecution = buildLoggedBashExecution(scriptPath, options.logPath);
25818
25993
  return await new Promise((resolve, reject) => {
25819
- const commandProcess = child_process.spawn('bash', bashExecution.args, {
25820
- env: bashExecution.env ? { ...process.env, ...bashExecution.env } : process.env,
25994
+ const commandProcess = $spawnLoggedBashScript({
25995
+ scriptPath,
25996
+ logPath: options.logPath,
25821
25997
  });
25822
25998
  let stdoutBuffer = '';
25823
25999
  let stderrBuffer = '';
25824
26000
  let fullOutput = '';
25825
26001
  let markerSeen = false;
26002
+ let isCompletedAfterIdleTimeout = false;
25826
26003
  let idleTimer;
25827
26004
  let settled = false;
25828
26005
  let isSettling = false;
@@ -25864,15 +26041,16 @@
25864
26041
  });
25865
26042
  };
25866
26043
  /**
25867
- * Finishes a marker-completed run and then asks bash to stop.
26044
+ * Stops a marker-completed process tree after its trailing output becomes idle.
25868
26045
  */
25869
26046
  const finishAfterIdleTimeout = () => {
25870
- settleWithLog('completed after idle timeout', () => resolve(fullOutput));
26047
+ isCompletedAfterIdleTimeout = true;
26048
+ idleTimer = undefined;
25871
26049
  try {
25872
- commandProcess.kill();
26050
+ $terminateLoggedBashProcessTree(commandProcess);
25873
26051
  }
25874
26052
  catch (_a) {
25875
- // Windows can report EPERM when bash exits before the idle timer fires.
26053
+ // The close handler below still settles normally when Bash already exited before the idle timer fired.
25876
26054
  }
25877
26055
  };
25878
26056
  /**
@@ -25931,7 +26109,11 @@
25931
26109
  const failure = code === 0 || markerSeen
25932
26110
  ? undefined
25933
26111
  : new Error(buildCommandFailureMessage(scriptPathPosix, code, fullOutput));
25934
- const status = failure ? `failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}` : 'succeeded';
26112
+ const status = isCompletedAfterIdleTimeout
26113
+ ? 'completed after idle timeout'
26114
+ : failure
26115
+ ? `failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}`
26116
+ : 'succeeded';
25935
26117
  settleWithLog(status, () => {
25936
26118
  if (!failure) {
25937
26119
  resolve(fullOutput);
@@ -25940,8 +26122,9 @@
25940
26122
  reject(failure);
25941
26123
  });
25942
26124
  };
26125
+ // Wait for `close`, not only `exit`, because the Bash wrapper can still be flushing its tee process
26126
+ // substitutions after the direct shell exits.
25943
26127
  commandProcess.on('close', handleExit);
25944
- commandProcess.on('exit', handleExit);
25945
26128
  commandProcess.on('disconnect', () => {
25946
26129
  const failure = new Error(buildCommandFailureMessage(scriptPathPosix, null, fullOutput));
25947
26130
  settleWithLog('failed after disconnect', () => reject(failure), failure);
@@ -29437,7 +29620,7 @@
29437
29620
  return crypto.createHash('sha1').update(content).digest('hex');
29438
29621
  }
29439
29622
  catch (error) {
29440
- if (isFileNotFoundError$3(error)) {
29623
+ if (isFileNotFoundError$4(error)) {
29441
29624
  return null;
29442
29625
  }
29443
29626
  throw error;
@@ -29452,7 +29635,7 @@
29452
29635
  /**
29453
29636
  * Returns true when an error is a missing-file filesystem error.
29454
29637
  */
29455
- function isFileNotFoundError$3(error) {
29638
+ function isFileNotFoundError$4(error) {
29456
29639
  return Boolean(error &&
29457
29640
  typeof error === 'object' &&
29458
29641
  'code' in error &&
@@ -30445,7 +30628,7 @@
30445
30628
  - 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.
30446
30629
  - 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.
30447
30630
  - A project can be a git repository — you can run \`git init\` inside a project folder and commit your work there.
30448
- - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory except the queued message file you are answering.
30631
+ - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory beyond the file changes allowed at the top of this prompt.
30449
30632
 
30450
30633
  ${block(buildProjectReferenceInstructions(projectsUrlPath))}
30451
30634
  ${block(buildProjectRuntimeInstructions(options.projectRuntimeApi))}
@@ -30501,55 +30684,166 @@
30501
30684
  }
30502
30685
 
30503
30686
  /**
30504
- * Title stored on every agent goal chat.
30687
+ * Milliseconds in one second.
30688
+ *
30689
+ * @private internal timeout-chat constant
30690
+ */
30691
+ const SECOND_IN_MILLISECONDS = 1000;
30692
+ /**
30693
+ * Seconds in one minute.
30694
+ *
30695
+ * @private internal timeout-chat constant
30696
+ */
30697
+ const MINUTE_IN_SECONDS = 60;
30698
+ /**
30699
+ * Seconds in one hour.
30700
+ *
30701
+ * @private internal timeout-chat constant
30702
+ */
30703
+ const HOUR_IN_SECONDS = 60 * MINUTE_IN_SECONDS;
30704
+ /**
30705
+ * Seconds in one day.
30706
+ *
30707
+ * @private internal timeout-chat constant
30708
+ */
30709
+ const HOURS_IN_DAY = 24;
30710
+ /**
30711
+ * Seconds in one day.
30712
+ *
30713
+ * @private internal timeout-chat constant
30505
30714
  */
30715
+ const DAY_IN_SECONDS = HOURS_IN_DAY * HOUR_IN_SECONDS;
30506
30716
  /**
30507
- * Internal endpoint used by managed coding agents to plan goal-chat messages.
30717
+ * Converts timeout duration into natural language sentence fragments.
30718
+ *
30719
+ * Shared by every timeout surface: the chat chips, the goal-chat planned messages of the Agents
30720
+ * Server, and the planned-message prompt section of the agent-folder runner.
30721
+ *
30722
+ * @param milliseconds - Duration or repeat interval in milliseconds.
30723
+ * @returns Label such as `5 minutes` or `1 hour 30 minutes`.
30724
+ *
30725
+ * @private internal utility of `<Chat/>` and the Agents Server planned messages
30508
30726
  */
30509
- const AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH = '/api/internal/agent-goal-chat-planned-messages';
30727
+ function formatTimeoutDurationHuman(milliseconds) {
30728
+ const normalizedMilliseconds = Math.max(0, Math.floor(milliseconds));
30729
+ const totalSeconds = Math.max(1, Math.floor(normalizedMilliseconds / SECOND_IN_MILLISECONDS));
30730
+ if (totalSeconds < MINUTE_IN_SECONDS) {
30731
+ return formatTimeoutUnit(totalSeconds, 'second');
30732
+ }
30733
+ if (totalSeconds < HOUR_IN_SECONDS) {
30734
+ const roundedMinutes = Math.max(1, Math.round(totalSeconds / MINUTE_IN_SECONDS));
30735
+ return formatTimeoutUnit(roundedMinutes, 'minute');
30736
+ }
30737
+ if (totalSeconds < DAY_IN_SECONDS) {
30738
+ const hours = Math.floor(totalSeconds / HOUR_IN_SECONDS);
30739
+ const minutes = Math.floor((totalSeconds % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS);
30740
+ if (minutes === 0) {
30741
+ return formatTimeoutUnit(hours, 'hour');
30742
+ }
30743
+ return `${formatTimeoutUnit(hours, 'hour')} ${formatTimeoutUnit(minutes, 'minute')}`;
30744
+ }
30745
+ const days = Math.floor(totalSeconds / DAY_IN_SECONDS);
30746
+ const hours = Math.floor((totalSeconds % DAY_IN_SECONDS) / HOUR_IN_SECONDS);
30747
+ if (hours === 0) {
30748
+ return formatTimeoutUnit(days, 'day');
30749
+ }
30750
+ return `${formatTimeoutUnit(days, 'day')} ${formatTimeoutUnit(hours, 'hour')}`;
30751
+ }
30752
+ /**
30753
+ * Formats one pluralized timeout unit.
30754
+ *
30755
+ * @private internal timeout-chat helper
30756
+ */
30757
+ function formatTimeoutUnit(value, unit) {
30758
+ return `${value} ${unit}${value === 1 ? '' : 's'}`;
30759
+ }
30760
+ // Note: [💞] Ignore a discrepancy between file name and entity name
30510
30761
 
30511
30762
  /**
30512
30763
  * Builds the planned-message instructions available to an Agents Server-managed coding agent.
30513
30764
  *
30514
- * Planned messages always target the agent's singleton goal chat, regardless of the chat in which
30515
- * the command is invoked.
30765
+ * Planned messages always target the agent's singleton goal chat, regardless of the chat in which the
30766
+ * wake-up is planned, and they repeat like `setInterval` until they are cancelled. The sidecar file is
30767
+ * the only channel that changes them, so an answer that merely claims a follow-up was planned changes
30768
+ * nothing — and an answer that changes nothing keeps the current plan running.
30769
+ *
30770
+ * @param sidecar - Sidecar prepared by the Agents Server for the answered message.
30771
+ * @returns Prompt section, or an empty string outside Agents Server-managed runs.
30516
30772
  */
30517
- function buildAgentGoalChatPromptSection(runtimeApi) {
30518
- if (!runtimeApi) {
30773
+ function buildAgentGoalChatPromptSection(sidecar) {
30774
+ if (!sidecar) {
30519
30775
  return '';
30520
30776
  }
30521
- return _spaceTrim.spaceTrim(`
30522
- ## Planned goal-chat messages
30777
+ return _spaceTrim.spaceTrim((block) => `
30778
+ ## Planned goal-chat messages
30523
30779
 
30524
- 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.
30780
+ 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.
30525
30781
 
30526
- - Use \`list_timeouts\` before planning follow-up work so you do not create duplicates.
30527
- - 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.
30528
- - Use \`cancel_timeout\` with the returned \`timeoutId\` when a planned message is no longer useful.
30529
- - 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.
30782
+ ${block(buildCurrentPlannedMessagesLines(sidecar.currentPlannedMessages))}
30530
30783
 
30531
- Run the corresponding authenticated command:
30784
+ Editing \`${sidecar.relativeSidecarPath}\` is the **only** way to change what wakes you. Writing in your answer that you scheduled something does not schedule anything.
30532
30785
 
30533
- \`\`\`bash
30534
- # list_timeouts
30535
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30536
- -H "Content-Type: application/json" \\
30537
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30538
- -d '{"action":"list","agentPermanentId":"${runtimeApi.agentPermanentId}"}'
30786
+ - **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.
30787
+ - 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.
30788
+ - 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.
30789
+ - When your goal no longer needs a listed message at all, cancel it without planning a replacement.
30790
+ - Never edit \`version\`, \`agentPermanentId\`, or \`currentPlannedMessages\`.
30791
+ - The Agents Server applies every command once your answer is finished, and each repetition appears in the goal chat and invokes you there.
30792
+ - 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.
30793
+ `);
30794
+ }
30795
+ /**
30796
+ * Renders the already planned messages so the agent can compare them with its goal.
30797
+ *
30798
+ * @param currentPlannedMessages - Planned messages prepared by the Agents Server.
30799
+ * @returns Markdown list, or a sentence stating that nothing is planned yet.
30800
+ *
30801
+ * @private function of `buildAgentGoalChatPromptSection`
30802
+ */
30803
+ function buildCurrentPlannedMessagesLines(currentPlannedMessages) {
30804
+ if (currentPlannedMessages.length === 0) {
30805
+ return 'You currently have no planned messages waiting.';
30806
+ }
30807
+ const plannedMessageLines = currentPlannedMessages.map(createCurrentPlannedMessageLine);
30808
+ return _spaceTrim.spaceTrim((block) => `
30809
+ These planned messages are already waiting for you, so keep them unless they stopped matching your goal:
30539
30810
 
30540
- # set_timeout
30541
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30542
- -H "Content-Type: application/json" \\
30543
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30544
- -d '{"action":"set","agentPermanentId":"${runtimeApi.agentPermanentId}","milliseconds":3600000,"message":"Continue the current goal and decide the next concrete action."}'
30811
+ ${block(plannedMessageLines.join('\n'))}
30812
+ `);
30813
+ }
30814
+ /**
30815
+ * Renders one already planned message with the schedule the agent has to compare with its goal.
30816
+ *
30817
+ * @param plannedMessage - One planned message prepared by the Agents Server.
30818
+ * @returns Markdown list item.
30819
+ *
30820
+ * @private function of `buildAgentGoalChatPromptSection`
30821
+ */
30822
+ function createCurrentPlannedMessageLine(plannedMessage) {
30823
+ var _a;
30824
+ const message = ((_a = plannedMessage.message) === null || _a === void 0 ? void 0 : _a.trim()) || 'Continue working towards the current goal.';
30825
+ const schedule = plannedMessage.intervalMs
30826
+ ? `repeats every ${formatTimeoutDurationHuman(plannedMessage.intervalMs)}`
30827
+ : `wakes you once at ${plannedMessage.dueAt}`;
30828
+ return `- \`${plannedMessage.timeoutId}\` ${schedule}: ${message}`;
30829
+ }
30545
30830
 
30546
- # cancel_timeout
30547
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30548
- -H "Content-Type: application/json" \\
30549
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30550
- -d '{"action":"cancel","agentPermanentId":"${runtimeApi.agentPermanentId}","timeoutId":"<timeout-id>"}'
30551
- \`\`\`
30552
- `);
30831
+ /**
30832
+ * Creates the stable file-system safe base name that belongs to one queued `.book` message.
30833
+ *
30834
+ * Every sidecar the Agents Server prepares next to a queued message (TEAM workspaces, planned-message
30835
+ * files, ...) derives its own name from this one normalization, so a sidecar can always be found again
30836
+ * from the message file name alone.
30837
+ *
30838
+ * @param messageFileName - File name of the queued message, with or without directories.
30839
+ * @returns Normalized base name usable as a directory or file name.
30840
+ *
30841
+ * @private internal convention shared by the Agents Server and agent-folder runner
30842
+ */
30843
+ function createAgentMessageSidecarBaseName(messageFileName) {
30844
+ const rawBaseName = path.basename(messageFileName).replace(/\.book$/iu, '');
30845
+ const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
30846
+ return normalizedBaseName || 'message';
30553
30847
  }
30554
30848
 
30555
30849
  // Note: [💞] This file defines the shared TEAM workspace convention rather than one standalone entity.
@@ -30583,9 +30877,7 @@
30583
30877
  * @private internal convention shared by the Agents Server and agent-folder runner
30584
30878
  */
30585
30879
  function createAgentTeamConversationWorkspaceDirectoryName(messageFileName) {
30586
- const rawBaseName = path.basename(messageFileName).replace(/\.book$/iu, '');
30587
- const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
30588
- return normalizedBaseName || 'message';
30880
+ return createAgentMessageSidecarBaseName(messageFileName);
30589
30881
  }
30590
30882
  /**
30591
30883
  * Creates the relative active workspace path for one queued message.
@@ -30655,8 +30947,8 @@
30655
30947
  return '';
30656
30948
  }
30657
30949
  const teammateSections = workspace.manifest.teammates.map((teammate) => {
30658
- const sourcePath = toPromptPath(path.join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
30659
- const transcriptPath = toPromptPath(path.join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
30950
+ const sourcePath = toPromptPath$1(path.join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
30951
+ const transcriptPath = toPromptPath$1(path.join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
30660
30952
  const instructions = teammate.instructions.trim() || 'No additional TEAM instructions were provided.';
30661
30953
  return _spaceTrim.spaceTrim(`
30662
30954
  - ${teammate.agentName}
@@ -30674,7 +30966,7 @@
30674
30966
 
30675
30967
  - 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.
30676
30968
  - The primary agent remains responsible for the final user-facing \`MESSAGE @Agent\` answer.
30677
- - 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)}\`.
30969
+ - 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)}\`.
30678
30970
  - Name each transcript \`<teammate permanent id>--<sequence>.book\`, for example \`${workspace.manifest.teammates[0].permanentId}--01.book\`.
30679
30971
  - 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.
30680
30972
  `);
@@ -30682,7 +30974,7 @@
30682
30974
  /**
30683
30975
  * Converts a filesystem-relative path into the portable path notation used in prompts.
30684
30976
  */
30685
- function toPromptPath(path) {
30977
+ function toPromptPath$1(path) {
30686
30978
  return path.replace(/\\/gu, '/');
30687
30979
  }
30688
30980
 
@@ -30695,7 +30987,7 @@
30695
30987
 
30696
30988
  - 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\`.
30697
30989
  - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
30698
- ${block(buildAllowedFileChangesPromptLine(options.teamWorkspace))}
30990
+ ${block(buildAllowedFileChangesPromptLine(options))}
30699
30991
 
30700
30992
  ## Rules for the answering
30701
30993
 
@@ -30731,7 +31023,7 @@
30731
31023
 
30732
31024
  - 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.
30733
31025
 
30734
- ${block(buildAgentGoalChatPromptSection(options.goalChatRuntimeApi))}
31026
+ ${block(buildAgentGoalChatPromptSection(options.plannedMessagesSidecar))}
30735
31027
 
30736
31028
  ${block(buildAgentProjectsPromptSection(options))}
30737
31029
 
@@ -30745,11 +31037,30 @@
30745
31037
  /**
30746
31038
  * Explains the exact files that one coding harness may change for a user turn.
30747
31039
  */
30748
- function buildAllowedFileChangesPromptLine(teamWorkspace) {
30749
- if (!teamWorkspace) {
30750
- return `- Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`;
31040
+ function buildAllowedFileChangesPromptLine(options) {
31041
+ const allowedFileChanges = [`files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`];
31042
+ if (options.teamWorkspace) {
31043
+ allowedFileChanges.push(`new consultation transcripts inside \`${toPromptPath(options.teamWorkspace.relativeWorkspacePath)}\``);
31044
+ }
31045
+ if (options.plannedMessagesSidecar) {
31046
+ allowedFileChanges.push(`the \`commands\` array of \`${toPromptPath(options.plannedMessagesSidecar.relativeSidecarPath)}\``);
31047
+ }
31048
+ return `- Do not modify any other file in the repository, except ${formatAllowedFileChanges(allowedFileChanges)}`;
31049
+ }
31050
+ /**
31051
+ * Joins the allowed file-change phrases into one readable enumeration.
31052
+ */
31053
+ function formatAllowedFileChanges(allowedFileChanges) {
31054
+ if (allowedFileChanges.length === 1) {
31055
+ return allowedFileChanges[0];
30751
31056
  }
30752
- 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, '/')}\``;
31057
+ return `${allowedFileChanges.slice(0, -1).join(', ')} and ${allowedFileChanges[allowedFileChanges.length - 1]}`;
31058
+ }
31059
+ /**
31060
+ * Converts a filesystem-relative path into the portable path notation used in prompts.
31061
+ */
31062
+ function toPromptPath(path) {
31063
+ return path.replace(/\\/gu, '/');
30753
31064
  }
30754
31065
 
30755
31066
  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"}];
@@ -36811,7 +37122,8 @@
36811
37122
  requirements = await importReferencedFiles(requirements);
36812
37123
  requirements = appendMcpServers(requirements, agentSource);
36813
37124
  requirements = appendNonCommitmentContent(requirements, parseResult);
36814
- return appendExampleInteractions(requirements, parseResult);
37125
+ requirements = appendExampleInteractions(requirements, parseResult);
37126
+ return appendUnknownCommitmentContent(requirements, parseResult);
36815
37127
  }
36816
37128
  /**
36817
37129
  * Imports text files referenced by IMPORT commitments and appends their transformed content to the system message.
@@ -36933,6 +37245,36 @@
36933
37245
  }
36934
37246
  return appendSystemMessageSection(requirements, nonCommitmentContent);
36935
37247
  }
37248
+ /**
37249
+ * Appends unknown commitment blocks as final extra context without applying commitment side effects.
37250
+ *
37251
+ * @param requirements - Current requirements snapshot.
37252
+ * @param parseResult - Parsed source including unknown commitment blocks.
37253
+ * @returns Requirements with unknown commitment context appended at the end of the system message.
37254
+ *
37255
+ * @private internal utility of `augmentAgentModelRequirementsFromSource`
37256
+ */
37257
+ function appendUnknownCommitmentContent(requirements, parseResult) {
37258
+ const unknownCommitmentContent = getUnknownCommitmentContent(parseResult);
37259
+ if (!unknownCommitmentContent) {
37260
+ return requirements;
37261
+ }
37262
+ return appendSystemMessageSection(requirements, unknownCommitmentContent);
37263
+ }
37264
+ /**
37265
+ * Joins unknown commitment blocks into their final extra-context representation.
37266
+ *
37267
+ * @param parseResult - Parsed source including unknown commitment blocks.
37268
+ * @returns Unknown commitment source blocks in their original order.
37269
+ *
37270
+ * @private internal utility of `appendUnknownCommitmentContent`
37271
+ */
37272
+ function getUnknownCommitmentContent(parseResult) {
37273
+ return parseResult.unknownCommitments
37274
+ .map((commitment) => commitment.source)
37275
+ .filter(Boolean)
37276
+ .join('\n\n');
37277
+ }
36936
37278
  /**
36937
37279
  * Collects plain-text lines that were not parsed as commitments and should still become part of the final system message.
36938
37280
  *
@@ -37472,12 +37814,12 @@
37472
37814
  await promises.rm(finishedWorkspacePath, { recursive: true, force: true });
37473
37815
  await promises.mkdir(finishedWorkspacePath, { recursive: true });
37474
37816
  const relativePaths = [
37475
- toPortablePath$1(path.join(finishedRelativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME)),
37817
+ toPortablePath$2(path.join(finishedRelativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME)),
37476
37818
  ];
37477
37819
  await promises.rename(activeManifestPath, path.join(finishedWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME));
37478
37820
  for (const transcriptFileName of transcriptFileNames) {
37479
37821
  await promises.rename(path.join(activeWorkspacePath, transcriptFileName), path.join(finishedWorkspacePath, transcriptFileName));
37480
- relativePaths.push(toPortablePath$1(path.join(finishedRelativeWorkspacePath, transcriptFileName)));
37822
+ relativePaths.push(toPortablePath$2(path.join(finishedRelativeWorkspacePath, transcriptFileName)));
37481
37823
  }
37482
37824
  await promises.rm(activeWorkspacePath, { recursive: true, force: true });
37483
37825
  return { relativePaths };
@@ -37494,7 +37836,7 @@
37494
37836
  .sort((firstFileName, secondFileName) => firstFileName.localeCompare(secondFileName));
37495
37837
  }
37496
37838
  catch (error) {
37497
- if (isFileNotFoundError$2(error)) {
37839
+ if (isFileNotFoundError$3(error)) {
37498
37840
  return [];
37499
37841
  }
37500
37842
  throw error;
@@ -37509,7 +37851,7 @@
37509
37851
  return true;
37510
37852
  }
37511
37853
  catch (error) {
37512
- if (isFileNotFoundError$2(error)) {
37854
+ if (isFileNotFoundError$3(error)) {
37513
37855
  return false;
37514
37856
  }
37515
37857
  throw error;
@@ -37518,11 +37860,204 @@
37518
37860
  /**
37519
37861
  * Converts a filesystem-relative path into a portable Git path.
37520
37862
  */
37863
+ function toPortablePath$2(path) {
37864
+ return path.replace(/\\/gu, '/');
37865
+ }
37866
+ /**
37867
+ * Returns true when one filesystem error indicates a missing path.
37868
+ */
37869
+ function isFileNotFoundError$3(error) {
37870
+ return Boolean(error &&
37871
+ typeof error === 'object' &&
37872
+ 'code' in error &&
37873
+ (error.code === 'ENOENT' || error.code === 'ENOTDIR'));
37874
+ }
37875
+
37876
+ // Note: [💞] This file defines the shared planned-message sidecar convention rather than one standalone entity.
37877
+ /**
37878
+ * Relative directory holding the planned-message sidecar of each queued message.
37879
+ *
37880
+ * @private internal convention shared by the Agents Server and agent-folder runner
37881
+ */
37882
+ const AGENT_PLANNED_MESSAGES_DIRECTORY_PATH = path.join(AGENT_MESSAGES_DIRECTORY_PATH, 'planned');
37883
+ /**
37884
+ * Command actions an agent can request from its planned-message sidecar.
37885
+ *
37886
+ * @private internal convention shared by the Agents Server and agent-folder runner
37887
+ */
37888
+ const AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS = ['set', 'cancel'];
37889
+ /**
37890
+ * Creates the relative planned-message sidecar path of one queued message.
37891
+ *
37892
+ * @param messageFileName - File name of the queued `.book` message.
37893
+ * @returns Relative path of the sidecar inside the agent folder.
37894
+ *
37895
+ * @private internal convention shared by the Agents Server and agent-folder runner
37896
+ */
37897
+ function createAgentPlannedMessagesSidecarPath(messageFileName) {
37898
+ return path.join(AGENT_PLANNED_MESSAGES_DIRECTORY_PATH, `${createAgentMessageSidecarBaseName(messageFileName)}.json`);
37899
+ }
37900
+ /**
37901
+ * Parses one untrusted planned-message sidecar written back by a coding harness.
37902
+ *
37903
+ * A malformed sidecar never fails the already answered chat, so this returns `null` instead of throwing.
37904
+ *
37905
+ * @param sidecarContent - Raw file content read from the agent folder.
37906
+ * @returns Normalized sidecar, or `null` when it cannot be used.
37907
+ *
37908
+ * @private internal convention shared by the Agents Server and agent-folder runner
37909
+ */
37910
+ function parseAgentPlannedMessagesSidecar(sidecarContent) {
37911
+ let parsedSidecar;
37912
+ try {
37913
+ parsedSidecar = JSON.parse(sidecarContent);
37914
+ }
37915
+ catch (_a) {
37916
+ return null;
37917
+ }
37918
+ if (!parsedSidecar || typeof parsedSidecar !== 'object' || Array.isArray(parsedSidecar)) {
37919
+ return null;
37920
+ }
37921
+ const sidecar = parsedSidecar;
37922
+ if (sidecar.version !== 1 || typeof sidecar.agentPermanentId !== 'string') {
37923
+ return null;
37924
+ }
37925
+ return {
37926
+ version: 1,
37927
+ agentPermanentId: sidecar.agentPermanentId,
37928
+ currentPlannedMessages: normalizeAgentPlannedMessageSnapshots(sidecar.currentPlannedMessages),
37929
+ commands: normalizeAgentPlannedMessageCommands(sidecar.commands),
37930
+ };
37931
+ }
37932
+ /**
37933
+ * Keeps only the commands whose action is understood by the planned-message actions.
37934
+ *
37935
+ * @param rawCommands - Untrusted `commands` value read from one sidecar.
37936
+ * @returns Commands that can be dispatched, in their original order.
37937
+ *
37938
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37939
+ */
37940
+ function normalizeAgentPlannedMessageCommands(rawCommands) {
37941
+ if (!Array.isArray(rawCommands)) {
37942
+ return [];
37943
+ }
37944
+ return rawCommands.filter(isAgentPlannedMessageCommand);
37945
+ }
37946
+ /**
37947
+ * Checks whether one untrusted sidecar entry names a supported planned-message action.
37948
+ *
37949
+ * @param value - Untrusted command entry.
37950
+ * @returns `true` when the entry can be dispatched.
37951
+ *
37952
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37953
+ */
37954
+ function isAgentPlannedMessageCommand(value) {
37955
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
37956
+ return false;
37957
+ }
37958
+ const action = value.action;
37959
+ return (typeof action === 'string' &&
37960
+ AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS.includes(action));
37961
+ }
37962
+ /**
37963
+ * Keeps only the already planned messages that can be shown to the agent.
37964
+ *
37965
+ * @param rawSnapshots - Untrusted `currentPlannedMessages` value read from one sidecar.
37966
+ * @returns Planned-message snapshots with the fields required for display and cancellation.
37967
+ *
37968
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37969
+ */
37970
+ function normalizeAgentPlannedMessageSnapshots(rawSnapshots) {
37971
+ if (!Array.isArray(rawSnapshots)) {
37972
+ return [];
37973
+ }
37974
+ return rawSnapshots.filter(isAgentPlannedMessageSnapshot).map(createNormalizedAgentPlannedMessageSnapshot);
37975
+ }
37976
+ /**
37977
+ * Normalizes the optional fields of one already planned message.
37978
+ *
37979
+ * @param snapshot - Planned-message entry with the required identity fields.
37980
+ * @returns Snapshot with a usable repeat interval and message.
37981
+ *
37982
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37983
+ */
37984
+ function createNormalizedAgentPlannedMessageSnapshot(snapshot) {
37985
+ const intervalMs = Number(snapshot.intervalMs);
37986
+ return {
37987
+ timeoutId: snapshot.timeoutId,
37988
+ dueAt: snapshot.dueAt,
37989
+ message: typeof snapshot.message === 'string' ? snapshot.message : null,
37990
+ intervalMs: Number.isFinite(intervalMs) && intervalMs > 0 ? Math.floor(intervalMs) : null,
37991
+ };
37992
+ }
37993
+ /**
37994
+ * Checks whether one untrusted sidecar entry describes an already planned message.
37995
+ *
37996
+ * @param value - Untrusted planned-message entry.
37997
+ * @returns `true` when the entry can be shown and cancelled.
37998
+ *
37999
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
38000
+ */
38001
+ function isAgentPlannedMessageSnapshot(value) {
38002
+ return Boolean(value &&
38003
+ typeof value === 'object' &&
38004
+ typeof value.timeoutId === 'string' &&
38005
+ typeof value.dueAt === 'string');
38006
+ }
38007
+
38008
+ /**
38009
+ * Loads the planned-message sidecar the Agents Server prepared for one queued message.
38010
+ *
38011
+ * @param projectPath - Absolute path of the current agent runner folder.
38012
+ * @param queuedMessage - Queued message currently being answered.
38013
+ * @returns Sidecar details for the prompt, or `null` outside Agents Server-managed runs.
38014
+ */
38015
+ async function loadAgentPlannedMessagesSidecar(projectPath, queuedMessage) {
38016
+ const relativeSidecarPath = createAgentPlannedMessagesSidecarPath(queuedMessage.fileName);
38017
+ const sidecarContent = await readOptionalTextFile$2(path.join(projectPath, relativeSidecarPath));
38018
+ if (sidecarContent === null) {
38019
+ return null;
38020
+ }
38021
+ const sidecar = parseAgentPlannedMessagesSidecar(sidecarContent);
38022
+ if (!sidecar) {
38023
+ return null;
38024
+ }
38025
+ return {
38026
+ relativeSidecarPath: toPortablePath$1(relativeSidecarPath),
38027
+ currentPlannedMessages: sidecar.currentPlannedMessages,
38028
+ };
38029
+ }
38030
+ /**
38031
+ * Reads one text file and treats a missing planned-message sidecar as absent.
38032
+ *
38033
+ * @param path - Absolute sidecar path.
38034
+ * @returns File content, or `null` when the sidecar does not exist.
38035
+ */
38036
+ async function readOptionalTextFile$2(path) {
38037
+ try {
38038
+ return await promises.readFile(path, 'utf-8');
38039
+ }
38040
+ catch (error) {
38041
+ if (isFileNotFoundError$2(error)) {
38042
+ return null;
38043
+ }
38044
+ throw error;
38045
+ }
38046
+ }
38047
+ /**
38048
+ * Converts a filesystem-relative path to the portable form used in prompts.
38049
+ *
38050
+ * @param path - Relative path inside the agent folder.
38051
+ * @returns Path with forward slashes.
38052
+ */
37521
38053
  function toPortablePath$1(path) {
37522
38054
  return path.replace(/\\/gu, '/');
37523
38055
  }
37524
38056
  /**
37525
38057
  * Returns true when one filesystem error indicates a missing path.
38058
+ *
38059
+ * @param error - Caught filesystem failure.
38060
+ * @returns `true` for missing-path errors.
37526
38061
  */
37527
38062
  function isFileNotFoundError$2(error) {
37528
38063
  return Boolean(error &&
@@ -37537,7 +38072,7 @@
37537
38072
  async function loadAgentTeamConversationWorkspace(projectPath, queuedMessage) {
37538
38073
  const relativeWorkspacePath = createAgentTeamConversationWorkspacePath(queuedMessage.fileName);
37539
38074
  const manifestPath = path.join(projectPath, relativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME);
37540
- const manifestContent = await readOptionalTextFile(manifestPath);
38075
+ const manifestContent = await readOptionalTextFile$1(manifestPath);
37541
38076
  if (manifestContent === null) {
37542
38077
  return null;
37543
38078
  }
@@ -37565,7 +38100,7 @@
37565
38100
  /**
37566
38101
  * Reads one text file and treats a missing team workspace as absent.
37567
38102
  */
37568
- async function readOptionalTextFile(path) {
38103
+ async function readOptionalTextFile$1(path) {
37569
38104
  try {
37570
38105
  return await promises.readFile(path, 'utf-8');
37571
38106
  }
@@ -37632,6 +38167,233 @@
37632
38167
  return `/agents/${encodeURIComponent(agentId)}/projects`;
37633
38168
  }
37634
38169
 
38170
+ /**
38171
+ * Parses every structured event out of one coding-harness runtime log.
38172
+ *
38173
+ * The local agent runner streams the answering harness output into a live runtime log file, one
38174
+ * JSON event per line, optionally prefixed by shell noise. Lines without a usable JSON object are
38175
+ * skipped so a partially written or interleaved log never breaks its consumers.
38176
+ *
38177
+ * @param logText - Raw runtime log content.
38178
+ * @returns Structured events in the order they were streamed.
38179
+ * @private internal utility of the agent-message runtime
38180
+ */
38181
+ function parseAgentMessageRuntimeLogEvents(logText) {
38182
+ if (!logText) {
38183
+ return [];
38184
+ }
38185
+ const events = [];
38186
+ for (const line of logText.split(/\r?\n/u)) {
38187
+ const event = parseAgentMessageRuntimeLogEvent(line);
38188
+ if (event) {
38189
+ events.push(event);
38190
+ }
38191
+ }
38192
+ return events;
38193
+ }
38194
+ /**
38195
+ * Parses one runtime log line into a structured event when it embeds JSON.
38196
+ *
38197
+ * @param line - One raw runtime log line.
38198
+ * @returns Structured event, or `null` when the line carries no JSON object.
38199
+ * @private internal helper of `parseAgentMessageRuntimeLogEvents`
38200
+ */
38201
+ function parseAgentMessageRuntimeLogEvent(line) {
38202
+ const jsonStartIndex = line.indexOf('{');
38203
+ if (jsonStartIndex === -1) {
38204
+ return null;
38205
+ }
38206
+ try {
38207
+ const parsedEvent = JSON.parse(line.slice(jsonStartIndex).trim());
38208
+ if (typeof parsedEvent !== 'object' || parsedEvent === null || Array.isArray(parsedEvent)) {
38209
+ return null;
38210
+ }
38211
+ return parsedEvent;
38212
+ }
38213
+ catch (_a) {
38214
+ return null;
38215
+ }
38216
+ }
38217
+
38218
+ /**
38219
+ * Matches one project directory name following the agent `projects/` folder in a file path or command.
38220
+ *
38221
+ * Both path separators are accepted because the local runner answers messages on Unix and Windows,
38222
+ * and JSON-encoded payloads escape backslashes.
38223
+ *
38224
+ * @private internal constant of agent-message touched projects
38225
+ */
38226
+ const TOUCHED_PROJECT_PATH_REGEX = new RegExp(`${AGENT_PROJECTS_DIRECTORY_PATH}[\\\\/]+([^\\\\/"'\\s,;:]+)`, 'giu');
38227
+ /**
38228
+ * Resolves which projects one coding harness touched while answering a single message.
38229
+ *
38230
+ * The runtime log streams what the harness really did, so a project counts as touched when the
38231
+ * harness named it in a tool invocation — reading, searching, running, or editing files inside
38232
+ * `projects/<project-name>/`. Only tool payloads are inspected: tool results and assistant
38233
+ * narration are ignored so a project merely mentioned in the conversation is never reported.
38234
+ *
38235
+ * Every candidate is matched against the projects that really exist, which both filters prompt
38236
+ * placeholders such as `projects/<project-name>/` and keeps the reported names canonical.
38237
+ *
38238
+ * @param options - Raw runtime log content and the project directory names of the agent.
38239
+ * @returns Touched project names in the canonical spelling, ordered by first appearance.
38240
+ * @private internal utility of the agent-message runtime
38241
+ */
38242
+ function resolveAgentMessageTouchedProjectNames(options) {
38243
+ const canonicalProjectNames = createCanonicalProjectNameLookup(options.knownProjectNames);
38244
+ if (canonicalProjectNames.size === 0) {
38245
+ return [];
38246
+ }
38247
+ const touchedProjectNames = new Set();
38248
+ for (const event of parseAgentMessageRuntimeLogEvents(options.logText)) {
38249
+ for (const toolInvocationText of resolveRuntimeLogEventToolInvocationTexts(event)) {
38250
+ for (const candidateProjectName of resolveCandidateProjectNames(toolInvocationText)) {
38251
+ const canonicalProjectName = canonicalProjectNames.get(candidateProjectName.toLowerCase());
38252
+ if (canonicalProjectName !== undefined) {
38253
+ touchedProjectNames.add(canonicalProjectName);
38254
+ }
38255
+ }
38256
+ }
38257
+ }
38258
+ return Array.from(touchedProjectNames);
38259
+ }
38260
+ /**
38261
+ * Indexes the existing project directory names by their lower-cased form.
38262
+ *
38263
+ * @param knownProjectNames - Project directory names of the agent.
38264
+ * @returns Lookup from the lower-cased name to its canonical spelling.
38265
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38266
+ */
38267
+ function createCanonicalProjectNameLookup(knownProjectNames) {
38268
+ const canonicalProjectNames = new Map();
38269
+ for (const knownProjectName of knownProjectNames) {
38270
+ const normalizedProjectName = knownProjectName.trim();
38271
+ if (normalizedProjectName.length > 0) {
38272
+ canonicalProjectNames.set(normalizedProjectName.toLowerCase(), normalizedProjectName);
38273
+ }
38274
+ }
38275
+ return canonicalProjectNames;
38276
+ }
38277
+ /**
38278
+ * Collects the texts of every tool invocation described by one runtime log event.
38279
+ *
38280
+ * Claude Code reports tool invocations as `tool_use` content blocks, while Codex reports executed
38281
+ * commands and file changes as items — both carry the paths the harness worked with.
38282
+ *
38283
+ * @param event - One structured runtime log event.
38284
+ * @returns Texts that may contain project paths.
38285
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38286
+ */
38287
+ function resolveRuntimeLogEventToolInvocationTexts(event) {
38288
+ var _a, _b, _c;
38289
+ const toolInvocationTexts = [];
38290
+ for (const contentBlock of ((_a = event.message) === null || _a === void 0 ? void 0 : _a.content) || []) {
38291
+ if (contentBlock.type === 'tool_use' && contentBlock.input) {
38292
+ toolInvocationTexts.push(serializeToolInput(contentBlock.input));
38293
+ }
38294
+ }
38295
+ if (typeof ((_b = event.item) === null || _b === void 0 ? void 0 : _b.command) === 'string') {
38296
+ toolInvocationTexts.push(event.item.command);
38297
+ }
38298
+ for (const fileChange of ((_c = event.item) === null || _c === void 0 ? void 0 : _c.changes) || []) {
38299
+ if (typeof fileChange.path === 'string') {
38300
+ toolInvocationTexts.push(fileChange.path);
38301
+ }
38302
+ }
38303
+ return toolInvocationTexts;
38304
+ }
38305
+ /**
38306
+ * Serializes one tool input payload into scannable text.
38307
+ *
38308
+ * @param toolInput - Tool arguments of one harness tool invocation.
38309
+ * @returns Serialized payload, or an empty string when it cannot be serialized.
38310
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38311
+ */
38312
+ function serializeToolInput(toolInput) {
38313
+ try {
38314
+ return JSON.stringify(toolInput);
38315
+ }
38316
+ catch (_a) {
38317
+ return '';
38318
+ }
38319
+ }
38320
+ /**
38321
+ * Extracts every project directory name mentioned right after the agent `projects/` folder.
38322
+ *
38323
+ * @param toolInvocationText - One tool payload, command, or changed path.
38324
+ * @returns Candidate project directory names.
38325
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38326
+ */
38327
+ function resolveCandidateProjectNames(toolInvocationText) {
38328
+ const candidateProjectNames = [];
38329
+ const projectPathRegex = new RegExp(TOUCHED_PROJECT_PATH_REGEX.source, TOUCHED_PROJECT_PATH_REGEX.flags);
38330
+ let projectPathMatch = projectPathRegex.exec(toolInvocationText);
38331
+ while (projectPathMatch !== null) {
38332
+ const candidateProjectName = projectPathMatch[1];
38333
+ if (candidateProjectName) {
38334
+ candidateProjectNames.push(candidateProjectName);
38335
+ }
38336
+ projectPathMatch = projectPathRegex.exec(toolInvocationText);
38337
+ }
38338
+ return candidateProjectNames;
38339
+ }
38340
+
38341
+ /**
38342
+ * Resolves which agent projects one answered message viewed or edited.
38343
+ *
38344
+ * The runtime log is deleted as soon as the harness run finishes, so this must be called while
38345
+ * the log still exists — the Agents Server later shows the reported projects as chips below the
38346
+ * answer. Reporting touched projects is best-effort telemetry: an unreadable log or projects
38347
+ * folder simply yields no projects instead of failing the already answered message.
38348
+ *
38349
+ * @param options - Agent folder path and the live runtime log path of the answered message.
38350
+ * @returns Touched project directory names, ordered by first appearance in the run.
38351
+ */
38352
+ async function resolveTouchedAgentProjects(options) {
38353
+ const [logText, knownProjectNames] = await Promise.all([
38354
+ readOptionalTextFile(options.runtimeLogPath),
38355
+ listAgentProjectDirectoryNames(options.projectPath),
38356
+ ]);
38357
+ return resolveAgentMessageTouchedProjectNames({ logText, knownProjectNames });
38358
+ }
38359
+ /**
38360
+ * Lists the direct project directories of one agent folder.
38361
+ *
38362
+ * @param projectPath - Absolute path of the local agent folder.
38363
+ * @returns Project directory names, or an empty list when the agent has no projects folder.
38364
+ *
38365
+ * @private helper of `resolveTouchedAgentProjects`
38366
+ */
38367
+ async function listAgentProjectDirectoryNames(projectPath) {
38368
+ try {
38369
+ const projectsRootEntries = await promises.readdir(path.join(projectPath, AGENT_PROJECTS_DIRECTORY_PATH), {
38370
+ withFileTypes: true,
38371
+ });
38372
+ return projectsRootEntries
38373
+ .filter((projectsRootEntry) => projectsRootEntry.isDirectory())
38374
+ .map((projectDirectoryEntry) => projectDirectoryEntry.name);
38375
+ }
38376
+ catch (_a) {
38377
+ return [];
38378
+ }
38379
+ }
38380
+ /**
38381
+ * Reads one text file and treats a missing runtime log as "nothing was recorded".
38382
+ *
38383
+ * @param filePath - Absolute path of the file to read.
38384
+ * @returns File content, or `null` when it cannot be read.
38385
+ *
38386
+ * @private helper of `resolveTouchedAgentProjects`
38387
+ */
38388
+ async function readOptionalTextFile(filePath) {
38389
+ try {
38390
+ return await promises.readFile(filePath, 'utf-8');
38391
+ }
38392
+ catch (_a) {
38393
+ return null;
38394
+ }
38395
+ }
38396
+
37635
38397
  // Note: [💞] Ignore a discrepancy between file name and entity name
37636
38398
  /**
37637
38399
  * File suffix appended to one answered message book to store its run report sidecar.
@@ -37911,10 +38673,11 @@
37911
38673
  const { projectPath, options: runOptions, runner, actualRunnerModel, queuedMessage, uiHandle, isSharedDashboard, } = options;
37912
38674
  const agentSystemMessage = await loadLocalAgentSystemMessage(projectPath);
37913
38675
  const teamWorkspace = await loadAgentTeamConversationWorkspace(projectPath, queuedMessage);
38676
+ const plannedMessagesSidecar = await loadAgentPlannedMessagesSidecar(projectPath, queuedMessage);
37914
38677
  const agentServerRuntimeApi = resolveAgentProjectRuntimePromptApi(projectPath);
37915
38678
  const prompt = buildAgentMessagePrompt(queuedMessage.relativePath, agentSystemMessage, {
37916
38679
  projectRuntimeApi: agentServerRuntimeApi,
37917
- goalChatRuntimeApi: agentServerRuntimeApi,
38680
+ plannedMessagesSidecar: plannedMessagesSidecar || undefined,
37918
38681
  projectsUrlPath: resolveAgentProjectsUrlPath(projectPath),
37919
38682
  teamWorkspace: teamWorkspace || undefined,
37920
38683
  });
@@ -37935,21 +38698,29 @@
37935
38698
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentScriptPath(scriptPath);
37936
38699
  }
37937
38700
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
38701
+ const executionStartedAt = new Date().toISOString();
37938
38702
  let promptRunResult;
38703
+ let touchedProjectNames = [];
37939
38704
  try {
37940
38705
  try {
37941
- promptRunResult = await withPromptRuntimeLog(scriptPath, async (logPath) => await runPromptWithTestFeedback({
37942
- runner,
37943
- prompt,
37944
- scriptPath,
37945
- projectPath,
37946
- promptLabel: queuedMessage.relativePath,
37947
- logPath,
37948
- preserveArtifactsOnSuccess: false,
37949
- onAttemptStarted: (attemptCount) => {
37950
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(attemptCount);
37951
- },
37952
- }), { preserveArtifactsOnSuccess: false });
38706
+ promptRunResult = await withPromptRuntimeLog(scriptPath, async (logPath) => {
38707
+ const runResult = await runPromptWithTestFeedback({
38708
+ runner,
38709
+ prompt,
38710
+ scriptPath,
38711
+ projectPath,
38712
+ promptLabel: queuedMessage.relativePath,
38713
+ logPath,
38714
+ preserveArtifactsOnSuccess: false,
38715
+ onAttemptStarted: (attemptCount) => {
38716
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(attemptCount);
38717
+ },
38718
+ });
38719
+ // Note: The runtime log is deleted right after this handler, so the projects this
38720
+ // answer worked with must be resolved while the log still exists.
38721
+ touchedProjectNames = await resolveTouchedAgentProjects({ projectPath, runtimeLogPath: logPath });
38722
+ return runResult;
38723
+ }, { preserveArtifactsOnSuccess: false });
37953
38724
  }
37954
38725
  catch (error) {
37955
38726
  throw withAgentWatchErrorContext(error, {
@@ -37963,6 +38734,7 @@
37963
38734
  finally {
37964
38735
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
37965
38736
  }
38737
+ const executionFinishedAt = new Date().toISOString();
37966
38738
  await normalizeLineEndingsForAgentRound(projectPath, runOptions, roundChangedFilesSnapshot);
37967
38739
  const finishedTeamWorkspace = await finalizeAgentTeamConversationWorkspace({
37968
38740
  projectPath,
@@ -37981,6 +38753,11 @@
37981
38753
  modelName: actualRunnerModel,
37982
38754
  loginMethod: promptRunResult.loginMethod,
37983
38755
  usage: promptRunResult.usage,
38756
+ executionTiming: {
38757
+ startedAt: executionStartedAt,
38758
+ finishedAt: executionFinishedAt,
38759
+ },
38760
+ ...(touchedProjectNames.length === 0 ? {} : { touchedProjectNames }),
37984
38761
  },
37985
38762
  });
37986
38763
  await commitAnsweredMessageIfEnabled({
@@ -39673,24 +40450,12 @@
39673
40450
  - @@@
39674
40451
  - Keep in mind the DRY _(don't repeat yourself)_ principle.
39675
40452
  - Do a proper analysis of the current functionality before you start implementing.
39676
- - Add the changes into the [changelog](changelog/_current-preversion.md)
40453
+ - Add the changes into the [changelog](CHANGELOG.md)
40454
+ - Update the [README](README.md) if needed.
40455
+ - Update the [AGENTS.md](AGENTS.md) for the next job to be done if it makes sense.
39677
40456
  `),
39678
40457
  isDefaultProjectTemplate: true,
39679
40458
  },
39680
- {
39681
- id: 'agents-server',
39682
- relativeFilePath: path.join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'agents-server.md'),
39683
- slugPrefix: 'agents-server',
39684
- content: _spaceTrim.spaceTrim(`
39685
- - @@@
39686
- - Keep in mind the DRY _(don't repeat yourself)_ principle.
39687
- - Do a proper analysis of the current functionality before you start implementing.
39688
- - You are working with the [Agents Server](apps/agents-server)
39689
- - If you need to do the database migration, do it
39690
- - Add the changes into the [changelog](changelog/_current-preversion.md)
39691
- `),
39692
- isDefaultProjectTemplate: false,
39693
- },
39694
40459
  ];
39695
40460
  /**
39696
40461
  * Project-agnostic coder templates that `ptbk coder init` should materialize in any repository.
@@ -40782,6 +41547,10 @@
40782
41547
  console.info(colors__default["default"].gray(`✔ ${definition.label} ${installedVersion} is up to date.`));
40783
41548
  return;
40784
41549
  }
41550
+ if (installationState === 'installed') {
41551
+ console.info(colors__default["default"].gray(`✔ ${definition.label} ${installedVersion} is installed. Skipped checking for updates.`));
41552
+ return;
41553
+ }
40785
41554
  if (installationState === 'unknown') {
40786
41555
  console.info(colors__default["default"].gray(`✔ ${definition.label} ${installedVersion} is installed, the newest version could not be checked.`));
40787
41556
  return;
@@ -40995,20 +41764,20 @@
40995
41764
  // 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`
40996
41765
 
40997
41766
  /**
40998
- * Detects whether one CLI coding harness is installed globally and whether it is up to date.
41767
+ * Detects whether one CLI coding harness is installed globally and, when enabled, whether it is up to date.
40999
41768
  *
41000
- * Note: `$` is used to indicate that this function is not a pure function - it runs the harness command and queries npm
41769
+ * Note: `$` is used to indicate that this function is not a pure function - it runs the harness command and may query npm
41001
41770
  *
41002
41771
  * @private internal utility of `promptbookCli`
41003
41772
  */
41004
- async function $checkHarnessInstallation(definition) {
41773
+ async function $checkHarnessInstallation(definition, isHarnessUpdateCheckEnabled = true) {
41005
41774
  const [installedVersion, latestVersion] = await Promise.all([
41006
41775
  $resolveInstalledHarnessVersion(definition),
41007
- $resolveLatestNpmPackageVersion(definition.npmPackageName),
41776
+ isHarnessUpdateCheckEnabled ? $resolveLatestNpmPackageVersion(definition.npmPackageName) : Promise.resolve(null),
41008
41777
  ]);
41009
41778
  return {
41010
41779
  definition,
41011
- installationState: resolveHarnessInstallationState(installedVersion, latestVersion),
41780
+ installationState: resolveHarnessInstallationState(installedVersion, latestVersion, isHarnessUpdateCheckEnabled),
41012
41781
  installedVersion,
41013
41782
  latestVersion,
41014
41783
  };
@@ -41016,10 +41785,13 @@
41016
41785
  /**
41017
41786
  * Derives the installation state from the detected versions.
41018
41787
  */
41019
- function resolveHarnessInstallationState(installedVersion, latestVersion) {
41788
+ function resolveHarnessInstallationState(installedVersion, latestVersion, isHarnessUpdateCheckEnabled) {
41020
41789
  if (installedVersion === null) {
41021
41790
  return 'not-installed';
41022
41791
  }
41792
+ if (!isHarnessUpdateCheckEnabled) {
41793
+ return 'installed';
41794
+ }
41023
41795
  if (latestVersion === null) {
41024
41796
  return 'unknown';
41025
41797
  }
@@ -41030,8 +41802,8 @@
41030
41802
  // Note: [🟡] Code for CLI harness installation check [$checkHarnessInstallation](src/cli/cli-commands/common/harness/$checkHarnessInstallation.ts) should never be published outside of `@promptbook/cli`
41031
41803
 
41032
41804
  /**
41033
- * Checks that every given CLI coding harness is installed globally and up to date and offers to
41034
- * install or update the ones which are not.
41805
+ * Checks that every given CLI coding harness is installed globally and, when enabled, up to date.
41806
+ * Offers to install missing harnesses and update outdated ones.
41035
41807
  *
41036
41808
  * Harness names which are `undefined`, for example when no `--harness` is selected in a dry run,
41037
41809
  * are ignored so that every call site can pass its raw selection.
@@ -41040,13 +41812,13 @@
41040
41812
  *
41041
41813
  * @private internal utility of `promptbookCli`
41042
41814
  */
41043
- async function $ensureHarnessInstallations(harnessNames) {
41815
+ async function $ensureHarnessInstallations(harnessNames, isHarnessUpdateCheckEnabled = true) {
41044
41816
  const definitions = resolveCheckedHarnessDefinitions(harnessNames);
41045
41817
  if (definitions.length === 0) {
41046
41818
  return;
41047
41819
  }
41048
41820
  // Note: Detection of all harnesses runs in parallel, the questions have to be asked one by one
41049
- const statuses = await Promise.all(definitions.map((definition) => $checkHarnessInstallation(definition)));
41821
+ const statuses = await Promise.all(definitions.map((definition) => $checkHarnessInstallation(definition, isHarnessUpdateCheckEnabled)));
41050
41822
  for (const status of statuses) {
41051
41823
  await $applyHarnessInstallationStatus(status);
41052
41824
  }
@@ -41060,6 +41832,27 @@
41060
41832
  }
41061
41833
  // Note: [🟡] Code for CLI harness installation orchestration [$ensureHarnessInstallations](src/cli/cli-commands/common/harness/$ensureHarnessInstallations.ts) should never be published outside of `@promptbook/cli`
41062
41834
 
41835
+ /**
41836
+ * Registers the shared `--no-harness-update` option on a `ptbk coder` command that uses a coding harness.
41837
+ *
41838
+ * @private internal utility of `promptbookCli`
41839
+ */
41840
+ function addHarnessUpdateOption(command) {
41841
+ command.option('--no-harness-update', 'Skip checking whether globally installed coding harnesses are up to date');
41842
+ }
41843
+ /**
41844
+ * Converts the Commander harness-update flag into the normalized harness-check option.
41845
+ *
41846
+ * @private internal utility of `promptbookCli`
41847
+ */
41848
+ function normalizeHarnessUpdateCliOptions(cliOptions) {
41849
+ return {
41850
+ isHarnessUpdateCheckEnabled: cliOptions.harnessUpdate,
41851
+ };
41852
+ }
41853
+ // Note: [🟡] Code for CLI harness update options [harnessUpdateCliOptions](src/cli/cli-commands/common/harnessUpdateCliOptions.ts) should never be published outside of `@promptbook/cli`
41854
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
41855
+
41063
41856
  /**
41064
41857
  * Relative path to the Promptbook Coder quick-reference file initialized in project roots.
41065
41858
  *
@@ -41622,14 +42415,16 @@
41622
42415
  - CODING_AGENT_GIT_EMAIL
41623
42416
  - CODING_AGENT_GIT_SIGNING_KEY
41624
42417
 
41625
- Checks that the coding harnesses are installed globally and up to date:
42418
+ Checks that the coding harnesses are installed globally and up to date unless \`--no-harness-update\` is used:
41626
42419
  ${block(listCheckedHarnessLabels())}
41627
42420
 
41628
42421
  ${block(CODER_GIT_SYNC_DESCRIPTION)}
41629
42422
  `));
41630
42423
  addCoderGitSyncOptions(command);
42424
+ addHarnessUpdateOption(command);
41631
42425
  command.action(handleActionErrors(async (cliOptions) => {
41632
42426
  const gitSync = normalizeCoderGitSyncCliOptions(cliOptions);
42427
+ const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
41633
42428
  const projectPath = process.cwd();
41634
42429
  // Note: Import the git synchronization dynamically to keep the CLI fast for runs without `--commit`
41635
42430
  const { $commitCoderChanges, $startCoderGitSync } = await Promise.resolve().then(function () { return coderGitSync; });
@@ -41642,7 +42437,7 @@
41642
42437
  commitScope,
41643
42438
  commitMessage: 'Initialize Promptbook Coder',
41644
42439
  });
41645
- await $ensureHarnessInstallations(CODER_INIT_CHECKED_HARNESS_NAMES);
42440
+ await $ensureHarnessInstallations(CODER_INIT_CHECKED_HARNESS_NAMES, isHarnessUpdateCheckEnabled);
41646
42441
  }));
41647
42442
  }
41648
42443
  /**
@@ -41681,13 +42476,16 @@
41681
42476
  - Reports the answer of the harness, the response time and the reported usage
41682
42477
  - Starts the hourly/weekly quota window before you need it, so it is already refreshing when you do
41683
42478
  - Leaves the project exactly as it was — nothing is read, written, changed or committed
42479
+ - Checks that the selected harness is installed globally and up to date unless --no-harness-update is used
41684
42480
  - Use --no-ui to stream the raw harness output instead of only the compact result
41685
42481
  `));
41686
42482
  addPromptRunnerSelectionOptions(command);
42483
+ addHarnessUpdateOption(command);
41687
42484
  addPromptRunnerRuntimeOptions(command);
41688
42485
  command.action(handleActionErrors(async (cliOptions) => {
41689
42486
  const runnerOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, { isAgentRequired: true });
41690
- await $ensureHarnessInstallations([runnerOptions.agentName]);
42487
+ const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
42488
+ await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
41691
42489
  // Note: Import the ping dynamically to avoid loading heavy dependencies until needed
41692
42490
  const { pingCoderHarness } = await Promise.resolve().then(function () { return pingCoderHarness$1; });
41693
42491
  const { printCoderPingResult } = await Promise.resolve().then(function () { return printCoderPingResult$1; });
@@ -42178,7 +42976,7 @@
42178
42976
  - Optional --isolate runs every prompt in its own temporary git worktree and merges it back when verified
42179
42977
  - Optional --preserve-logs keeps temp prompt/log artifacts after successful rounds
42180
42978
  - Optional --no-ui keeps plain streaming console output for logging and debugging
42181
- - Checks that the selected harness is installed globally and up to date before the first prompt
42979
+ - Checks that the selected harness is installed globally and up to date before the first prompt unless --no-harness-update is used
42182
42980
  - In interactive mode, checks local and global Promptbook CLI installations and offers to update them
42183
42981
  - Supports GPG signing of commits
42184
42982
  - Optional pre-coding test run that can stop or repair pre-existing failures
@@ -42188,6 +42986,7 @@
42188
42986
  `));
42189
42987
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
42190
42988
  addPromptRunnerSelectionOptions(command);
42989
+ addHarnessUpdateOption(command);
42191
42990
  command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
42192
42991
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
42193
42992
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
@@ -42229,6 +43028,7 @@
42229
43028
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
42230
43029
  isAgentRequired: !dryRun,
42231
43030
  });
43031
+ const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
42232
43032
  // [1] Parse the wait options and --no-auto:
42233
43033
  // default: run automatically through the queue (no waiting between prompts)
42234
43034
  // --no-auto: wait for user confirmation before each prompt (interactive mode)
@@ -42239,7 +43039,7 @@
42239
43039
  if (await $ensurePromptbookCliInstallations()) {
42240
43040
  return process.exit(0);
42241
43041
  }
42242
- await $ensureHarnessInstallations([runnerOptions.agentName]);
43042
+ await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
42243
43043
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
42244
43044
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
42245
43045
  const waitAfterError = parseOptionalWaitDuration(waitAfterErrorValue, DEFAULT_WAIT_AFTER_ERROR_MS$1);
@@ -42333,7 +43133,7 @@
42333
43133
 
42334
43134
  Features:
42335
43135
  - Runs the same prompt processing as \`ptbk coder run\`
42336
- - Checks that the selected harness is installed globally and up to date on startup
43136
+ - Checks that the selected harness is installed globally and up to date on startup unless --no-harness-update is used
42337
43137
  - Does not exit when all prompts are done; polls for new prompt files instead
42338
43138
  - Serves a kanban board at http://localhost:<port> for visual progress tracking
42339
43139
  - Allows editing prompt files directly from the browser (Trello-style)
@@ -42345,6 +43145,7 @@
42345
43145
  .default(DEFAULT_CODER_SERVER_PORT));
42346
43146
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
42347
43147
  addPromptRunnerSelectionOptions(command);
43148
+ addHarnessUpdateOption(command);
42348
43149
  command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
42349
43150
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
42350
43151
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
@@ -42376,7 +43177,8 @@
42376
43177
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
42377
43178
  isAgentRequired: !dryRun,
42378
43179
  });
42379
- await $ensureHarnessInstallations([runnerOptions.agentName]);
43180
+ const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
43181
+ await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
42380
43182
  // [1] Parse the wait options and --no-auto (same logic as `coder run`)
42381
43183
  const waitForUser = !auto;
42382
43184
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
@@ -44300,23 +45102,27 @@
44300
45102
  // TODO: [🧠][♿] Maybe export through `@promptbook/node`
44301
45103
 
44302
45104
  /**
44303
- * Attempts to locate the specified application on a Windows system by searching common installation directories.
45105
+ * Attempts to locate the specified application on a Windows system by searching PATH and common installation directories.
44304
45106
  * Returns the path to the executable if found, or null otherwise.
44305
45107
  *
44306
45108
  * @private within the repository
44307
45109
  */
44308
45110
  async function locateAppOnWindows({ appName, windowsSuffix, }) {
44309
45111
  try {
44310
- const prefixes = [
45112
+ const EXECUTABLE_FILENAME = windowsSuffix.replace(/^.*[\\/]/, '');
45113
+ const PATH_EXECUTABLE_PATHS = (process.env.PATH || '')
45114
+ .split(path.delimiter)
45115
+ .filter(Boolean)
45116
+ .map((pathPrefix) => path.join(pathPrefix, EXECUTABLE_FILENAME));
45117
+ const INSTALLATION_EXECUTABLE_PATHS = [
44311
45118
  process.env.LOCALAPPDATA,
44312
45119
  path.join(process.env.LOCALAPPDATA || '', 'Programs'),
44313
45120
  process.env.PROGRAMFILES,
44314
45121
  process.env['PROGRAMFILES(X86)'],
44315
- ];
44316
- for (const prefix of prefixes) {
44317
- const path = prefix + windowsSuffix;
44318
- if (await isExecutable(path, $provideFilesystemForNode())) {
44319
- return path;
45122
+ ].map((pathPrefix) => pathPrefix + windowsSuffix);
45123
+ for (const executablePath of [...PATH_EXECUTABLE_PATHS, ...INSTALLATION_EXECUTABLE_PATHS]) {
45124
+ if (await isExecutable(executablePath, $provideFilesystemForNode())) {
45125
+ return executablePath;
44320
45126
  }
44321
45127
  }
44322
45128
  throw new Error(`Can not locate app ${appName} on Windows.`);
@@ -75709,6 +76515,10 @@
75709
76515
  * Constant for prompts dir.
75710
76516
  */
75711
76517
  const PROMPTS_DIR$1 = path.join(process.cwd(), 'prompts');
76518
+ /**
76519
+ * Commit message for files changed by a successful or failed pre-coding test in repair mode.
76520
+ */
76521
+ const PRE_CODING_TEST_CHANGES_COMMIT_MESSAGE = 'test: Apply changes made by pre-coding tests';
75712
76522
  /**
75713
76523
  * Main entry point for running prompts with the selected agent.
75714
76524
  *
@@ -76032,6 +76842,7 @@
76032
76842
  });
76033
76843
  await ensureWorkingTreeClean();
76034
76844
  }
76845
+ const testBeforeCommitScope = await captureTestBeforeCommitScopeIfNeeded(runOptions);
76035
76846
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
76036
76847
  const testBeforeResult = await runTestBefore({
76037
76848
  testCommand: runOptions.testCommand,
@@ -76040,6 +76851,11 @@
76040
76851
  }).finally(() => {
76041
76852
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
76042
76853
  });
76854
+ await commitTestBeforeChangesIfNeeded({
76855
+ runOptions,
76856
+ testBeforeCommitScope,
76857
+ waitForRequestedPause,
76858
+ });
76043
76859
  if (testBeforeResult.isPassed) {
76044
76860
  return hasWaitedForStart;
76045
76861
  }
@@ -76088,6 +76904,38 @@
76088
76904
  });
76089
76905
  return updatedHasWaitedForStart;
76090
76906
  }
76907
+ /**
76908
+ * Captures the files present before a `yes-and-fix` pre-coding test, so only changes made by that test can be committed.
76909
+ */
76910
+ async function captureTestBeforeCommitScopeIfNeeded(runOptions) {
76911
+ if (runOptions.testBefore !== 'yes-and-fix' || runOptions.noCommit) {
76912
+ return undefined;
76913
+ }
76914
+ return captureCoderCommitScope(process.cwd());
76915
+ }
76916
+ /**
76917
+ * Commits files changed by a `yes-and-fix` pre-coding test before the coder continues to a queued or repair prompt.
76918
+ */
76919
+ async function commitTestBeforeChangesIfNeeded(options) {
76920
+ const { runOptions, testBeforeCommitScope, waitForRequestedPause } = options;
76921
+ if (!testBeforeCommitScope) {
76922
+ return;
76923
+ }
76924
+ const relevantPaths = await resolveCoderCommitScopePaths(testBeforeCommitScope);
76925
+ if (relevantPaths.length === 0) {
76926
+ return;
76927
+ }
76928
+ await waitForRequestedPause({
76929
+ checkpointLabel: 'committing changes made by pre-coding tests',
76930
+ phase: 'verifying',
76931
+ statusMessage: 'Committing changes made by pre-coding tests...',
76932
+ });
76933
+ await commitChanges(PRE_CODING_TEST_CHANGES_COMMIT_MESSAGE, {
76934
+ autoPush: runOptions.autoPush,
76935
+ projectPath: testBeforeCommitScope.projectPath,
76936
+ relevantPaths,
76937
+ });
76938
+ }
76091
76939
  /**
76092
76940
  * Creates a pause waiter that keeps the progress display and rich UI in sync.
76093
76941
  */