@promptbook/cli 0.114.0-5 → 0.114.0-7

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 (296) hide show
  1. package/agents/default/developer.book +1 -0
  2. package/apps/agents-server/next.config.ts +22 -0
  3. package/apps/agents-server/package.json +6 -6
  4. package/apps/agents-server/scripts/build-agents-server.js +43 -7
  5. package/apps/agents-server/scripts/build-e2e.js +12 -2
  6. package/apps/agents-server/scripts/generate-reserved-paths/generate-reserved-paths.ts +30 -16
  7. package/apps/agents-server/scripts/kill-port.js +163 -0
  8. package/apps/agents-server/scripts/run-e2e-tests.js +21 -8
  9. package/apps/agents-server/scripts/run-npm.js +81 -5
  10. package/apps/agents-server/src/app/actions.ts +3 -0
  11. package/apps/agents-server/src/app/admin/email-server/page.tsx +17 -18
  12. package/apps/agents-server/src/app/admin/task-manager/TaskManagerTaskRow.tsx +2 -0
  13. package/apps/agents-server/src/app/admin/task-manager/[taskId]/TaskManagerTaskDetailClient.tsx +2 -0
  14. package/apps/agents-server/src/app/admin/task-manager/taskManagerTaskPresentation.tsx +23 -0
  15. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +7 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/api/timeouts/[timeoutId]/route.ts +10 -153
  17. package/apps/agents-server/src/app/agents/[agentName]/api/timeouts/route.ts +9 -4
  18. package/apps/agents-server/src/app/agents/[agentName]/api/user-chats/[chatId]/route.ts +3 -0
  19. package/apps/agents-server/src/app/agents/[agentName]/api/user-chats/[chatId]/stream/route.ts +2 -0
  20. package/apps/agents-server/src/app/agents/[agentName]/api/user-chats/resolveUserChatScope.ts +8 -1
  21. package/apps/agents-server/src/app/agents/[agentName]/api/user-chats/route.ts +15 -7
  22. package/apps/agents-server/src/app/agents/[agentName]/book/useBookEditorSaving.ts +18 -28
  23. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatHistoryClient.tsx +19 -1
  24. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatSidebarDefault.tsx +17 -5
  25. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentGoalChatNotice.tsx +23 -0
  26. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentGoalChatPlannedMessages.tsx +153 -0
  27. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatPanel.tsx +6 -0
  28. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +13 -0
  29. package/apps/agents-server/src/app/agents/[agentName]/chat/ExternalUserChatAdminActions.tsx +51 -3
  30. package/apps/agents-server/src/app/agents/[agentName]/chat/TeamMemberFrozenChatPrimaryAgentLink.tsx +31 -0
  31. package/apps/agents-server/src/app/agents/[agentName]/chat/useAgentChatSidebarState.ts +26 -2
  32. package/apps/agents-server/src/app/agents/[agentName]/goal/page.tsx +35 -0
  33. package/apps/agents-server/src/app/agents/[agentName]/projects/[projectName]/page.tsx +2 -2
  34. package/apps/agents-server/src/app/api/admin/dns-records/cloudflare/route.ts +26 -16
  35. package/apps/agents-server/src/app/api/emails/incoming/stalwart/route.ts +9 -2
  36. package/apps/agents-server/src/app/superadmin/servers/ServersRegistryTable.tsx +31 -39
  37. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +10 -3
  38. package/apps/agents-server/src/components/AgentProjects/AgentProjectIcon.tsx +137 -0
  39. package/apps/agents-server/src/components/AgentProjects/AgentProjectItem.tsx +26 -10
  40. package/apps/agents-server/src/components/CloudflareDnsWizard/CloudflareDnsApiTokenImportStep.tsx +5 -6
  41. package/apps/agents-server/src/components/CloudflareDnsWizard/CloudflareDnsCheckStep.tsx +6 -5
  42. package/apps/agents-server/src/components/CloudflareDnsWizard/CloudflareDnsManualStep.tsx +10 -14
  43. package/apps/agents-server/src/components/CloudflareDnsWizard/CloudflareDnsWizard.tsx +11 -20
  44. package/apps/agents-server/src/components/CloudflareDnsWizard/useCloudflareDnsRecordImport.ts +5 -6
  45. package/apps/agents-server/src/components/DnsRecordsInstructions/DnsRecordsInstructions.tsx +91 -57
  46. package/apps/agents-server/src/components/DnsRecordsInstructions/DnsRecordsSectionPanel.tsx +63 -0
  47. package/apps/agents-server/src/components/DnsRecordsInstructions/DnsRecordsSectionVariantTabs.tsx +67 -0
  48. package/apps/agents-server/src/components/DnsRecordsInstructions/DnsRecordsTable.tsx +40 -0
  49. package/apps/agents-server/src/components/Header/buildActiveAgentViewItems.ts +5 -4
  50. package/apps/agents-server/src/components/Header/createAgentViewLabel.tsx +4 -4
  51. package/apps/agents-server/src/components/Header/resolveActiveAgentNavigation.ts +2 -2
  52. package/apps/agents-server/src/components/Homepage/useAgentsListImportExportState.ts +22 -10
  53. package/apps/agents-server/src/components/_utils/generateMetaTxt.ts +1 -1
  54. package/apps/agents-server/src/database/migratePrefix.ts +13 -12
  55. package/apps/agents-server/src/database/migrations/2026-08-0100-agent-goal-chat-source.sql +10 -0
  56. package/apps/agents-server/src/generated/reservedPaths.ts +42 -42
  57. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +9 -1
  58. package/apps/agents-server/src/languages/translations/czech.yaml +9 -1
  59. package/apps/agents-server/src/languages/translations/english.yaml +9 -1
  60. package/apps/agents-server/src/message-providers/email/stalwart/parseInboundStalwartEmail.ts +17 -2
  61. package/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.ts +9 -0
  62. package/apps/agents-server/src/utils/agentGoalChat/agentGoalChatIdentity.ts +28 -0
  63. package/apps/agents-server/src/utils/agentGoalChat/appendAgentGoalChatNote.ts +43 -0
  64. package/apps/agents-server/src/utils/agentGoalChat/canAccessAgentGoalChat.ts +14 -0
  65. package/apps/agents-server/src/utils/agentGoalChat/createAgentGoalChatNoteContent.ts +69 -0
  66. package/apps/agents-server/src/utils/agentGoalChat/ensureAgentGoalChat.ts +66 -0
  67. package/apps/agents-server/src/utils/agentGoalChat/prependAgentGoalChatSummarySeed.ts +40 -0
  68. package/apps/agents-server/src/utils/agentGoalChat/recordAgentGoalChatLifecycleNote.ts +36 -0
  69. package/apps/agents-server/src/utils/agentGoalChat/resolveAgentGoalChatOwnerUserId.ts +104 -0
  70. package/apps/agents-server/src/utils/agentGoalChat/scheduleAgentGoalChatModifiedNote.ts +82 -0
  71. package/apps/agents-server/src/utils/agentGoalChat.ts +19 -0
  72. package/apps/agents-server/src/utils/agentProjects/AgentProjectInfo.ts +10 -1
  73. package/apps/agents-server/src/utils/agentProjects/AgentProjectReferenceInfo.ts +5 -2
  74. package/apps/agents-server/src/utils/agentProjects/agentProjectFileNames.ts +21 -0
  75. package/apps/agents-server/src/utils/agentProjects/agentProjectHrefs.ts +20 -11
  76. package/apps/agents-server/src/utils/agentProjects/agentProjectsPaths.ts +15 -5
  77. package/apps/agents-server/src/utils/agentProjects/createAgentProjectInitials.ts +36 -0
  78. package/apps/agents-server/src/utils/agentProjects/createAgentProjectMarkdownReferences.ts +11 -9
  79. package/apps/agents-server/src/utils/agentProjects/createAgentProjectsDnsRecordsSection.ts +113 -0
  80. package/apps/agents-server/src/utils/agentProjects/createStaticAgentProjectServer.ts +2 -6
  81. package/apps/agents-server/src/utils/agentProjects/humanizeAgentProjectName.ts +25 -0
  82. package/apps/agents-server/src/utils/agentProjects/listAgentProjectChatReferences.ts +10 -3
  83. package/apps/agents-server/src/utils/agentProjects/parseAgentProjectIndexHtml.ts +167 -0
  84. package/apps/agents-server/src/utils/agentProjects/readAgentProjectIndexHtmlProfile.ts +24 -0
  85. package/apps/agents-server/src/utils/agentProjects/readAgentProjectReadme.ts +3 -53
  86. package/apps/agents-server/src/utils/agentProjects/readAgentProjectRootTextFile.ts +73 -0
  87. package/apps/agents-server/src/utils/agentProjects/resolveAgentProjectFaviconRelativePath.ts +108 -0
  88. package/apps/agents-server/src/utils/agentProjects/resolveAgentProjectInfo.ts +12 -3
  89. package/apps/agents-server/src/utils/agentProjects/{resolveAgentProjectReadmeProfile.ts → resolveAgentProjectProfile.ts} +33 -33
  90. package/apps/agents-server/src/utils/agentProjects/resolveAgentProjectPublicUrls.ts +34 -0
  91. package/apps/agents-server/src/utils/bookLanguageDocumentation/createBookLanguageDocumentationPdfResponse.ts +19 -17
  92. package/apps/agents-server/src/utils/chatTasksAdmin.ts +9 -0
  93. package/apps/agents-server/src/utils/dnsRecords/DnsRecordInstruction.ts +20 -0
  94. package/apps/agents-server/src/utils/dnsRecords/DnsRecordsSection.ts +77 -0
  95. package/apps/agents-server/src/utils/dnsRecords/createServerDnsRecordsSections.ts +58 -0
  96. package/apps/agents-server/src/utils/dnsRecords/createServerDomainDnsRecordsSection.ts +48 -0
  97. package/apps/agents-server/src/utils/dnsRecords/dnsRecordGroups.ts +52 -0
  98. package/apps/agents-server/src/utils/dnsRecords/resolveDnsRecordBatchPlan.ts +47 -1
  99. package/apps/agents-server/src/utils/getAdminChatTasksResponse/getAdminChatTasks/adminChatTaskSqlQuery.ts +41 -36
  100. package/apps/agents-server/src/utils/getAdminChatTasksResponse/getAdminChatTasks/mapAdminChatTaskFallbackRows.ts +3 -0
  101. package/apps/agents-server/src/utils/getAdminChatTasksResponse/getAdminChatTasks/mapAdminChatTaskSqlRows.ts +2 -0
  102. package/apps/agents-server/src/utils/localChatRunner/parseLocalTeamConversations.ts +281 -0
  103. package/apps/agents-server/src/utils/localChatRunner/persistLocalTeamConversations.ts +71 -0
  104. package/apps/agents-server/src/utils/localChatRunner/prepareLocalTeamConversationWorkspace.ts +233 -0
  105. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +37 -6
  106. package/apps/agents-server/src/utils/serverManagement/createManagedServer/insertManagedServerRegistryRow.ts +2 -2
  107. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerCoreAgents.ts +7 -6
  108. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerDefaultAgents.ts +5 -4
  109. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerMetadata.ts +3 -2
  110. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerUsers.ts +3 -2
  111. package/apps/agents-server/src/utils/serverManagement/deleteManagedServer.ts +2 -2
  112. package/apps/agents-server/src/utils/stalwart/createEmailDnsRecordsSection.ts +53 -0
  113. package/apps/agents-server/src/utils/userChat/UserChatRecord.ts +13 -0
  114. package/apps/agents-server/src/utils/userChat/UserChatSource.ts +14 -0
  115. package/apps/agents-server/src/utils/userChat/createUserChatSummary.ts +10 -2
  116. package/apps/agents-server/src/utils/userChat/finalizeUserChatJob.ts +3 -2
  117. package/apps/agents-server/src/utils/userChat/getUserChat.ts +16 -1
  118. package/apps/agents-server/src/utils/userChat/getUserChatForJobRunner.ts +31 -0
  119. package/apps/agents-server/src/utils/userChat/listUserChats.ts +113 -110
  120. package/apps/agents-server/src/utils/userChat/persistFrozenUserChat.ts +21 -6
  121. package/apps/agents-server/src/utils/userChat/runImmediateUserChatAnswer.ts +2 -6
  122. package/apps/agents-server/src/utils/userChat/runUserChatJob.ts +2 -6
  123. package/apps/agents-server/src/utils/userChat/teamMemberUserChatContext.ts +63 -0
  124. package/apps/agents-server/src/utils/userChat.ts +5 -1
  125. package/apps/agents-server/src/utils/userChatClient/cancelAgentUserTimeout.ts +2 -2
  126. package/apps/agents-server/src/utils/userChatClient/fetchAgentUserTimeouts.ts +2 -2
  127. package/apps/agents-server/src/utils/userChatClient.ts +9 -31
  128. package/apps/agents-server/src/utils/userChatTimeout/UserChatTimeoutRecord.ts +10 -2
  129. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore/claimNextDueUserChatTimeout.ts +28 -25
  130. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore/getAgentScopedUserChatTimeout.ts +8 -4
  131. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore/listAgentUserChatTimeouts.ts +4 -1
  132. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore/recoverExpiredRunningUserChatTimeouts.ts +3 -2
  133. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutWorker.ts +53 -0
  134. package/esm/index.es.js +1534 -401
  135. package/esm/index.es.js.map +1 -1
  136. package/esm/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +7 -1
  137. package/esm/scripts/run-agent-messages/messages/buildAgentTeamPromptSection.d.ts +12 -0
  138. package/esm/scripts/run-agent-messages/messages/finalizeAgentTeamConversationWorkspace.d.ts +16 -0
  139. package/esm/scripts/run-agent-messages/messages/loadAgentTeamConversationWorkspace.d.ts +6 -0
  140. package/esm/scripts/run-codex-prompts/common/normalizeLineEndingsInChangedFiles.d.ts +2 -3
  141. package/esm/scripts/run-codex-prompts/git/coderCommitScope.d.ts +31 -0
  142. package/esm/scripts/run-codex-prompts/git/coderGitSync.d.ts +15 -2
  143. package/esm/scripts/run-codex-prompts/git/commitChanges.d.ts +9 -3
  144. package/esm/scripts/run-codex-prompts/git/workingTreeChanges.d.ts +40 -0
  145. package/esm/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
  146. package/esm/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +5 -5
  147. package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiTerminalFrameUpdate.d.ts +27 -0
  148. package/esm/src/avatars/renderAvatarVisualTerminalText.d.ts +80 -0
  149. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +54 -0
  150. package/esm/src/book-3.0/AgentTeamConversationWorkspace.d.ts +85 -0
  151. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +3 -2
  152. package/esm/src/book-components/Chat/utils/renderMarkdown.d.ts +12 -3
  153. package/esm/src/cli/cli-commands/coder/boilerplateCount.d.ts +1 -1
  154. package/esm/src/cli/cli-commands/coder/generate-boilerplates.d.ts +1 -1
  155. package/esm/src/cli/cli-commands/common/{harness/$askForHarnessInstallationApproval.d.ts → npm/$askForNpmPackageInstallationApproval.d.ts} +2 -2
  156. package/{umd/src/cli/cli-commands/common/harness/$resolveLatestHarnessVersion.d.ts → esm/src/cli/cli-commands/common/npm/$resolveLatestNpmPackageVersion.d.ts} +2 -3
  157. package/esm/src/cli/cli-commands/common/npm/extractNpmPackageVersionFromOutput.d.ts +12 -0
  158. package/esm/src/cli/cli-commands/common/npm/isNpmPackageVersionOutdated.d.ts +9 -0
  159. package/esm/src/cli/cli-commands/common/promptbook-cli/$checkPromptbookCliInstallations.d.ts +9 -0
  160. package/esm/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.d.ts +14 -0
  161. package/esm/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.test.d.ts +1 -0
  162. package/esm/src/cli/cli-commands/common/promptbook-cli/$resolvePromptbookCliInstallations.d.ts +12 -0
  163. package/esm/src/cli/cli-commands/common/promptbook-cli/$resolvePromptbookCliInstallations.test.d.ts +1 -0
  164. package/esm/src/cli/cli-commands/common/promptbook-cli/$updatePromptbookCliInstallation.d.ts +10 -0
  165. package/esm/src/cli/cli-commands/common/promptbook-cli/PromptbookCliInstallation.d.ts +39 -0
  166. package/esm/src/cli/cli-commands/common/promptbook-cli/PromptbookCliInstallationStatus.d.ts +26 -0
  167. package/esm/src/cli/cli-commands/common/promptbook-cli/buildPromptbookCliInstallCommand.d.ts +7 -0
  168. package/esm/src/cli/cli-commands/common/promptbook-cli/buildPromptbookCliInstallCommand.test.d.ts +1 -0
  169. package/esm/src/cli/cli-commands/common/promptbook-cli/formatPromptbookCliInstallationWarning.d.ts +7 -0
  170. package/esm/src/utils/agent-message-runtime/resolveAgentMessageRuntimeActivity.d.ts +3 -3
  171. package/esm/src/utils/agents/terminalAgentAvatarVisual.d.ts +5 -2
  172. package/esm/src/utils/ascii-art/convertImageDataToAsciiArt.d.ts +6 -0
  173. package/esm/src/utils/ascii-art/createAnsiColorCode.d.ts +46 -0
  174. package/esm/src/utils/misc/debounce.d.ts +4 -2
  175. package/esm/src/utils/misc/debounce.test.d.ts +1 -0
  176. package/esm/src/version.d.ts +1 -1
  177. package/package.json +1 -1
  178. package/src/avatars/renderAvatarVisualTerminalText.ts +249 -0
  179. package/src/avatars/types/AvatarVisualDefinition.ts +60 -0
  180. package/src/avatars/visuals/asciiOctopusAvatarVisual.ts +144 -22
  181. package/src/book-2.0/book-language-documentation/createStandaloneBookLanguageMarkdown.ts +4 -4
  182. package/src/book-2.0/book-language-documentation/renderCommitmentCatalogSection.ts +1 -4
  183. package/src/book-2.0/book-language-documentation/renderGroupedCommitmentDocumentationMarkdown.ts +4 -17
  184. package/src/book-3.0/AgentTeamConversationWorkspace.ts +172 -0
  185. package/src/book-components/BookEditor/useBookEditorMonacoStyles.ts +56 -55
  186. package/src/book-components/Chat/Chat/Chat.module.css +1 -8
  187. package/src/book-components/Chat/Chat/ChatMessageMap.tsx +8 -4
  188. package/src/book-components/Chat/Chat/ChatProps.tsx +3 -2
  189. package/src/book-components/Chat/MarkdownContent/MarkdownContent.module.css +14 -14
  190. package/src/book-components/Chat/save/react/exports/chat-preview-2025-10-13 (1).jsx +95 -5
  191. package/src/book-components/Chat/utils/renderMarkdown.ts +535 -89
  192. package/src/cli/cli-commands/coder/add.ts +3 -3
  193. package/src/cli/cli-commands/coder/boilerplateCount.ts +1 -1
  194. package/src/cli/cli-commands/coder/generate-boilerplates.ts +27 -27
  195. package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +5 -4
  196. package/src/cli/cli-commands/coder/init.ts +3 -3
  197. package/src/cli/cli-commands/coder/run.ts +10 -2
  198. package/src/cli/cli-commands/common/coderGitSyncCliOptions.ts +6 -2
  199. package/src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.ts +2 -2
  200. package/src/cli/cli-commands/common/harness/$checkHarnessInstallation.ts +4 -4
  201. package/src/cli/cli-commands/common/harness/$resolveInstalledHarnessVersion.ts +2 -2
  202. package/src/cli/cli-commands/common/{harness/$askForHarnessInstallationApproval.ts → npm/$askForNpmPackageInstallationApproval.ts} +3 -3
  203. package/src/cli/cli-commands/common/npm/$resolveLatestNpmPackageVersion.ts +49 -0
  204. package/src/cli/cli-commands/common/npm/extractNpmPackageVersionFromOutput.ts +25 -0
  205. package/src/cli/cli-commands/common/{harness/isHarnessVersionOutdated.ts → npm/isNpmPackageVersionOutdated.ts} +6 -4
  206. package/src/cli/cli-commands/common/projectInitialization.ts +10 -1
  207. package/src/cli/cli-commands/common/promptbook-cli/$checkPromptbookCliInstallations.ts +73 -0
  208. package/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.ts +94 -0
  209. package/src/cli/cli-commands/common/promptbook-cli/$resolvePromptbookCliInstallations.ts +223 -0
  210. package/src/cli/cli-commands/common/promptbook-cli/$updatePromptbookCliInstallation.ts +53 -0
  211. package/src/cli/cli-commands/common/promptbook-cli/PromptbookCliInstallation.ts +46 -0
  212. package/src/cli/cli-commands/common/promptbook-cli/PromptbookCliInstallationStatus.ts +32 -0
  213. package/src/cli/cli-commands/common/promptbook-cli/buildPromptbookCliInstallCommand.ts +22 -0
  214. package/src/cli/cli-commands/common/promptbook-cli/formatPromptbookCliInstallationWarning.ts +47 -0
  215. package/src/commands/FORMAT/formatCommandParser.ts +2 -2
  216. package/src/llm-providers/openai/utils/buildToolInvocationScript.ts +21 -18
  217. package/src/other/templates/getTemplatesPipelineCollection.ts +976 -668
  218. package/src/utils/agent-message-runtime/resolveAgentMessageRuntimeActivity.ts +80 -27
  219. package/src/utils/agents/terminalAgentAvatarVisual.ts +31 -3
  220. package/src/utils/ascii-art/convertImageDataToAsciiArt.ts +9 -109
  221. package/src/utils/ascii-art/createAnsiColorCode.ts +132 -0
  222. package/src/utils/misc/debounce.ts +26 -5
  223. package/src/version.ts +2 -2
  224. package/src/versions.txt +2 -0
  225. package/umd/index.umd.js +1533 -400
  226. package/umd/index.umd.js.map +1 -1
  227. package/umd/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +7 -1
  228. package/umd/scripts/run-agent-messages/messages/buildAgentTeamPromptSection.d.ts +12 -0
  229. package/umd/scripts/run-agent-messages/messages/finalizeAgentTeamConversationWorkspace.d.ts +16 -0
  230. package/umd/scripts/run-agent-messages/messages/loadAgentTeamConversationWorkspace.d.ts +6 -0
  231. package/umd/scripts/run-codex-prompts/common/normalizeLineEndingsInChangedFiles.d.ts +2 -3
  232. package/umd/scripts/run-codex-prompts/git/coderCommitScope.d.ts +31 -0
  233. package/umd/scripts/run-codex-prompts/git/coderGitSync.d.ts +15 -2
  234. package/umd/scripts/run-codex-prompts/git/commitChanges.d.ts +9 -3
  235. package/umd/scripts/run-codex-prompts/git/workingTreeChanges.d.ts +40 -0
  236. package/umd/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
  237. package/umd/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +5 -5
  238. package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiTerminalFrameUpdate.d.ts +27 -0
  239. package/umd/src/avatars/renderAvatarVisualTerminalText.d.ts +80 -0
  240. package/umd/src/avatars/renderAvatarVisualTerminalText.test.d.ts +1 -0
  241. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +54 -0
  242. package/umd/src/book-3.0/AgentTeamConversationWorkspace.d.ts +85 -0
  243. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +3 -2
  244. package/umd/src/book-components/Chat/utils/renderMarkdown.d.ts +12 -3
  245. package/umd/src/cli/cli-commands/coder/boilerplateCount.d.ts +1 -1
  246. package/umd/src/cli/cli-commands/coder/generate-boilerplates.d.ts +1 -1
  247. package/umd/src/cli/cli-commands/common/{harness/$askForHarnessInstallationApproval.d.ts → npm/$askForNpmPackageInstallationApproval.d.ts} +2 -2
  248. package/{esm/src/cli/cli-commands/common/harness/$resolveLatestHarnessVersion.d.ts → umd/src/cli/cli-commands/common/npm/$resolveLatestNpmPackageVersion.d.ts} +2 -3
  249. package/umd/src/cli/cli-commands/common/npm/$resolveLatestNpmPackageVersion.test.d.ts +1 -0
  250. package/umd/src/cli/cli-commands/common/npm/extractNpmPackageVersionFromOutput.d.ts +12 -0
  251. package/umd/src/cli/cli-commands/common/npm/extractNpmPackageVersionFromOutput.test.d.ts +1 -0
  252. package/umd/src/cli/cli-commands/common/npm/isNpmPackageVersionOutdated.d.ts +9 -0
  253. package/umd/src/cli/cli-commands/common/npm/isNpmPackageVersionOutdated.test.d.ts +1 -0
  254. package/umd/src/cli/cli-commands/common/promptbook-cli/$checkPromptbookCliInstallations.d.ts +9 -0
  255. package/umd/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.d.ts +14 -0
  256. package/umd/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.test.d.ts +1 -0
  257. package/umd/src/cli/cli-commands/common/promptbook-cli/$resolvePromptbookCliInstallations.d.ts +12 -0
  258. package/umd/src/cli/cli-commands/common/promptbook-cli/$resolvePromptbookCliInstallations.test.d.ts +1 -0
  259. package/umd/src/cli/cli-commands/common/promptbook-cli/$updatePromptbookCliInstallation.d.ts +10 -0
  260. package/umd/src/cli/cli-commands/common/promptbook-cli/PromptbookCliInstallation.d.ts +39 -0
  261. package/umd/src/cli/cli-commands/common/promptbook-cli/PromptbookCliInstallationStatus.d.ts +26 -0
  262. package/umd/src/cli/cli-commands/common/promptbook-cli/buildPromptbookCliInstallCommand.d.ts +7 -0
  263. package/umd/src/cli/cli-commands/common/promptbook-cli/buildPromptbookCliInstallCommand.test.d.ts +1 -0
  264. package/umd/src/cli/cli-commands/common/promptbook-cli/formatPromptbookCliInstallationWarning.d.ts +7 -0
  265. package/umd/src/utils/agent-message-runtime/resolveAgentMessageRuntimeActivity.d.ts +3 -3
  266. package/umd/src/utils/agents/terminalAgentAvatarVisual.d.ts +5 -2
  267. package/umd/src/utils/ascii-art/convertImageDataToAsciiArt.d.ts +6 -0
  268. package/umd/src/utils/ascii-art/createAnsiColorCode.d.ts +46 -0
  269. package/umd/src/utils/misc/debounce.d.ts +4 -2
  270. package/umd/src/utils/misc/debounce.test.d.ts +1 -0
  271. package/umd/src/version.d.ts +1 -1
  272. package/apps/agents-server/scripts/ignore-kill-eperm.js +0 -31
  273. package/apps/agents-server/src/app/agents/[agentName]/api/timeouts/actions/route.ts +0 -103
  274. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsClient.tsx +0 -43
  275. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsEditDialog.tsx +0 -98
  276. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsFiltersCard.tsx +0 -75
  277. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsHeader.tsx +0 -53
  278. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsSummaryMetrics.tsx +0 -70
  279. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsTableCard.tsx +0 -58
  280. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsTableRow.tsx +0 -242
  281. package/apps/agents-server/src/app/agents/[agentName]/timeouts/loading.tsx +0 -15
  282. package/apps/agents-server/src/app/agents/[agentName]/timeouts/page.tsx +0 -28
  283. package/apps/agents-server/src/app/agents/[agentName]/timeouts/useAgentTimeoutsClientState.ts +0 -769
  284. package/apps/agents-server/src/components/AgentProjectDnsInstructions/AgentProjectDnsInstructions.tsx +0 -182
  285. package/apps/agents-server/src/utils/userChatClient/runAgentUserTimeoutBulkAction.ts +0 -24
  286. package/apps/agents-server/src/utils/userChatClient/updateAgentUserTimeout.ts +0 -25
  287. package/esm/src/cli/cli-commands/common/harness/extractHarnessVersionFromOutput.d.ts +0 -11
  288. package/esm/src/cli/cli-commands/common/harness/isHarnessVersionOutdated.d.ts +0 -9
  289. package/src/cli/cli-commands/common/harness/$resolveLatestHarnessVersion.ts +0 -34
  290. package/src/cli/cli-commands/common/harness/extractHarnessVersionFromOutput.ts +0 -28
  291. package/umd/src/cli/cli-commands/common/harness/extractHarnessVersionFromOutput.d.ts +0 -11
  292. package/umd/src/cli/cli-commands/common/harness/isHarnessVersionOutdated.d.ts +0 -9
  293. /package/esm/src/{cli/cli-commands/common/harness/extractHarnessVersionFromOutput.test.d.ts → avatars/renderAvatarVisualTerminalText.test.d.ts} +0 -0
  294. /package/esm/src/cli/cli-commands/common/{harness/isHarnessVersionOutdated.test.d.ts → npm/$resolveLatestNpmPackageVersion.test.d.ts} +0 -0
  295. /package/{umd/src/cli/cli-commands/common/harness/extractHarnessVersionFromOutput.test.d.ts → esm/src/cli/cli-commands/common/npm/extractNpmPackageVersionFromOutput.test.d.ts} +0 -0
  296. /package/{umd/src/cli/cli-commands/common/harness/isHarnessVersionOutdated.test.d.ts → esm/src/cli/cli-commands/common/npm/isNpmPackageVersionOutdated.test.d.ts} +0 -0
package/esm/index.es.js CHANGED
@@ -10,7 +10,7 @@ import * as fs from 'fs';
10
10
  import { createWriteStream, mkdirSync, writeFileSync, statSync, readFileSync, existsSync, promises } from 'fs';
11
11
  import * as dotenv from 'dotenv';
12
12
  import * as readline from 'readline';
13
- import { emitKeypressEvents, clearLine, cursorTo, createInterface } from 'readline';
13
+ import { emitKeypressEvents, createInterface, cursorTo, clearLine } from 'readline';
14
14
  import { forTime, forEver } from 'waitasecond';
15
15
  import hexEncoder from 'crypto-js/enc-hex';
16
16
  import sha256 from 'crypto-js/sha256';
@@ -48,7 +48,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
48
48
  * @generated
49
49
  * @see https://github.com/webgptorg/promptbook
50
50
  */
51
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-5';
51
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-7';
52
52
  /**
53
53
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
54
54
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2583,7 +2583,10 @@ async function ensureProjectGitignoreFile({ projectPath, blockHeader, rules, })
2583
2583
  if (currentGitignoreContent !== undefined && missingRules.length === 0) {
2584
2584
  return 'unchanged';
2585
2585
  }
2586
- const nextGitignoreContent = appendBlock(currentGitignoreContent || '', [blockHeader, ...missingRules].join('\n'));
2586
+ const nextGitignoreContent = appendBlock(currentGitignoreContent || '', spaceTrim$1((block) => `
2587
+ ${block(blockHeader)}
2588
+ ${block(missingRules.join('\n'))}
2589
+ `));
2587
2590
  await writeFile(gitignorePath, nextGitignoreContent, 'utf-8');
2588
2591
  return currentGitignoreContent === undefined ? 'created' : 'updated';
2589
2592
  }
@@ -7453,6 +7456,15 @@ const OUTLINE_GLYPHS = ['#', '%', '@'];
7453
7456
  * @private helper of `asciiOctopusAvatarVisual`
7454
7457
  */
7455
7458
  const ATMOSPHERE_GLYPHS = ['.', ':', "'", '`'];
7459
+ /**
7460
+ * Coordinate space the octopus layout is generated in when it is painted into a terminal.
7461
+ *
7462
+ * The terminal renderer has its own character grid, so the geometry only needs one stable
7463
+ * square coordinate space which the grid is then sampled from.
7464
+ *
7465
+ * @private helper of `asciiOctopusAvatarVisual`
7466
+ */
7467
+ const TERMINAL_ASCII_OCTOPUS_LAYOUT_SIZE = DEFAULT_AVATAR_SIZE;
7456
7468
  /**
7457
7469
  * AsciiOctopus avatar visual.
7458
7470
  *
@@ -7469,41 +7481,115 @@ const asciiOctopusAvatarVisual = {
7469
7481
  const staticRandom = createRandom('ascii-octopus-static');
7470
7482
  const gridMetrics = createAsciiGridMetrics(size, gridRandom);
7471
7483
  const layout = createAsciiOctopusLayout(size, timeMs, createRandom, staticRandom, interaction);
7484
+ const glyphGrid = createAsciiOctopusGlyphGrid({
7485
+ sampleGrid: gridMetrics,
7486
+ layout,
7487
+ palette,
7488
+ cellRandom: createRandom('ascii-octopus-cells'),
7489
+ timeMs,
7490
+ });
7472
7491
  drawAvatarFrame(context, size, palette);
7473
7492
  drawAsciiBackdrop(context, size, palette, layout, timeMs);
7474
7493
  context.save();
7475
7494
  context.font = `600 ${gridMetrics.fontSize}px monospace`;
7476
7495
  context.textAlign = 'center';
7477
7496
  context.textBaseline = 'middle';
7478
- // The ASCII renderer samples the morphing octopus field on a low-resolution grid so the shape stays organic
7479
- // while the glyph layout remains deterministic for the same avatar input.
7480
- const cellRandom = createRandom('ascii-octopus-cells');
7481
7497
  for (let rowIndex = 0; rowIndex < gridMetrics.rowCount; rowIndex++) {
7482
7498
  for (let columnIndex = 0; columnIndex < gridMetrics.columnCount; columnIndex++) {
7483
- const point = {
7484
- x: gridMetrics.offsetX + columnIndex * gridMetrics.cellWidth,
7485
- y: gridMetrics.offsetY + rowIndex * gridMetrics.cellHeight,
7486
- };
7487
- const noise = cellRandom();
7488
- const glyphDescriptor = resolveAsciiGlyph({
7489
- point,
7490
- layout,
7491
- palette,
7492
- cellWidth: gridMetrics.cellWidth,
7493
- cellHeight: gridMetrics.cellHeight,
7494
- noise,
7495
- timeMs,
7496
- });
7499
+ const glyphDescriptor = glyphGrid[rowIndex][columnIndex];
7497
7500
  if (!glyphDescriptor) {
7498
7501
  continue;
7499
7502
  }
7503
+ const point = resolveAsciiSampleGridPoint(gridMetrics, columnIndex, rowIndex);
7500
7504
  context.fillStyle = glyphDescriptor.color;
7501
7505
  context.fillText(glyphDescriptor.character, point.x, point.y);
7502
7506
  }
7503
7507
  }
7504
7508
  context.restore();
7505
7509
  },
7510
+ renderTerminalText({ columns, rows, palette, createRandom, timeMs, interaction }) {
7511
+ const staticRandom = createRandom('ascii-octopus-static');
7512
+ const layout = createAsciiOctopusLayout(TERMINAL_ASCII_OCTOPUS_LAYOUT_SIZE, timeMs, createRandom, staticRandom, interaction);
7513
+ return createAsciiOctopusGlyphGrid({
7514
+ sampleGrid: createAsciiTerminalSampleGrid(columns, rows),
7515
+ layout,
7516
+ palette,
7517
+ cellRandom: createRandom('ascii-octopus-cells'),
7518
+ timeMs,
7519
+ });
7520
+ },
7506
7521
  };
7522
+ /**
7523
+ * Resolves the geometry point sampled for one cell of the grid.
7524
+ *
7525
+ * @param sampleGrid Character-cell sampling grid.
7526
+ * @param columnIndex Zero-based cell column.
7527
+ * @param rowIndex Zero-based cell row.
7528
+ * @returns Sampled point in the octopus coordinate space.
7529
+ *
7530
+ * @private helper of `asciiOctopusAvatarVisual`
7531
+ */
7532
+ function resolveAsciiSampleGridPoint(sampleGrid, columnIndex, rowIndex) {
7533
+ return {
7534
+ x: sampleGrid.offsetX + columnIndex * sampleGrid.cellWidth,
7535
+ y: sampleGrid.offsetY + rowIndex * sampleGrid.cellHeight,
7536
+ };
7537
+ }
7538
+ /**
7539
+ * Builds the sampling grid used when the octopus is painted straight into terminal character cells.
7540
+ *
7541
+ * @param columns Available terminal columns.
7542
+ * @param rows Available terminal rows.
7543
+ * @returns Character-cell sampling grid covering the whole octopus coordinate space.
7544
+ *
7545
+ * @private helper of `asciiOctopusAvatarVisual`
7546
+ */
7547
+ function createAsciiTerminalSampleGrid(columns, rows) {
7548
+ const cellWidth = TERMINAL_ASCII_OCTOPUS_LAYOUT_SIZE / columns;
7549
+ const cellHeight = TERMINAL_ASCII_OCTOPUS_LAYOUT_SIZE / rows;
7550
+ return {
7551
+ cellWidth,
7552
+ cellHeight,
7553
+ columnCount: columns,
7554
+ rowCount: rows,
7555
+ offsetX: cellWidth / 2,
7556
+ offsetY: cellHeight / 2,
7557
+ };
7558
+ }
7559
+ /**
7560
+ * Samples the morphing octopus field into one deterministic grid of ASCII glyphs.
7561
+ *
7562
+ * The field is sampled on a low-resolution grid so the shape stays organic while the glyph
7563
+ * layout remains deterministic for the same avatar input. Both the canvas visual and the
7564
+ * terminal visual share this single sampling pass so they always describe the same octopus.
7565
+ *
7566
+ * @param options Sampling grid, prepared octopus layout, palette, per-cell noise, and animation time.
7567
+ * @returns Resolved glyph of every sampled cell, where `null` means an empty cell.
7568
+ *
7569
+ * @private helper of `asciiOctopusAvatarVisual`
7570
+ */
7571
+ function createAsciiOctopusGlyphGrid(options) {
7572
+ const { sampleGrid, layout, palette, cellRandom, timeMs } = options;
7573
+ const glyphGrid = [];
7574
+ for (let rowIndex = 0; rowIndex < sampleGrid.rowCount; rowIndex++) {
7575
+ const glyphRow = [];
7576
+ for (let columnIndex = 0; columnIndex < sampleGrid.columnCount; columnIndex++) {
7577
+ const point = resolveAsciiSampleGridPoint(sampleGrid, columnIndex, rowIndex);
7578
+ const noise = cellRandom();
7579
+ glyphRow.push(resolveAsciiGlyph({
7580
+ point,
7581
+ layout,
7582
+ palette,
7583
+ cellWidth: sampleGrid.cellWidth,
7584
+ cellHeight: sampleGrid.cellHeight,
7585
+ noise,
7586
+ timeMs,
7587
+ }));
7588
+ }
7589
+ glyphGrid.push(glyphRow);
7590
+ }
7591
+ return glyphGrid;
7592
+ }
7507
7593
  /**
7508
7594
  * Draws the dark terminal-like glow behind the ASCII octopus.
7509
7595
  *
@@ -21750,7 +21836,7 @@ async function isAgentRunnerProject(projectPath) {
21750
21836
  return agentBookStats.isFile();
21751
21837
  }
21752
21838
  catch (error) {
21753
- if (isFileNotFoundError$4(error)) {
21839
+ if (isFileNotFoundError$6(error)) {
21754
21840
  return false;
21755
21841
  }
21756
21842
  throw error;
@@ -21759,7 +21845,7 @@ async function isAgentRunnerProject(projectPath) {
21759
21845
  /**
21760
21846
  * Returns true when one filesystem error indicates a missing path.
21761
21847
  */
21762
- function isFileNotFoundError$4(error) {
21848
+ function isFileNotFoundError$6(error) {
21763
21849
  return Boolean(error &&
21764
21850
  typeof error === 'object' &&
21765
21851
  'code' in error &&
@@ -21789,7 +21875,7 @@ async function readQueuedMessageDirectoryEntries(queuedMessagesDirectoryPath) {
21789
21875
  return await readdir(queuedMessagesDirectoryPath, { withFileTypes: true });
21790
21876
  }
21791
21877
  catch (error) {
21792
- if (isFileNotFoundError$3(error)) {
21878
+ if (isFileNotFoundError$5(error)) {
21793
21879
  return [];
21794
21880
  }
21795
21881
  throw error;
@@ -21810,7 +21896,7 @@ function normalizeRelativePath$2(relativePath) {
21810
21896
  /**
21811
21897
  * Returns true when an error is a missing-path filesystem error.
21812
21898
  */
21813
- function isFileNotFoundError$3(error) {
21899
+ function isFileNotFoundError$5(error) {
21814
21900
  return Boolean(error &&
21815
21901
  typeof error === 'object' &&
21816
21902
  'code' in error &&
@@ -22288,7 +22374,7 @@ async function runGitCommand(options) {
22288
22374
  lastIndexLockState = await readGitIndexLockState(cwd, options.env);
22289
22375
  if ((lastIndexLockState === null || lastIndexLockState === void 0 ? void 0 : lastIndexLockState.isStale) && !isStaleIndexLockRemoved) {
22290
22376
  await unlink(lastIndexLockState.path).catch((unlinkError) => {
22291
- if (isFileNotFoundError$2(unlinkError)) {
22377
+ if (isFileNotFoundError$4(unlinkError)) {
22292
22378
  return;
22293
22379
  }
22294
22380
  throw unlinkError;
@@ -22349,7 +22435,7 @@ async function readGitIndexLockState(cwd, env) {
22349
22435
  };
22350
22436
  }
22351
22437
  catch (error) {
22352
- if (isFileNotFoundError$2(error)) {
22438
+ if (isFileNotFoundError$4(error)) {
22353
22439
  return undefined;
22354
22440
  }
22355
22441
  throw error;
@@ -22406,7 +22492,7 @@ function formatDelay$1(delayMs) {
22406
22492
  /**
22407
22493
  * Detects missing-file errors while inspecting the lock file.
22408
22494
  */
22409
- function isFileNotFoundError$2(error) {
22495
+ function isFileNotFoundError$4(error) {
22410
22496
  return Boolean(error &&
22411
22497
  typeof error === 'object' &&
22412
22498
  'code' in error &&
@@ -27302,66 +27388,125 @@ function renderAvatarVisual(options, resolvedAvatarRenderDefinition) {
27302
27388
  });
27303
27389
  }
27304
27390
 
27391
+ // Note: [💞] Ignore a discrepancy between file name and entity name
27305
27392
  /**
27306
- * Default alpha channel value below which a half-cell is rendered as terminal background.
27393
+ * ANSI escape sequence that resets all colors and attributes.
27307
27394
  *
27308
27395
  * @private within the repository
27309
27396
  */
27310
- const DEFAULT_ALPHA_THRESHOLD = 32;
27397
+ const ANSI_RESET = '\u001b[0m';
27311
27398
  /**
27312
- * Number of channels per pixel in an RGBA buffer.
27399
+ * Maximum spread between RGB channels for a color to be treated as (nearly) achromatic gray.
27313
27400
  *
27314
27401
  * @private within the repository
27315
27402
  */
27316
- const RGBA_CHANNEL_COUNT = 4;
27403
+ const ANSI_256_ACHROMATIC_CHANNEL_SPREAD = 12;
27317
27404
  /**
27318
- * Upper half block character - foreground paints the top pixel, background paints the bottom pixel.
27405
+ * Gray level above which an achromatic color maps to the pure white color-cube entry.
27319
27406
  *
27320
27407
  * @private within the repository
27321
27408
  */
27322
- const UPPER_HALF_BLOCK = '▀'; // <- ▀
27409
+ const ANSI_256_NEAR_WHITE_GRAY_LEVEL = 246;
27323
27410
  /**
27324
- * Lower half block character - foreground paints the bottom pixel while the top pixel stays transparent.
27411
+ * Index of pure white inside the 6×6×6 ANSI color cube.
27325
27412
  *
27326
27413
  * @private within the repository
27327
27414
  */
27328
- const LOWER_HALF_BLOCK = '▄'; // <- ▄
27415
+ const ANSI_256_WHITE_INDEX = 231;
27329
27416
  /**
27330
- * ANSI escape sequence that resets all colors and attributes.
27417
+ * Brightness of the lightest entry of the ANSI 256 grayscale ramp.
27331
27418
  *
27332
27419
  * @private within the repository
27333
27420
  */
27334
- const ANSI_RESET = '\u001b[0m';
27421
+ const ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL = 238;
27335
27422
  /**
27336
- * Maximum spread between RGB channels for a color to be treated as (nearly) achromatic gray.
27423
+ * Number of grayscale ramp steps above its first entry (ANSI indexes 232-255).
27337
27424
  *
27338
27425
  * @private within the repository
27339
27426
  */
27340
- const ANSI_256_ACHROMATIC_CHANNEL_SPREAD = 12;
27427
+ const ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN = 23;
27341
27428
  /**
27342
- * Gray level above which an achromatic color maps to the pure white color-cube entry.
27429
+ * Creates the ANSI escape code that sets the foreground color of following characters.
27430
+ *
27431
+ * @param color Color painted onto the characters.
27432
+ * @param colorDepth Color depth supported by the target terminal.
27433
+ * @returns ANSI escape sequence.
27343
27434
  *
27344
27435
  * @private within the repository
27345
27436
  */
27346
- const ANSI_256_NEAR_WHITE_GRAY_LEVEL = 246;
27437
+ function createAnsiForegroundColorCode(color, colorDepth) {
27438
+ if (colorDepth === 'TRUE_COLOR') {
27439
+ return `\u001b[38;2;${color.red};${color.green};${color.blue}m`;
27440
+ }
27441
+ return `\u001b[38;5;${mapColorToAnsi256(color)}m`;
27442
+ }
27347
27443
  /**
27348
- * Index of pure white inside the 6×6×6 ANSI color cube.
27444
+ * Creates the ANSI escape code that sets the background color of following characters.
27445
+ *
27446
+ * @param color Color painted behind the characters.
27447
+ * @param colorDepth Color depth supported by the target terminal.
27448
+ * @returns ANSI escape sequence.
27349
27449
  *
27350
27450
  * @private within the repository
27351
27451
  */
27352
- const ANSI_256_WHITE_INDEX = 231;
27452
+ function createAnsiBackgroundColorCode(color, colorDepth) {
27453
+ if (colorDepth === 'TRUE_COLOR') {
27454
+ return `\u001b[48;2;${color.red};${color.green};${color.blue}m`;
27455
+ }
27456
+ return `\u001b[48;5;${mapColorToAnsi256(color)}m`;
27457
+ }
27353
27458
  /**
27354
- * Brightness of the lightest entry of the ANSI 256 grayscale ramp.
27459
+ * Maps a 24-bit color onto the closest entry of the 256-color ANSI palette.
27460
+ *
27461
+ * Uses the 6×6×6 color cube (entries 16-231) and the grayscale ramp (entries 232-255).
27462
+ *
27463
+ * @private helper of `createAnsiForegroundColorCode` and `createAnsiBackgroundColorCode`
27464
+ */
27465
+ function mapColorToAnsi256(color) {
27466
+ const { red, green, blue } = color;
27467
+ // Note: Prefer the finer grayscale ramp when the color is (nearly) achromatic
27468
+ const maxChannel = Math.max(red, green, blue);
27469
+ const minChannel = Math.min(red, green, blue);
27470
+ if (maxChannel - minChannel < ANSI_256_ACHROMATIC_CHANNEL_SPREAD) {
27471
+ const gray = Math.round((red + green + blue) / 3);
27472
+ if (gray < 4) {
27473
+ return 16; // <- Note: Pure black lives in the color cube
27474
+ }
27475
+ if (gray > ANSI_256_NEAR_WHITE_GRAY_LEVEL) {
27476
+ return ANSI_256_WHITE_INDEX; // <- Note: Pure white lives in the color cube
27477
+ }
27478
+ return 232 + Math.round(((gray - 8) / ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL) * ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN);
27479
+ }
27480
+ const redIndex = Math.round((red / 255) * 5);
27481
+ const greenIndex = Math.round((green / 255) * 5);
27482
+ const blueIndex = Math.round((blue / 255) * 5);
27483
+ return 16 + 36 * redIndex + 6 * greenIndex + blueIndex;
27484
+ }
27485
+
27486
+ /**
27487
+ * Default alpha channel value below which a cell is rendered as terminal background.
27355
27488
  *
27356
27489
  * @private within the repository
27357
27490
  */
27358
- const ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL = 238;
27491
+ const DEFAULT_ALPHA_THRESHOLD = 32;
27359
27492
  /**
27360
- * Number of grayscale ramp steps above its first entry (ANSI indexes 232-255).
27493
+ * Number of channels per pixel in an RGBA buffer.
27361
27494
  *
27362
27495
  * @private within the repository
27363
27496
  */
27364
- const ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN = 23;
27497
+ const RGBA_CHANNEL_COUNT = 4;
27498
+ /**
27499
+ * Upper half block character - foreground paints the top pixel, background paints the bottom pixel.
27500
+ *
27501
+ * @private within the repository
27502
+ */
27503
+ const UPPER_HALF_BLOCK = '▀'; // <- ▀
27504
+ /**
27505
+ * Lower half block character - foreground paints the bottom pixel while the top pixel stays transparent.
27506
+ *
27507
+ * @private within the repository
27508
+ */
27509
+ const LOWER_HALF_BLOCK = '▄'; // <- ▄
27365
27510
  /**
27366
27511
  * Converts raw RGBA image pixels into colored ASCII art for ANSI terminals.
27367
27512
  *
@@ -27410,17 +27555,17 @@ function convertImageDataToAsciiArt(options) {
27410
27555
  let nextBackgroundCode;
27411
27556
  if (topHalfColor.isOpaque && bottomHalfColor.isOpaque) {
27412
27557
  character = UPPER_HALF_BLOCK;
27413
- nextForegroundCode = createForegroundColorCode(topHalfColor, colorDepth);
27414
- nextBackgroundCode = createBackgroundColorCode(bottomHalfColor, colorDepth);
27558
+ nextForegroundCode = createAnsiForegroundColorCode(topHalfColor, colorDepth);
27559
+ nextBackgroundCode = createAnsiBackgroundColorCode(bottomHalfColor, colorDepth);
27415
27560
  }
27416
27561
  else if (topHalfColor.isOpaque) {
27417
27562
  character = UPPER_HALF_BLOCK;
27418
- nextForegroundCode = createForegroundColorCode(topHalfColor, colorDepth);
27563
+ nextForegroundCode = createAnsiForegroundColorCode(topHalfColor, colorDepth);
27419
27564
  nextBackgroundCode = undefined;
27420
27565
  }
27421
27566
  else if (bottomHalfColor.isOpaque) {
27422
27567
  character = LOWER_HALF_BLOCK;
27423
- nextForegroundCode = createForegroundColorCode(bottomHalfColor, colorDepth);
27568
+ nextForegroundCode = createAnsiForegroundColorCode(bottomHalfColor, colorDepth);
27424
27569
  nextBackgroundCode = undefined;
27425
27570
  }
27426
27571
  else {
@@ -27495,55 +27640,6 @@ function computeHalfCellColor(imageData, columnIndex, halfCellRowIndex, columns,
27495
27640
  isOpaque: true,
27496
27641
  };
27497
27642
  }
27498
- /**
27499
- * Creates the ANSI escape code that sets the foreground color of following characters.
27500
- *
27501
- * @private helper of `convertImageDataToAsciiArt`
27502
- */
27503
- function createForegroundColorCode(color, colorDepth) {
27504
- if (colorDepth === 'TRUE_COLOR') {
27505
- return `\u001b[38;2;${color.red};${color.green};${color.blue}m`;
27506
- }
27507
- return `\u001b[38;5;${mapColorToAnsi256(color)}m`;
27508
- }
27509
- /**
27510
- * Creates the ANSI escape code that sets the background color of following characters.
27511
- *
27512
- * @private helper of `convertImageDataToAsciiArt`
27513
- */
27514
- function createBackgroundColorCode(color, colorDepth) {
27515
- if (colorDepth === 'TRUE_COLOR') {
27516
- return `\u001b[48;2;${color.red};${color.green};${color.blue}m`;
27517
- }
27518
- return `\u001b[48;5;${mapColorToAnsi256(color)}m`;
27519
- }
27520
- /**
27521
- * Maps a 24-bit color onto the closest entry of the 256-color ANSI palette.
27522
- *
27523
- * Uses the 6×6×6 color cube (entries 16-231) and the grayscale ramp (entries 232-255).
27524
- *
27525
- * @private helper of `convertImageDataToAsciiArt`
27526
- */
27527
- function mapColorToAnsi256(color) {
27528
- const { red, green, blue } = color;
27529
- // Note: Prefer the finer grayscale ramp when the color is (nearly) achromatic
27530
- const maxChannel = Math.max(red, green, blue);
27531
- const minChannel = Math.min(red, green, blue);
27532
- if (maxChannel - minChannel < ANSI_256_ACHROMATIC_CHANNEL_SPREAD) {
27533
- const gray = Math.round((red + green + blue) / 3);
27534
- if (gray < 4) {
27535
- return 16; // <- Note: Pure black lives in the color cube
27536
- }
27537
- if (gray > ANSI_256_NEAR_WHITE_GRAY_LEVEL) {
27538
- return ANSI_256_WHITE_INDEX; // <- Note: Pure white lives in the color cube
27539
- }
27540
- return 232 + Math.round(((gray - 8) / ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL) * ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN);
27541
- }
27542
- const redIndex = Math.round((red / 255) * 5);
27543
- const greenIndex = Math.round((green / 255) * 5);
27544
- const blueIndex = Math.round((blue / 255) * 5);
27545
- return 16 + 36 * redIndex + 6 * greenIndex + blueIndex;
27546
- }
27547
27643
 
27548
27644
  /**
27549
27645
  * Default output width of the ASCII avatar in terminal character cells.
@@ -27673,6 +27769,143 @@ function assertPositiveCanvasDimension(value, dimensionName) {
27673
27769
  `));
27674
27770
  }
27675
27771
 
27772
+ // Note: [💞] Ignore a discrepancy between file name and entity name
27773
+ /**
27774
+ * Height of one terminal character cell relative to its width.
27775
+ *
27776
+ * Terminal fonts are roughly twice as tall as they are wide, so a square avatar needs
27777
+ * twice as many columns as rows to keep its proportions.
27778
+ *
27779
+ * @private within the repository
27780
+ */
27781
+ const TERMINAL_CHARACTER_CELL_ASPECT_RATIO = 2;
27782
+ /**
27783
+ * Renders one frame of a character-based avatar visual straight into terminal character cells.
27784
+ *
27785
+ * Visuals which are themselves made of characters, for example `AsciiOctopus`, lose their identity
27786
+ * when they are rasterized onto a canvas and converted back into half-block ASCII art - every glyph
27787
+ * is averaged away into a colored blob. Such visuals expose `renderTerminalText`, which paints the
27788
+ * terminal grid directly and is used instead of the raster pipeline.
27789
+ *
27790
+ * The square avatar is centered inside the requested grid the same way `renderAvatarVisualAsciiArt`
27791
+ * centers the avatar canvas inside a wider terminal frame.
27792
+ *
27793
+ * @param options Avatar identity, visual selection, and output grid size.
27794
+ * @returns Grid of `rows` rows of `columns` cells, or `null` when the visual has no terminal renderer.
27795
+ *
27796
+ * @private within the repository
27797
+ */
27798
+ function renderAvatarVisualTerminalTextGrid(options) {
27799
+ const resolvedRenderDefinition = options.resolvedAvatarRenderDefinition ||
27800
+ resolveAvatarRenderDefinition({
27801
+ avatarDefinition: options.avatarDefinition,
27802
+ visualId: options.visualId,
27803
+ surface: options.surface,
27804
+ });
27805
+ const { renderTerminalText } = resolvedRenderDefinition.avatarVisual;
27806
+ if (renderTerminalText === undefined) {
27807
+ return null;
27808
+ }
27809
+ const avatarColumnCount = Math.min(options.columns, options.rows * TERMINAL_CHARACTER_CELL_ASPECT_RATIO);
27810
+ const avatarRowCount = Math.min(options.rows, Math.round(options.columns / TERMINAL_CHARACTER_CELL_ASPECT_RATIO));
27811
+ if (avatarColumnCount <= 0 || avatarRowCount <= 0) {
27812
+ return null;
27813
+ }
27814
+ const avatarGrid = renderTerminalText({
27815
+ columns: avatarColumnCount,
27816
+ rows: avatarRowCount,
27817
+ timeMs: options.timeMs,
27818
+ avatarDefinition: resolvedRenderDefinition.avatarDefinition,
27819
+ palette: resolvedRenderDefinition.palette,
27820
+ createRandom: resolvedRenderDefinition.createRandom,
27821
+ interaction: createIdleAvatarInteractionState(),
27822
+ });
27823
+ return centerAvatarVisualTerminalTextGrid(avatarGrid, options.columns, options.rows);
27824
+ }
27825
+ /**
27826
+ * Renders one frame of a character-based avatar visual into ANSI-colored terminal lines.
27827
+ *
27828
+ * @param options Avatar identity, visual selection, output grid size, and ANSI color depth.
27829
+ * @returns One ANSI-colored string per output row, or `null` when the visual has no terminal renderer.
27830
+ *
27831
+ * @private within the repository
27832
+ */
27833
+ function renderAvatarVisualTerminalTextLines(options) {
27834
+ const terminalTextGrid = renderAvatarVisualTerminalTextGrid(options);
27835
+ if (terminalTextGrid === null) {
27836
+ return null;
27837
+ }
27838
+ return terminalTextGrid.map((terminalTextRow) => buildAvatarVisualTerminalTextLine(terminalTextRow, options.colorDepth || 'TRUE_COLOR'));
27839
+ }
27840
+ /**
27841
+ * Converts one avatar cell color into the opaque terminal color it is painted with.
27842
+ *
27843
+ * Terminal cells cannot be blended, so the cell alpha is composited onto the dark terminal
27844
+ * background and fully transparent cells keep the terminal background instead.
27845
+ *
27846
+ * @param color Cell color as a CSS color string, optionally with an alpha channel.
27847
+ * @returns Opaque terminal color or `null` when the cell should stay empty.
27848
+ *
27849
+ * @private helper of `renderAvatarVisualTerminalTextLines`
27850
+ */
27851
+ function resolveAvatarVisualTerminalTextColor(color) {
27852
+ const parsedColor = Color.fromSafe(color);
27853
+ if (parsedColor.alpha < DEFAULT_ALPHA_THRESHOLD) {
27854
+ return null;
27855
+ }
27856
+ const opacity = parsedColor.alpha / 255;
27857
+ return {
27858
+ red: Math.round(parsedColor.red * opacity),
27859
+ green: Math.round(parsedColor.green * opacity),
27860
+ blue: Math.round(parsedColor.blue * opacity),
27861
+ };
27862
+ }
27863
+ /**
27864
+ * Places the square avatar grid into the center of the requested terminal grid.
27865
+ *
27866
+ * @private helper of `renderAvatarVisualTerminalTextGrid`
27867
+ */
27868
+ function centerAvatarVisualTerminalTextGrid(avatarGrid, columns, rows) {
27869
+ var _a;
27870
+ const avatarRowCount = avatarGrid.length;
27871
+ const avatarColumnCount = ((_a = avatarGrid[0]) === null || _a === void 0 ? void 0 : _a.length) || 0;
27872
+ const leftCellCount = Math.floor((columns - avatarColumnCount) / 2);
27873
+ const topRowCount = Math.floor((rows - avatarRowCount) / 2);
27874
+ return Array.from({ length: rows }, (_, rowIndex) => {
27875
+ const avatarRow = avatarGrid[rowIndex - topRowCount];
27876
+ return Array.from({ length: columns }, (__, columnIndex) => (avatarRow === null || avatarRow === void 0 ? void 0 : avatarRow[columnIndex - leftCellCount]) || null);
27877
+ });
27878
+ }
27879
+ /**
27880
+ * Builds one ANSI-colored terminal line from resolved character cells.
27881
+ *
27882
+ * @private helper of `renderAvatarVisualTerminalTextLines`
27883
+ */
27884
+ function buildAvatarVisualTerminalTextLine(terminalTextRow, colorDepth) {
27885
+ let line = '';
27886
+ let currentForegroundCode = undefined;
27887
+ for (const terminalTextCell of terminalTextRow) {
27888
+ const cellColor = terminalTextCell === null ? null : resolveAvatarVisualTerminalTextColor(terminalTextCell.color);
27889
+ const nextForegroundCode = cellColor === null ? undefined : createAnsiForegroundColorCode(cellColor, colorDepth);
27890
+ if (nextForegroundCode !== currentForegroundCode) {
27891
+ // Note: A reset is required whenever a previously set color must be cleared,
27892
+ // otherwise a stale color would bleed into the following empty cells.
27893
+ if (nextForegroundCode === undefined) {
27894
+ line += ANSI_RESET;
27895
+ }
27896
+ else {
27897
+ line += nextForegroundCode;
27898
+ }
27899
+ currentForegroundCode = nextForegroundCode;
27900
+ }
27901
+ line += cellColor === null || terminalTextCell === null ? ' ' : terminalTextCell.character;
27902
+ }
27903
+ if (currentForegroundCode !== undefined) {
27904
+ line += ANSI_RESET;
27905
+ }
27906
+ return line;
27907
+ }
27908
+
27676
27909
  /**
27677
27910
  * Default built-in avatar visual used when an agent does not define `META IMAGE`, `META AVATAR`, or `META VISUAL`.
27678
27911
  *
@@ -27791,9 +28024,30 @@ function resolveTerminalAgentAvatarVisualIdFromAgentBasicInformation(agentBasicI
27791
28024
  /**
27792
28025
  * Renders one terminal avatar frame through the shared avatar-to-ASCII pipeline.
27793
28026
  *
28027
+ * Visuals which are made of characters themselves, for example `AsciiOctopus`, paint the terminal
28028
+ * character grid directly, because rasterizing them into half-block ASCII art averages every glyph
28029
+ * away and makes them indistinguishable from the other blob-shaped visuals.
28030
+ *
27794
28031
  * @private shared helper for terminal avatar rendering
27795
28032
  */
27796
28033
  function renderTerminalAgentAvatarVisualFrame(options) {
28034
+ const terminalTextLines = renderAvatarVisualTerminalTextLines({
28035
+ avatarDefinition: options.avatarDefinition,
28036
+ visualId: options.avatarVisualId,
28037
+ surface: 'transparent',
28038
+ columns: TERMINAL_AGENT_AVATAR_VISUAL_COLUMNS,
28039
+ rows: TERMINAL_AGENT_AVATAR_VISUAL_ROWS,
28040
+ colorDepth: options.colorDepth,
28041
+ timeMs: options.animationTimeMs,
28042
+ resolvedAvatarRenderDefinition: options.resolvedAvatarRenderDefinition,
28043
+ });
28044
+ if (terminalTextLines !== null) {
28045
+ return terminalTextLines;
28046
+ }
28047
+ if (options.createCanvas === undefined) {
28048
+ // Note: A pixel-based visual cannot be rasterized without a canvas, so the caller keeps its default banner
28049
+ return [];
28050
+ }
27797
28051
  return renderAvatarVisualAsciiArt({
27798
28052
  avatarDefinition: options.avatarDefinition,
27799
28053
  visualId: options.avatarVisualId,
@@ -28046,6 +28300,134 @@ function formatPromptRunCount(count) {
28046
28300
  return `${count} prompt run${count === 1 ? '' : 's'}`;
28047
28301
  }
28048
28302
 
28303
+ /**
28304
+ * ANSI sequence that clears the current terminal row.
28305
+ *
28306
+ * @private internal constant of coder run UI
28307
+ */
28308
+ const CLEAR_CURRENT_LINE = '\x1b[2K';
28309
+ /**
28310
+ * ANSI control character that moves the cursor to the start of its current row.
28311
+ *
28312
+ * @private internal constant of coder run UI
28313
+ */
28314
+ const MOVE_CURSOR_TO_LINE_START = '\r';
28315
+ /**
28316
+ * Builds one atomic ANSI update that changes the displayed coder-run terminal frame.
28317
+ *
28318
+ * A change in frame height requires a full rewrite to reserve or clear rows correctly. Frames with
28319
+ * the same height only rewrite contiguous groups of changed rows, returning to the frame bottom
28320
+ * between groups so each group can be assembled into the same output payload safely.
28321
+ *
28322
+ * @returns One terminal output payload, or `undefined` when the frame is already up to date.
28323
+ *
28324
+ * @private internal utility of coder run UI
28325
+ */
28326
+ function buildCoderRunUiTerminalFrameUpdate(options) {
28327
+ const { previousFrameLines, nextFrameLines } = options;
28328
+ if (previousFrameLines.length === 0 && nextFrameLines.length === 0) {
28329
+ return undefined;
28330
+ }
28331
+ if (previousFrameLines.length === 0 || previousFrameLines.length !== nextFrameLines.length) {
28332
+ return buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
28333
+ }
28334
+ return buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines);
28335
+ }
28336
+ /**
28337
+ * Builds an ANSI update which completely rewrites the reserved terminal frame area.
28338
+ *
28339
+ * @private helper of `buildCoderRunUiTerminalFrameUpdate`
28340
+ */
28341
+ function buildFullCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
28342
+ var _a;
28343
+ const previousFrameLineCount = previousFrameLines.length;
28344
+ const lineCountToRewrite = Math.max(previousFrameLineCount, nextFrameLines.length);
28345
+ const outputParts = [];
28346
+ if (previousFrameLineCount > 1) {
28347
+ outputParts.push(moveCursorUp(previousFrameLineCount - 1));
28348
+ }
28349
+ for (let lineIndex = 0; lineIndex < lineCountToRewrite; lineIndex++) {
28350
+ outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, (_a = nextFrameLines[lineIndex]) !== null && _a !== void 0 ? _a : '');
28351
+ if (lineIndex < lineCountToRewrite - 1) {
28352
+ outputParts.push('\n');
28353
+ }
28354
+ }
28355
+ const clearedTrailingLineCount = lineCountToRewrite - nextFrameLines.length;
28356
+ if (clearedTrailingLineCount > 0) {
28357
+ outputParts.push(moveCursorUp(clearedTrailingLineCount));
28358
+ }
28359
+ outputParts.push(MOVE_CURSOR_TO_LINE_START);
28360
+ return outputParts.join('');
28361
+ }
28362
+ /**
28363
+ * Builds an ANSI update that rewrites only contiguous groups of rows which changed.
28364
+ *
28365
+ * @private helper of `buildCoderRunUiTerminalFrameUpdate`
28366
+ */
28367
+ function buildChangedCoderRunUiTerminalFrameUpdate(previousFrameLines, nextFrameLines) {
28368
+ const outputParts = [];
28369
+ let lineIndex = 0;
28370
+ while (lineIndex < nextFrameLines.length) {
28371
+ if (previousFrameLines[lineIndex] === nextFrameLines[lineIndex]) {
28372
+ lineIndex++;
28373
+ continue;
28374
+ }
28375
+ const changedRangeStartLineIndex = lineIndex;
28376
+ while (lineIndex + 1 < nextFrameLines.length &&
28377
+ previousFrameLines[lineIndex + 1] !== nextFrameLines[lineIndex + 1]) {
28378
+ lineIndex++;
28379
+ }
28380
+ appendChangedCoderRunUiTerminalFrameRange({
28381
+ outputParts,
28382
+ frameLineCount: nextFrameLines.length,
28383
+ changedRangeStartLineIndex,
28384
+ changedRangeEndLineIndex: lineIndex,
28385
+ nextFrameLines,
28386
+ });
28387
+ lineIndex++;
28388
+ }
28389
+ return outputParts.length === 0 ? undefined : outputParts.join('');
28390
+ }
28391
+ /**
28392
+ * Appends the cursor movements and row rewrites for one contiguous changed frame range.
28393
+ *
28394
+ * @private helper of `buildChangedCoderRunUiTerminalFrameUpdate`
28395
+ */
28396
+ function appendChangedCoderRunUiTerminalFrameRange(options) {
28397
+ const { outputParts, frameLineCount, changedRangeStartLineIndex, changedRangeEndLineIndex, nextFrameLines } = options;
28398
+ const linesUpFromFrameBottom = frameLineCount - 1 - changedRangeStartLineIndex;
28399
+ if (linesUpFromFrameBottom > 0) {
28400
+ outputParts.push(moveCursorUp(linesUpFromFrameBottom));
28401
+ }
28402
+ for (let lineIndex = changedRangeStartLineIndex; lineIndex <= changedRangeEndLineIndex; lineIndex++) {
28403
+ outputParts.push(CLEAR_CURRENT_LINE, MOVE_CURSOR_TO_LINE_START, nextFrameLines[lineIndex]);
28404
+ if (lineIndex < changedRangeEndLineIndex) {
28405
+ outputParts.push('\n');
28406
+ }
28407
+ }
28408
+ const linesDownToFrameBottom = frameLineCount - 1 - changedRangeEndLineIndex;
28409
+ if (linesDownToFrameBottom > 0) {
28410
+ outputParts.push(moveCursorDown(linesDownToFrameBottom));
28411
+ }
28412
+ outputParts.push(MOVE_CURSOR_TO_LINE_START);
28413
+ }
28414
+ /**
28415
+ * Builds an ANSI sequence which moves the terminal cursor up by a positive number of rows.
28416
+ *
28417
+ * @private helper of `buildCoderRunUiTerminalFrameUpdate`
28418
+ */
28419
+ function moveCursorUp(lineCount) {
28420
+ return `\x1b[${lineCount}A`;
28421
+ }
28422
+ /**
28423
+ * Builds an ANSI sequence which moves the terminal cursor down by a positive number of rows.
28424
+ *
28425
+ * @private helper of `buildCoderRunUiTerminalFrameUpdate`
28426
+ */
28427
+ function moveCursorDown(lineCount) {
28428
+ return `\x1b[${lineCount}B`;
28429
+ }
28430
+
28049
28431
  /**
28050
28432
  * Calendar formats used when displaying the estimated completion time.
28051
28433
  *
@@ -28530,58 +28912,6 @@ function renderCoderRunUi(startTime, options = {}) {
28530
28912
  scheduleRender();
28531
28913
  }, autoRefreshInterval);
28532
28914
  }
28533
- /**
28534
- * Moves the cursor relative to the bottom of the current frame and rewrites one line in place.
28535
- */
28536
- function rewriteFrameLine(frameLineCount, lineIndex, line) {
28537
- const linesUpFromBottom = Math.max(0, frameLineCount - 1 - lineIndex);
28538
- if (linesUpFromBottom > 0) {
28539
- process.stdout.write(`\x1b[${linesUpFromBottom}A`);
28540
- }
28541
- clearLine(process.stdout, 0);
28542
- cursorTo(process.stdout, 0);
28543
- process.stdout.write(line);
28544
- cursorTo(process.stdout, 0);
28545
- if (linesUpFromBottom > 0) {
28546
- process.stdout.write(`\x1b[${linesUpFromBottom}B`);
28547
- cursorTo(process.stdout, 0);
28548
- }
28549
- }
28550
- /**
28551
- * Fully rewrites the reserved frame area.
28552
- */
28553
- function renderFullFrame(lines) {
28554
- var _a;
28555
- const previousFrameLineCount = previousFrameLines.length;
28556
- const linesToRewriteCount = Math.max(previousFrameLineCount, lines.length);
28557
- if (previousFrameLineCount > 1) {
28558
- process.stdout.write(`\x1b[${previousFrameLineCount - 1}A`);
28559
- }
28560
- for (let i = 0; i < linesToRewriteCount; i++) {
28561
- clearLine(process.stdout, 0);
28562
- cursorTo(process.stdout, 0);
28563
- process.stdout.write((_a = lines[i]) !== null && _a !== void 0 ? _a : '');
28564
- if (i < linesToRewriteCount - 1) {
28565
- process.stdout.write('\n');
28566
- }
28567
- }
28568
- const clearedTrailingLines = linesToRewriteCount - lines.length;
28569
- if (clearedTrailingLines > 0) {
28570
- process.stdout.write(`\x1b[${clearedTrailingLines}A`);
28571
- }
28572
- cursorTo(process.stdout, 0);
28573
- }
28574
- /**
28575
- * Updates only the frame rows whose visible content changed.
28576
- */
28577
- function renderChangedLines(lines) {
28578
- for (let i = 0; i < lines.length; i++) {
28579
- if (previousFrameLines[i] === lines[i]) {
28580
- continue;
28581
- }
28582
- rewriteFrameLine(lines.length, i, lines[i]);
28583
- }
28584
- }
28585
28915
  /**
28586
28916
  * Builds the current frame snapshot from the latest state.
28587
28917
  */
@@ -28623,11 +28953,12 @@ function renderCoderRunUi(startTime, options = {}) {
28623
28953
  isRendering = true;
28624
28954
  try {
28625
28955
  const lines = buildFrameLines();
28626
- if (previousFrameLines.length === 0 || previousFrameLines.length !== lines.length) {
28627
- renderFullFrame(lines);
28628
- }
28629
- else {
28630
- renderChangedLines(lines);
28956
+ const terminalFrameUpdate = buildCoderRunUiTerminalFrameUpdate({
28957
+ previousFrameLines,
28958
+ nextFrameLines: lines,
28959
+ });
28960
+ if (terminalFrameUpdate !== undefined) {
28961
+ process.stdout.write(terminalFrameUpdate);
28631
28962
  }
28632
28963
  previousFrameLines = [...lines];
28633
28964
  spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
@@ -29018,12 +29349,102 @@ class MultipleAgentRunUiPresenter {
29018
29349
 
29019
29350
  /**
29020
29351
  * Git commands used to list changed and untracked files in the working tree.
29352
+ *
29353
+ * Note: None of them ever lists a file ignored by `.gitignore`, so a path taken from here can always be staged.
29354
+ * Note: `--no-renames` is used because a detected rename would be reported as its destination path only, which
29355
+ * would leave the source path of a moved file out of the commit.
29021
29356
  */
29022
29357
  const GIT_CHANGED_FILE_COMMANDS = [
29023
- 'git diff --name-only --',
29024
- 'git diff --name-only --cached --',
29358
+ 'git diff --name-only --no-renames --',
29359
+ 'git diff --name-only --no-renames --cached --',
29025
29360
  'git ls-files --others --exclude-standard',
29026
29361
  ];
29362
+ /**
29363
+ * Captures hashes for files that are dirty before one operation starts.
29364
+ */
29365
+ async function captureWorkingTreeChangesSnapshot(projectPath) {
29366
+ const changedFiles = await listWorkingTreeChangedFiles(projectPath);
29367
+ const changedFileHashes = new Map();
29368
+ for (const relativePath of changedFiles) {
29369
+ changedFileHashes.set(relativePath, await readWorkingTreeFileHash(projectPath, relativePath));
29370
+ }
29371
+ return { changedFileHashes };
29372
+ }
29373
+ /**
29374
+ * Lists the repository-relative paths which really changed since the snapshot was taken.
29375
+ *
29376
+ * A file which was already dirty before and was not touched afterwards is not listed, so the result contains
29377
+ * exactly the files changed by the operation which the snapshot wraps.
29378
+ */
29379
+ async function listFilesChangedSinceSnapshot(projectPath, snapshot) {
29380
+ const changedFiles = await listWorkingTreeChangedFiles(projectPath);
29381
+ const changedFilesSinceSnapshot = [];
29382
+ for (const relativePath of changedFiles) {
29383
+ const currentFileHash = await readWorkingTreeFileHash(projectPath, relativePath);
29384
+ const hashBeforeOperation = snapshot.changedFileHashes.get(relativePath);
29385
+ const wasDirtyBeforeOperation = snapshot.changedFileHashes.has(relativePath);
29386
+ if (wasDirtyBeforeOperation && hashBeforeOperation === currentFileHash) {
29387
+ continue;
29388
+ }
29389
+ changedFilesSinceSnapshot.push(relativePath);
29390
+ }
29391
+ return changedFilesSinceSnapshot;
29392
+ }
29393
+ /**
29394
+ * Lists dirty tracked files and untracked files in the working tree.
29395
+ */
29396
+ async function listWorkingTreeChangedFiles(projectPath) {
29397
+ const changedFiles = new Set();
29398
+ for (const command of GIT_CHANGED_FILE_COMMANDS) {
29399
+ const output = await $execCommand({
29400
+ command,
29401
+ cwd: projectPath,
29402
+ isVerbose: false,
29403
+ });
29404
+ for (const filePath of output.split('\n').map(normalizeGitFilePath).filter(Boolean)) {
29405
+ changedFiles.add(filePath);
29406
+ }
29407
+ }
29408
+ return [...changedFiles.values()];
29409
+ }
29410
+ /**
29411
+ * Reads the content hash of one repository-relative working tree file.
29412
+ *
29413
+ * @returns Hash of a regular file, otherwise `null` for a deleted or non-regular file.
29414
+ */
29415
+ async function readWorkingTreeFileHash(projectPath, relativePath) {
29416
+ try {
29417
+ const absolutePath = resolve(projectPath, relativePath);
29418
+ const fileStats = await stat(absolutePath);
29419
+ if (!fileStats.isFile()) {
29420
+ return null;
29421
+ }
29422
+ const content = await readFile(absolutePath);
29423
+ return createHash('sha1').update(content).digest('hex');
29424
+ }
29425
+ catch (error) {
29426
+ if (isFileNotFoundError$3(error)) {
29427
+ return null;
29428
+ }
29429
+ throw error;
29430
+ }
29431
+ }
29432
+ /**
29433
+ * Normalizes Git output paths for internal matching.
29434
+ */
29435
+ function normalizeGitFilePath(filePath) {
29436
+ return filePath.trim().replace(/\\/g, '/');
29437
+ }
29438
+ /**
29439
+ * Returns true when an error is a missing-file filesystem error.
29440
+ */
29441
+ function isFileNotFoundError$3(error) {
29442
+ return Boolean(error &&
29443
+ typeof error === 'object' &&
29444
+ 'code' in error &&
29445
+ (error.code === 'ENOENT' || error.code === 'ENOTDIR'));
29446
+ }
29447
+
29027
29448
  /**
29028
29449
  * File extensions that should always be treated as binary.
29029
29450
  */
@@ -29071,34 +29492,20 @@ const BINARY_FILE_EXTENSIONS = new Set([
29071
29492
  * Captures hashes for files that are dirty before a coding round starts.
29072
29493
  */
29073
29494
  async function captureChangedFilesSnapshot(projectPath) {
29074
- const changedFiles = await listWorkingTreeChangedFiles(projectPath);
29075
- const changedFileHashes = new Map();
29076
- for (const relativePath of changedFiles) {
29077
- const absolutePath = resolveProjectPath(projectPath, relativePath);
29078
- const fileHash = await readFileHashIfRegularFile(absolutePath);
29079
- if (fileHash) {
29080
- changedFileHashes.set(relativePath, fileHash);
29081
- }
29082
- }
29083
- return { changedFileHashes };
29495
+ return captureWorkingTreeChangesSnapshot(projectPath);
29084
29496
  }
29085
29497
  /**
29086
29498
  * Normalizes CRLF to LF only in files that changed since the captured snapshot.
29087
29499
  */
29088
29500
  async function normalizeLineEndingsInFilesChangedSinceSnapshot(options) {
29089
- const changedFiles = await listWorkingTreeChangedFiles(options.projectPath);
29501
+ const changedFiles = await listFilesChangedSinceSnapshot(options.projectPath, options.snapshot);
29090
29502
  let scannedFiles = 0;
29091
29503
  let normalizedFiles = 0;
29092
29504
  let skippedBinaryFiles = 0;
29093
29505
  for (const relativePath of changedFiles) {
29094
29506
  const absolutePath = resolveProjectPath(options.projectPath, relativePath);
29095
- const currentFileHash = await readFileHashIfRegularFile(absolutePath);
29096
- if (!currentFileHash) {
29097
- continue;
29098
- }
29099
- const hashBeforeRound = options.snapshot.changedFileHashes.get(relativePath);
29100
- const hasChangedInRound = hashBeforeRound === undefined || hashBeforeRound !== currentFileHash;
29101
- if (!hasChangedInRound) {
29507
+ // Note: A file deleted or replaced by a directory during the round has no content to normalize
29508
+ if (!(await isRegularFile(absolutePath))) {
29102
29509
  continue;
29103
29510
  }
29104
29511
  scannedFiles++;
@@ -29124,29 +29531,6 @@ async function normalizeLineEndingsInFilesChangedSinceSnapshot(options) {
29124
29531
  skippedBinaryFiles,
29125
29532
  };
29126
29533
  }
29127
- /**
29128
- * Lists dirty tracked files and untracked files in the working tree.
29129
- */
29130
- async function listWorkingTreeChangedFiles(projectPath) {
29131
- const changedFiles = new Set();
29132
- for (const command of GIT_CHANGED_FILE_COMMANDS) {
29133
- const output = await $execCommand({
29134
- command,
29135
- cwd: projectPath,
29136
- isVerbose: false,
29137
- });
29138
- for (const filePath of output.split('\n').map(normalizeGitFilePath).filter(Boolean)) {
29139
- changedFiles.add(filePath);
29140
- }
29141
- }
29142
- return [...changedFiles.values()];
29143
- }
29144
- /**
29145
- * Normalizes Git output paths for internal matching.
29146
- */
29147
- function normalizeGitFilePath(filePath) {
29148
- return filePath.trim().replace(/\\/g, '/');
29149
- }
29150
29534
  /**
29151
29535
  * Resolves a repository-relative file path to an absolute path.
29152
29536
  */
@@ -29154,33 +29538,16 @@ function resolveProjectPath(projectPath, relativePath) {
29154
29538
  return resolve(projectPath, relativePath);
29155
29539
  }
29156
29540
  /**
29157
- * Reads file hash for a regular file and returns undefined for non-files/missing files.
29541
+ * Checks whether a path exists and is a regular file.
29158
29542
  */
29159
- async function readFileHashIfRegularFile(path) {
29543
+ async function isRegularFile(path) {
29160
29544
  try {
29161
- const fileStats = await stat(path);
29162
- if (!fileStats.isFile()) {
29163
- return undefined;
29164
- }
29165
- const content = await readFile(path);
29166
- return createHash('sha1').update(content).digest('hex');
29545
+ return (await stat(path)).isFile();
29167
29546
  }
29168
- catch (error) {
29169
- if (isFileNotFoundError$1(error)) {
29170
- return undefined;
29171
- }
29172
- throw error;
29547
+ catch (_a) {
29548
+ return false;
29173
29549
  }
29174
29550
  }
29175
- /**
29176
- * Returns true when an error is a missing-file filesystem error.
29177
- */
29178
- function isFileNotFoundError$1(error) {
29179
- return Boolean(error &&
29180
- typeof error === 'object' &&
29181
- 'code' in error &&
29182
- ((error.code === 'ENOENT') || error.code === 'ENOTDIR'));
29183
- }
29184
29551
  /**
29185
29552
  * Detects binary files from extension.
29186
29553
  */
@@ -29370,7 +29737,7 @@ function readOptionalSigningKeyValue() {
29370
29737
  /**
29371
29738
  * Commits staged changes with the provided message using the dedicated coding-agent identity when configured,
29372
29739
  * otherwise falls back to the default Git configuration. Remote pushing is opt-in via `options.autoPush`,
29373
- * `options.includePaths` can restrict staging, `options.onlyPaths` can restrict the commit pathspec,
29740
+ * `options.relevantPaths` restricts both the staging and the commit to the files of the current operation,
29374
29741
  * `options.excludePaths` can keep temporary artifacts out of the created commit and
29375
29742
  * `options.isEmptyCommitAllowed` keeps a round without any file change from failing.
29376
29743
  *
@@ -29386,15 +29753,19 @@ async function commitChanges(message, options) {
29386
29753
  try {
29387
29754
  const agentEnv = buildAgentGitEnv();
29388
29755
  const signingFlag = buildAgentGitSigningFlag();
29389
- await stageCommitChanges(projectPath, agentEnv, options === null || options === void 0 ? void 0 : options.includePaths, [
29756
+ const excludedGitPaths = await normalizeExcludedGitPaths(projectPath, [
29390
29757
  commitMessagePath,
29391
29758
  ...((_a = options === null || options === void 0 ? void 0 : options.excludePaths) !== null && _a !== void 0 ? _a : []),
29392
29759
  ]);
29760
+ // Note: An excluded path must be dropped from the relevant paths as well, because a commit restricted by
29761
+ // a pathspec commits the working tree content of those paths and would ignore unstaging them
29762
+ const relevantPaths = excludeGitPaths(options === null || options === void 0 ? void 0 : options.relevantPaths, excludedGitPaths);
29763
+ await stageCommitChanges(projectPath, agentEnv, relevantPaths, excludedGitPaths);
29393
29764
  await runGitCommand({
29394
29765
  command: buildGitCommitCommand({
29395
29766
  commitMessagePath,
29396
29767
  signingFlag,
29397
- onlyPaths: options === null || options === void 0 ? void 0 : options.onlyPaths,
29768
+ relevantPaths,
29398
29769
  isEmptyCommitAllowed: options === null || options === void 0 ? void 0 : options.isEmptyCommitAllowed,
29399
29770
  }),
29400
29771
  cwd: projectPath,
@@ -29409,15 +29780,17 @@ async function commitChanges(message, options) {
29409
29780
  }
29410
29781
  }
29411
29782
  /**
29412
- * Stages repository changes and optionally unstages temporary files that should not end up inside the commit.
29783
+ * Stages the relevant repository changes and unstages temporary files that should not end up inside the commit.
29413
29784
  */
29414
- async function stageCommitChanges(projectPath, agentEnv, includePaths, excludePaths) {
29415
- await runGitCommand({
29416
- command: buildGitAddCommand(includePaths),
29417
- cwd: projectPath,
29418
- env: agentEnv,
29419
- });
29420
- const excludedGitPaths = await normalizeExcludedGitPaths(projectPath, excludePaths);
29785
+ async function stageCommitChanges(projectPath, agentEnv, relevantPaths, excludedGitPaths) {
29786
+ // Note: An operation which changed nothing relevant has nothing to stage
29787
+ if (relevantPaths === undefined || relevantPaths.length > 0) {
29788
+ await runGitCommand({
29789
+ command: buildGitAddCommand(relevantPaths),
29790
+ cwd: projectPath,
29791
+ env: agentEnv,
29792
+ });
29793
+ }
29421
29794
  if (excludedGitPaths.length === 0) {
29422
29795
  return;
29423
29796
  }
@@ -29429,13 +29802,29 @@ async function stageCommitChanges(projectPath, agentEnv, includePaths, excludePa
29429
29802
  });
29430
29803
  }
29431
29804
  /**
29432
- * Builds the git add command for either the whole tree or a focused set of paths.
29805
+ * Builds the git add command for either the whole tree or the relevant paths of the current operation.
29433
29806
  */
29434
- function buildGitAddCommand(includePaths) {
29435
- if (!includePaths || includePaths.length === 0) {
29807
+ function buildGitAddCommand(relevantPaths) {
29808
+ if (!relevantPaths || relevantPaths.length === 0) {
29436
29809
  return 'git add .';
29437
29810
  }
29438
- return `git add --all -- ${includePaths.map(quoteShellPath$1).join(' ')}`;
29811
+ return `git add --all -- ${relevantPaths.map(quoteShellPath$1).join(' ')}`;
29812
+ }
29813
+ /**
29814
+ * Removes the excluded repository paths from the relevant paths of the current operation.
29815
+ */
29816
+ function excludeGitPaths(relevantPaths, excludedGitPaths) {
29817
+ if (relevantPaths === undefined || excludedGitPaths.length === 0) {
29818
+ return relevantPaths;
29819
+ }
29820
+ const excludedGitPathSet = new Set(excludedGitPaths);
29821
+ return relevantPaths.filter((relevantPath) => !excludedGitPathSet.has(normalizeGitPathSeparators(relevantPath)));
29822
+ }
29823
+ /**
29824
+ * Normalizes path separators so a relevant path can be matched against a repository-relative Git path.
29825
+ */
29826
+ function normalizeGitPathSeparators(path) {
29827
+ return path.replace(/\\/gu, '/');
29439
29828
  }
29440
29829
  /**
29441
29830
  * Converts excluded filesystem paths into unique repository-relative Git paths.
@@ -29606,8 +29995,8 @@ function buildGitCommitCommand(options) {
29606
29995
  commandParts.push('--allow-empty');
29607
29996
  }
29608
29997
  commandParts.push(`--file "${options.commitMessagePath}"`);
29609
- if (options.onlyPaths && options.onlyPaths.length > 0) {
29610
- commandParts.push('--', ...options.onlyPaths.map(quoteShellPath$1));
29998
+ if (options.relevantPaths && options.relevantPaths.length > 0) {
29999
+ commandParts.push('--', ...options.relevantPaths.map(quoteShellPath$1));
29611
30000
  }
29612
30001
  return commandParts.join(' ');
29613
30002
  }
@@ -30097,6 +30486,140 @@ function buildProjectRuntimeInstructions(projectRuntimeApi) {
30097
30486
  `);
30098
30487
  }
30099
30488
 
30489
+ // Note: [💞] This file defines the shared TEAM workspace convention rather than one standalone entity.
30490
+ /**
30491
+ * Relative directory for an active TEAM conversation workspace.
30492
+ *
30493
+ * @private internal convention shared by the Agents Server and agent-folder runner
30494
+ */
30495
+ const AGENT_TEAM_CONVERSATIONS_DIRECTORY_PATH = join(AGENT_MESSAGES_DIRECTORY_PATH, 'team');
30496
+ /**
30497
+ * Relative directory where completed TEAM conversation transcripts are retained.
30498
+ *
30499
+ * @private internal convention shared by the Agents Server and agent-folder runner
30500
+ */
30501
+ const AGENT_FINISHED_TEAM_CONVERSATIONS_DIRECTORY_PATH = join(AGENT_FINISHED_MESSAGES_DIRECTORY_PATH, 'team');
30502
+ /**
30503
+ * Name of the JSON manifest that describes one TEAM conversation workspace.
30504
+ *
30505
+ * @private internal convention shared by the Agents Server and agent-folder runner
30506
+ */
30507
+ const AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME = 'team.json';
30508
+ /**
30509
+ * Name of the read-only teammate-source directory inside one TEAM workspace.
30510
+ *
30511
+ * @private internal convention shared by the Agents Server and agent-folder runner
30512
+ */
30513
+ const AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME = 'teammates';
30514
+ /**
30515
+ * Creates the stable directory name that belongs to one queued `.book` message.
30516
+ *
30517
+ * @private internal convention shared by the Agents Server and agent-folder runner
30518
+ */
30519
+ function createAgentTeamConversationWorkspaceDirectoryName(messageFileName) {
30520
+ const rawBaseName = basename(messageFileName).replace(/\.book$/iu, '');
30521
+ const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
30522
+ return normalizedBaseName || 'message';
30523
+ }
30524
+ /**
30525
+ * Creates the relative active workspace path for one queued message.
30526
+ *
30527
+ * @private internal convention shared by the Agents Server and agent-folder runner
30528
+ */
30529
+ function createAgentTeamConversationWorkspacePath(messageFileName) {
30530
+ return join(AGENT_TEAM_CONVERSATIONS_DIRECTORY_PATH, createAgentTeamConversationWorkspaceDirectoryName(messageFileName));
30531
+ }
30532
+ /**
30533
+ * Creates the relative completed-workspace path for one queued message.
30534
+ *
30535
+ * @private internal convention shared by the Agents Server and agent-folder runner
30536
+ */
30537
+ function createFinishedAgentTeamConversationWorkspacePath(messageFileName) {
30538
+ return join(AGENT_FINISHED_TEAM_CONVERSATIONS_DIRECTORY_PATH, createAgentTeamConversationWorkspaceDirectoryName(messageFileName));
30539
+ }
30540
+ /**
30541
+ * Checks whether unknown JSON has the minimum shape required for a TEAM workspace manifest.
30542
+ *
30543
+ * @private internal convention shared by the Agents Server and agent-folder runner
30544
+ */
30545
+ function isAgentTeamConversationWorkspaceManifest(value) {
30546
+ if (!value || typeof value !== 'object') {
30547
+ return false;
30548
+ }
30549
+ const manifest = value;
30550
+ if (manifest.version !== 1 ||
30551
+ !isPrimaryAgent(manifest.primaryAgent) ||
30552
+ !Array.isArray(manifest.teammates) ||
30553
+ !manifest.teammates.every(isTeammate)) {
30554
+ return false;
30555
+ }
30556
+ return true;
30557
+ }
30558
+ /**
30559
+ * Checks the primary-agent part of an untrusted manifest.
30560
+ *
30561
+ * @private internal utility of `isAgentTeamConversationWorkspaceManifest`
30562
+ */
30563
+ function isPrimaryAgent(value) {
30564
+ return Boolean(value &&
30565
+ typeof value === 'object' &&
30566
+ typeof value.permanentId === 'string' &&
30567
+ typeof value.agentName === 'string');
30568
+ }
30569
+ /**
30570
+ * Checks one teammate part of an untrusted manifest.
30571
+ *
30572
+ * @private internal utility of `isAgentTeamConversationWorkspaceManifest`
30573
+ */
30574
+ function isTeammate(value) {
30575
+ return Boolean(value &&
30576
+ typeof value === 'object' &&
30577
+ typeof value.permanentId === 'string' &&
30578
+ typeof value.agentName === 'string' &&
30579
+ typeof value.url === 'string' &&
30580
+ typeof value.instructions === 'string' &&
30581
+ typeof value.sourceFileName === 'string');
30582
+ }
30583
+
30584
+ /**
30585
+ * Builds the optional single-run TEAM consultation instructions for a coding harness.
30586
+ */
30587
+ function buildAgentTeamPromptSection(workspace) {
30588
+ if (!workspace || workspace.manifest.teammates.length === 0) {
30589
+ return '';
30590
+ }
30591
+ const teammateSections = workspace.manifest.teammates.map((teammate) => {
30592
+ const sourcePath = toPromptPath(join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
30593
+ const transcriptPath = toPromptPath(join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
30594
+ const instructions = teammate.instructions.trim() || 'No additional TEAM instructions were provided.';
30595
+ return spaceTrim$1(`
30596
+ - ${teammate.agentName}
30597
+ - TEAM instructions: ${instructions}
30598
+ - Read-only source: \`${sourcePath}\`
30599
+ - First transcript file: \`${transcriptPath}\`
30600
+ `);
30601
+ });
30602
+ return spaceTrim$1((block) => `
30603
+ ## Team consultations
30604
+
30605
+ The following teammates are available for this user turn:
30606
+
30607
+ ${block(teammateSections.join('\n'))}
30608
+
30609
+ - 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.
30610
+ - The primary agent remains responsible for the final user-facing \`MESSAGE @Agent\` answer.
30611
+ - 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)}\`.
30612
+ - Name each transcript \`<teammate permanent id>--<sequence>.book\`, for example \`${workspace.manifest.teammates[0].permanentId}--01.book\`.
30613
+ - 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.
30614
+ `);
30615
+ }
30616
+ /**
30617
+ * Converts a filesystem-relative path into the portable path notation used in prompts.
30618
+ */
30619
+ function toPromptPath(path) {
30620
+ return path.replace(/\\/gu, '/');
30621
+ }
30622
+
30100
30623
  /**
30101
30624
  * Builds the prompt sent to the selected coding runner for one queued user-thread book.
30102
30625
  */
@@ -30106,7 +30629,7 @@ function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage, option
30106
30629
 
30107
30630
  - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE @User\`
30108
30631
  - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
30109
- - Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory
30632
+ ${block(buildAllowedFileChangesPromptLine(options.teamWorkspace))}
30110
30633
 
30111
30634
  ## Rules for the answering
30112
30635
 
@@ -30144,11 +30667,22 @@ function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage, option
30144
30667
 
30145
30668
  ${block(buildAgentProjectsPromptSection(options))}
30146
30669
 
30670
+ ${block(buildAgentTeamPromptSection(options.teamWorkspace))}
30671
+
30147
30672
  ## This is how you should behave
30148
30673
 
30149
30674
  ${block(agentSystemMessage)}
30150
30675
  `);
30151
30676
  }
30677
+ /**
30678
+ * Explains the exact files that one coding harness may change for a user turn.
30679
+ */
30680
+ function buildAllowedFileChangesPromptLine(teamWorkspace) {
30681
+ if (!teamWorkspace) {
30682
+ return `- Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`;
30683
+ }
30684
+ 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, '/')}\``;
30685
+ }
30152
30686
 
30153
30687
  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"}];
30154
30688
 
@@ -36848,6 +37382,148 @@ function formatAgentModelRequirementsForRunner(modelRequirements) {
36848
37382
  .join('\n\n');
36849
37383
  }
36850
37384
 
37385
+ /**
37386
+ * Moves harness-created TEAM conversation transcripts into the finished-message history.
37387
+ */
37388
+ async function finalizeAgentTeamConversationWorkspace(options) {
37389
+ if (!options.workspace) {
37390
+ return null;
37391
+ }
37392
+ const activeWorkspacePath = join(options.projectPath, createAgentTeamConversationWorkspacePath(options.queuedMessage.fileName));
37393
+ const activeManifestPath = join(activeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME);
37394
+ const transcriptFileNames = await listAgentTeamTranscriptFileNames(activeWorkspacePath);
37395
+ if (!(await isExistingPath$1(activeManifestPath))) {
37396
+ return null;
37397
+ }
37398
+ if (transcriptFileNames.length === 0) {
37399
+ await rm(activeWorkspacePath, { recursive: true, force: true });
37400
+ return null;
37401
+ }
37402
+ const finishedRelativeWorkspacePath = createFinishedAgentTeamConversationWorkspacePath(options.queuedMessage.fileName);
37403
+ const finishedWorkspacePath = join(options.projectPath, finishedRelativeWorkspacePath);
37404
+ await rm(finishedWorkspacePath, { recursive: true, force: true });
37405
+ await mkdir(finishedWorkspacePath, { recursive: true });
37406
+ const relativePaths = [
37407
+ toPortablePath$1(join(finishedRelativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME)),
37408
+ ];
37409
+ await rename(activeManifestPath, join(finishedWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME));
37410
+ for (const transcriptFileName of transcriptFileNames) {
37411
+ await rename(join(activeWorkspacePath, transcriptFileName), join(finishedWorkspacePath, transcriptFileName));
37412
+ relativePaths.push(toPortablePath$1(join(finishedRelativeWorkspacePath, transcriptFileName)));
37413
+ }
37414
+ await rm(activeWorkspacePath, { recursive: true, force: true });
37415
+ return { relativePaths };
37416
+ }
37417
+ /**
37418
+ * Lists only direct transcript `.book` files and deliberately excludes read-only teammate sources.
37419
+ */
37420
+ async function listAgentTeamTranscriptFileNames(activeWorkspacePath) {
37421
+ try {
37422
+ const entries = await readdir(activeWorkspacePath, { withFileTypes: true });
37423
+ return entries
37424
+ .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.book'))
37425
+ .map((entry) => entry.name)
37426
+ .sort((firstFileName, secondFileName) => firstFileName.localeCompare(secondFileName));
37427
+ }
37428
+ catch (error) {
37429
+ if (isFileNotFoundError$2(error)) {
37430
+ return [];
37431
+ }
37432
+ throw error;
37433
+ }
37434
+ }
37435
+ /**
37436
+ * Checks whether one fixed workspace path exists.
37437
+ */
37438
+ async function isExistingPath$1(path) {
37439
+ try {
37440
+ await stat(path);
37441
+ return true;
37442
+ }
37443
+ catch (error) {
37444
+ if (isFileNotFoundError$2(error)) {
37445
+ return false;
37446
+ }
37447
+ throw error;
37448
+ }
37449
+ }
37450
+ /**
37451
+ * Converts a filesystem-relative path into a portable Git path.
37452
+ */
37453
+ function toPortablePath$1(path) {
37454
+ return path.replace(/\\/gu, '/');
37455
+ }
37456
+ /**
37457
+ * Returns true when one filesystem error indicates a missing path.
37458
+ */
37459
+ function isFileNotFoundError$2(error) {
37460
+ return Boolean(error &&
37461
+ typeof error === 'object' &&
37462
+ 'code' in error &&
37463
+ (error.code === 'ENOENT' || error.code === 'ENOTDIR'));
37464
+ }
37465
+
37466
+ /**
37467
+ * Loads the optional TEAM roster snapshot for one queued message.
37468
+ */
37469
+ async function loadAgentTeamConversationWorkspace(projectPath, queuedMessage) {
37470
+ const relativeWorkspacePath = createAgentTeamConversationWorkspacePath(queuedMessage.fileName);
37471
+ const manifestPath = join(projectPath, relativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME);
37472
+ const manifestContent = await readOptionalTextFile(manifestPath);
37473
+ if (manifestContent === null) {
37474
+ return null;
37475
+ }
37476
+ const manifest = parseAgentTeamConversationWorkspaceManifest(manifestContent);
37477
+ if (!manifest || manifest.teammates.length === 0) {
37478
+ return null;
37479
+ }
37480
+ return {
37481
+ relativeWorkspacePath: toPortablePath(relativeWorkspacePath),
37482
+ manifest,
37483
+ };
37484
+ }
37485
+ /**
37486
+ * Parses one untrusted TEAM workspace manifest without making a queued message fail for stale sidecar data.
37487
+ */
37488
+ function parseAgentTeamConversationWorkspaceManifest(manifestContent) {
37489
+ try {
37490
+ const parsedManifest = JSON.parse(manifestContent);
37491
+ return isAgentTeamConversationWorkspaceManifest(parsedManifest) ? parsedManifest : null;
37492
+ }
37493
+ catch (_a) {
37494
+ return null;
37495
+ }
37496
+ }
37497
+ /**
37498
+ * Reads one text file and treats a missing team workspace as absent.
37499
+ */
37500
+ async function readOptionalTextFile(path) {
37501
+ try {
37502
+ return await readFile(path, 'utf-8');
37503
+ }
37504
+ catch (error) {
37505
+ if (isFileNotFoundError$1(error)) {
37506
+ return null;
37507
+ }
37508
+ throw error;
37509
+ }
37510
+ }
37511
+ /**
37512
+ * Converts a filesystem-relative path to the portable form used by prompts and Git.
37513
+ */
37514
+ function toPortablePath(path) {
37515
+ return path.replace(/\\/gu, '/');
37516
+ }
37517
+ /**
37518
+ * Returns true when one filesystem error indicates a missing path.
37519
+ */
37520
+ function isFileNotFoundError$1(error) {
37521
+ return Boolean(error &&
37522
+ typeof error === 'object' &&
37523
+ 'code' in error &&
37524
+ (error.code === 'ENOENT' || error.code === 'ENOTDIR'));
37525
+ }
37526
+
36851
37527
  /**
36852
37528
  * Resolves project-runtime prompt API details for one local agent runner folder.
36853
37529
  *
@@ -37164,11 +37840,13 @@ function removeQueuedMessageFromSnapshot(queueSnapshot, processedQueuedMessage)
37164
37840
  * Runs the selected coding runner for one message and finalizes the answered file.
37165
37841
  */
37166
37842
  async function runQueuedAgentMessage(options) {
37167
- const { projectPath, options: runOptions, runner, actualRunnerModel, queuedMessage, uiHandle, isSharedDashboard } = options;
37843
+ const { projectPath, options: runOptions, runner, actualRunnerModel, queuedMessage, uiHandle, isSharedDashboard, } = options;
37168
37844
  const agentSystemMessage = await loadLocalAgentSystemMessage(projectPath);
37845
+ const teamWorkspace = await loadAgentTeamConversationWorkspace(projectPath, queuedMessage);
37169
37846
  const prompt = buildAgentMessagePrompt(queuedMessage.relativePath, agentSystemMessage, {
37170
37847
  projectRuntimeApi: resolveAgentProjectRuntimePromptApi(projectPath),
37171
37848
  projectsUrlPath: resolveAgentProjectsUrlPath(projectPath),
37849
+ teamWorkspace: teamWorkspace || undefined,
37172
37850
  });
37173
37851
  const scriptPath = buildAgentMessageScriptPath(projectPath, queuedMessage);
37174
37852
  const runtimeLogPath = buildScriptLogPath(scriptPath);
@@ -37216,6 +37894,13 @@ async function runQueuedAgentMessage(options) {
37216
37894
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
37217
37895
  }
37218
37896
  await normalizeLineEndingsForAgentRound(projectPath, runOptions, roundChangedFilesSnapshot);
37897
+ const finishedTeamWorkspace = await finalizeAgentTeamConversationWorkspace({
37898
+ projectPath,
37899
+ queuedMessage,
37900
+ workspace: teamWorkspace,
37901
+ });
37902
+ // Note: The Agents Server observes the primary finished `.book` as its completion signal.
37903
+ // Retain TEAM transcripts first so that observer can parse every consultation atomically.
37219
37904
  const finishedMessage = await moveAgentMessageToFinished(projectPath, queuedMessage);
37220
37905
  const writtenRunReport = await writeAgentMessageRunReport({
37221
37906
  finishedMessageAbsolutePath: finishedMessage.absolutePath,
@@ -37232,6 +37917,7 @@ async function runQueuedAgentMessage(options) {
37232
37917
  options: runOptions,
37233
37918
  queuedMessage,
37234
37919
  finishedMessage,
37920
+ finishedTeamWorkspace,
37235
37921
  writtenRunReport,
37236
37922
  isQueuedMessageTracked,
37237
37923
  uiHandle,
@@ -37303,7 +37989,7 @@ function seedAgentRunUiHandle(uiHandle, options, runner, actualRunnerModel, queu
37303
37989
  * Commits the answered message move unless `--no-commit` was requested.
37304
37990
  */
37305
37991
  async function commitAnsweredMessageIfEnabled(options) {
37306
- const { options: runOptions, queuedMessage, finishedMessage, writtenRunReport, isQueuedMessageTracked, uiHandle, isSharedDashboard, projectPath, } = options;
37992
+ const { options: runOptions, queuedMessage, finishedMessage, finishedTeamWorkspace, writtenRunReport, isQueuedMessageTracked, uiHandle, isSharedDashboard, projectPath, } = options;
37307
37993
  if (runOptions.noCommit) {
37308
37994
  if (!isSharedDashboard) {
37309
37995
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Leaving changes uncommitted');
@@ -37315,21 +38001,24 @@ async function commitAnsweredMessageIfEnabled(options) {
37315
38001
  }
37316
38002
  await commitChanges(buildAgentMessageCommitMessage(queuedMessage), {
37317
38003
  autoPush: runOptions.autoPush,
37318
- includePaths: buildCommitIncludePaths(queuedMessage, finishedMessage, writtenRunReport, isQueuedMessageTracked),
38004
+ relevantPaths: buildCommitRelevantPaths(queuedMessage, finishedMessage, writtenRunReport, isQueuedMessageTracked, finishedTeamWorkspace),
37319
38005
  projectPath,
37320
38006
  });
37321
38007
  }
37322
38008
  /**
37323
38009
  * Builds the focused git path list for the answered-message commit.
37324
38010
  */
37325
- function buildCommitIncludePaths(queuedMessage, finishedMessage, writtenRunReport, isQueuedMessageTracked) {
37326
- const includePaths = isQueuedMessageTracked
38011
+ function buildCommitRelevantPaths(queuedMessage, finishedMessage, writtenRunReport, isQueuedMessageTracked, finishedTeamWorkspace) {
38012
+ const relevantPaths = isQueuedMessageTracked
37327
38013
  ? [queuedMessage.relativePath, finishedMessage.relativePath]
37328
38014
  : [finishedMessage.relativePath];
37329
38015
  if (writtenRunReport) {
37330
- includePaths.push(writtenRunReport.relativePath);
38016
+ relevantPaths.push(writtenRunReport.relativePath);
38017
+ }
38018
+ if (finishedTeamWorkspace) {
38019
+ relevantPaths.push(...finishedTeamWorkspace.relativePaths);
37331
38020
  }
37332
- return includePaths;
38021
+ return relevantPaths;
37333
38022
  }
37334
38023
  /**
37335
38024
  * Normalizes line endings in files changed during the current agent round.
@@ -38841,7 +39530,7 @@ const loadPromptsModule = createLazyModuleLoader(() => import('prompts'));
38841
39530
  const CODER_GIT_SYNC_DESCRIPTION = spaceTrim$1(`
38842
39531
  Git synchronization:
38843
39532
  - --auto-pull pulls the latest changes before this command changes anything
38844
- - --commit commits the changes made by this command
39533
+ - --commit commits only the files this command has changed, unrelated changes stay in the working tree
38845
39534
  - --auto-push pushes the created commit to the remote repository
38846
39535
  `);
38847
39536
  /**
@@ -38853,7 +39542,7 @@ const CODER_GIT_SYNC_DESCRIPTION = spaceTrim$1(`
38853
39542
  * @private internal utility of `promptbookCli`
38854
39543
  */
38855
39544
  function addCoderGitSyncOptions(command) {
38856
- command.option('--commit', 'Commit the changes made by this command with the coding-agent git identity', false);
39545
+ command.option('--commit', 'Commit the files changed by this command with the coding-agent git identity, leaving unrelated changes uncommitted', false);
38857
39546
  command.option('--auto-push', 'Automatically git push the created commit, requires --commit', false);
38858
39547
  command.option('--auto-pull', 'Automatically git pull the latest changes before this command changes anything', false);
38859
39548
  }
@@ -39162,8 +39851,8 @@ function $initializeCoderAddCommand(program) {
39162
39851
  const projectPath = process.cwd();
39163
39852
  const description = await resolveCoderPromptDescription(descriptionArgument);
39164
39853
  // Note: Import the git synchronization dynamically to keep the CLI fast for runs without `--commit`
39165
- const { $commitCoderChanges, $pullCoderChanges } = await Promise.resolve().then(function () { return coderGitSync; });
39166
- await $pullCoderChanges({ gitSync, projectPath });
39854
+ const { $commitCoderChanges, $startCoderGitSync } = await Promise.resolve().then(function () { return coderGitSync; });
39855
+ const commitScope = await $startCoderGitSync({ gitSync, projectPath });
39167
39856
  const { /* filePath,*/ emojiTag } = await addCoderPrompt({
39168
39857
  projectPath,
39169
39858
  description,
@@ -39172,7 +39861,7 @@ function $initializeCoderAddCommand(program) {
39172
39861
  });
39173
39862
  await $commitCoderChanges({
39174
39863
  gitSync,
39175
- projectPath,
39864
+ commitScope,
39176
39865
  commitMessage: `${emojiTag} Add prompt`,
39177
39866
  });
39178
39867
  }));
@@ -39766,7 +40455,7 @@ function assertsPositiveBoilerplateCountPart(countPart, countOption, countPartDe
39766
40455
  function $initializeCoderGenerateBoilerplatesCommand(program) {
39767
40456
  const command = program.command('generate-boilerplates');
39768
40457
  command.description(spaceTrim$1((block) => `
39769
- Generate prompt boilerplate files with unique emoji tags
40458
+ Generate prompt boilerplate files with one unique emoji tag per file
39770
40459
 
39771
40460
  ${block(CODER_GIT_SYNC_DESCRIPTION)}
39772
40461
  `));
@@ -39785,8 +40474,8 @@ function $initializeCoderGenerateBoilerplatesCommand(program) {
39785
40474
  const gitSync = normalizeCoderGitSyncCliOptions(cliOptions);
39786
40475
  const projectPath = process.cwd();
39787
40476
  // Note: Import the git synchronization dynamically to keep the CLI fast for runs without `--commit`
39788
- const { $commitCoderChanges, $pullCoderChanges } = await Promise.resolve().then(function () { return coderGitSync; });
39789
- await $pullCoderChanges({ gitSync, projectPath });
40477
+ const { $commitCoderChanges, $startCoderGitSync } = await Promise.resolve().then(function () { return coderGitSync; });
40478
+ const commitScope = await $startCoderGitSync({ gitSync, projectPath });
39790
40479
  await generatePromptBoilerplate({
39791
40480
  projectPath,
39792
40481
  boilerplateCount,
@@ -39794,14 +40483,14 @@ function $initializeCoderGenerateBoilerplatesCommand(program) {
39794
40483
  });
39795
40484
  await $commitCoderChanges({
39796
40485
  gitSync,
39797
- projectPath,
40486
+ commitScope,
39798
40487
  commitMessage: `Prompts ${formatBoilerplateCount(boilerplateCount)}`,
39799
40488
  });
39800
40489
  return process.exit(0);
39801
40490
  }));
39802
40491
  }
39803
40492
  /**
39804
- * Generates boilerplate prompt files with unique emoji tags.
40493
+ * Generates boilerplate prompt files with one unique emoji tag per file.
39805
40494
  *
39806
40495
  * @private internal function of `generatePromptBoilerplate` command
39807
40496
  */
@@ -39821,10 +40510,10 @@ async function generatePromptBoilerplate({ projectPath, boilerplateCount, templa
39821
40510
  const highestNumber = promptNumbering.startNumber === 0 ? 0 : promptNumbering.startNumber - promptNumbering.step;
39822
40511
  const highestNumberFormatted = Math.max(0, highestNumber).toString().padStart(4, '0');
39823
40512
  console.info(colors.blue(`Highest existing number for ${promptNumbering.datePrefix} found: ${highestNumberFormatted}`));
39824
- // Note: Every single generated prompt is one separate coding task, so each of them reserves its own fresh emoji tag
39825
40513
  const promptsCount = filesCount * promptsPerFileCount;
39826
40514
  const { availableCount, selectedEmojis } = await getFreshPromptEmojiTags({
39827
- count: promptsCount,
40515
+ // Note: Each generated file reserves one fresh emoji tag shared by all of its prompt sections
40516
+ count: filesCount,
39828
40517
  rootDir: projectPath,
39829
40518
  });
39830
40519
  console.info(colors.green(`Found ${availableCount} available fresh emojis`));
@@ -39836,14 +40525,13 @@ async function generatePromptBoilerplate({ projectPath, boilerplateCount, templa
39836
40525
  for (let i = 0; i < filesCount; i++) {
39837
40526
  const number = promptNumbering.startNumber + i * promptNumbering.step;
39838
40527
  const title = titles[i % titles.length];
39839
- const emojiTags = selectedEmojis
39840
- .slice(i * promptsPerFileCount, (i + 1) * promptsPerFileCount)
39841
- .map((emoji) => formatPromptEmojiTag(emoji));
40528
+ const emojiTag = formatPromptEmojiTag(selectedEmojis[i]);
39842
40529
  const filename = buildPromptFilename(promptNumbering.datePrefix, number, buildPromptSlug$1(promptTemplate.slugPrefix, title));
39843
40530
  const filepath = join(PROMPTS_DIRECTORY_PATH, filename);
39844
40531
  const absoluteFilepath = join(projectPath, filepath);
39845
40532
  const content = buildBoilerplatePromptFileContent({
39846
- emojiTags,
40533
+ emojiTag,
40534
+ promptsPerFileCount,
39847
40535
  title,
39848
40536
  body: promptTemplate.content,
39849
40537
  });
@@ -39852,7 +40540,7 @@ async function generatePromptBoilerplate({ projectPath, boilerplateCount, templa
39852
40540
  absoluteFilepath,
39853
40541
  filename,
39854
40542
  content,
39855
- emojiTags,
40543
+ emojiTag,
39856
40544
  number,
39857
40545
  });
39858
40546
  }
@@ -39860,26 +40548,25 @@ async function generatePromptBoilerplate({ projectPath, boilerplateCount, templa
39860
40548
  console.info(colors.yellow(`Creating ${filesToCreate.length} files:`));
39861
40549
  for (const file of filesToCreate) {
39862
40550
  writeFileSync(file.absoluteFilepath, file.content, 'utf-8');
39863
- console.info(colors.green(`✓ Created: ${file.filename} with ${file.emojiTags.join(' ')}`));
40551
+ console.info(colors.green(`✓ Created: ${file.filename} with ${file.emojiTag}`));
39864
40552
  }
39865
40553
  console.info(colors.bgGreen(` Successfully created ${promptsCount} prompts in ${filesToCreate.length} prompt boilerplate files! `));
39866
40554
  }
39867
40555
  /**
39868
- * Builds the markdown content of one generated prompt file with one prompt section per emoji tag.
40556
+ * Builds the markdown content of one generated prompt file with one prompt section per prompt.
39869
40557
  *
39870
- * Multiple prompts in one file are separated by the `---` separator, exactly like the prompt runner expects them.
40558
+ * Multiple prompts in one file share the file emoji tag and are separated by the `---` separator,
40559
+ * exactly like the prompt runner expects them.
39871
40560
  *
39872
40561
  * @private internal utility of `generatePromptBoilerplate` command
39873
40562
  */
39874
- function buildBoilerplatePromptFileContent({ emojiTags, title, body, }) {
39875
- return emojiTags
39876
- .map((emojiTag) => buildCoderPromptSection({
40563
+ function buildBoilerplatePromptFileContent({ emojiTag, promptsPerFileCount, title, body, }) {
40564
+ return Array.from({ length: promptsPerFileCount }, () => buildCoderPromptSection({
39877
40565
  statusLine: '[-]',
39878
40566
  emojiTag,
39879
40567
  title,
39880
40568
  body,
39881
- }))
39882
- .join('\n\n---\n\n');
40569
+ })).join('\n\n---\n\n');
39883
40570
  }
39884
40571
  /**
39885
40572
  * Builds filename slug from template and placeholder title.
@@ -39896,14 +40583,14 @@ function buildPromptSlug$1(templateSlugPrefix, title) {
39896
40583
  // Note: [💞] Ignore a discrepancy between file name and entity name
39897
40584
 
39898
40585
  /**
39899
- * Asks the user in the terminal whether the harness should be installed or updated right now.
40586
+ * Asks the user in the terminal whether an npm package should be installed or updated now.
39900
40587
  *
39901
40588
  * Note: `$` is used to indicate that this function is not a pure function - it reads the answer from stdin
39902
40589
  *
39903
40590
  * @returns `true` when the user confirms, `false` when the user declines or the terminal is not interactive
39904
40591
  * @private internal utility of `promptbookCli`
39905
40592
  */
39906
- async function $askForHarnessInstallationApproval(question) {
40593
+ async function $askForNpmPackageInstallationApproval(question) {
39907
40594
  if (!process.stdin.isTTY) {
39908
40595
  // Note: In non-interactive environments like CI there is nobody who could confirm the installation
39909
40596
  return false;
@@ -39919,7 +40606,7 @@ async function $askForHarnessInstallationApproval(question) {
39919
40606
  readlineInterface.close();
39920
40607
  }
39921
40608
  }
39922
- // Note: [🟡] Code for CLI harness installation approval [$askForHarnessInstallationApproval](src/cli/cli-commands/common/harness/$askForHarnessInstallationApproval.ts) should never be published outside of `@promptbook/cli`
40609
+ // Note: [🟡] Code for CLI npm package installation approval [$askForNpmPackageInstallationApproval](src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.ts) should never be published outside of `@promptbook/cli`
39923
40610
 
39924
40611
  /**
39925
40612
  * Builds the shell command which installs or updates one CLI coding harness globally.
@@ -40014,7 +40701,7 @@ async function $applyHarnessInstallationStatus(status) {
40014
40701
  return;
40015
40702
  }
40016
40703
  console.warn(colors.yellow(formatHarnessInstallationWarning(status)));
40017
- const isInstallationApproved = await $askForHarnessInstallationApproval(installationState === 'not-installed'
40704
+ const isInstallationApproved = await $askForNpmPackageInstallationApproval(installationState === 'not-installed'
40018
40705
  ? `Install ${definition.label} globally now?`
40019
40706
  : `Update ${definition.label} to ${latestVersion} now?`);
40020
40707
  if (!isInstallationApproved) {
@@ -40026,30 +40713,25 @@ async function $applyHarnessInstallationStatus(status) {
40026
40713
  // Note: [🟡] Code for CLI harness installation handling [$applyHarnessInstallationStatus](src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.ts) should never be published outside of `@promptbook/cli`
40027
40714
 
40028
40715
  /**
40029
- * Pattern matching one `major.minor.patch` version anywhere in the output of a harness version command.
40716
+ * Pattern matching one `major.minor.patch` version anywhere in npm or CLI command output.
40030
40717
  */
40031
- const HARNESS_VERSION_PATTERN = /\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/u;
40718
+ const NPM_PACKAGE_VERSION_PATTERN = /\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/gu;
40032
40719
  /**
40033
- * Extracts the version from the raw output of a harness `--version` command.
40720
+ * Extracts an npm package version from raw command output.
40034
40721
  *
40035
- * Harness CLIs print their version in many shapes, for example `2.1.199 (Claude Code)`,
40036
- * `codex-cli 0.144.4`, `GitHub Copilot CLI 1.0.61.` or after unrelated deprecation warning lines,
40037
- * so the first version-looking token of the first matching line wins.
40722
+ * Both npm and CLI commands may emit unrelated warnings before the version. CLI commands use the first matching token;
40723
+ * npm registry commands can opt into the last matching token because npm warnings commonly precede their JSON output.
40038
40724
  *
40039
40725
  * @returns The parsed version or `null` when the output contains no version
40040
40726
  * @private internal utility of `promptbookCli`
40041
40727
  */
40042
- function extractHarnessVersionFromOutput(output) {
40728
+ function extractNpmPackageVersionFromOutput(output, { isLastMatchPreferred = false } = {}) {
40043
40729
  var _a;
40044
- for (const line of output.split('\n')) {
40045
- const versionMatch = HARNESS_VERSION_PATTERN.exec(line);
40046
- if (versionMatch !== null) {
40047
- return (_a = versionMatch[1]) !== null && _a !== void 0 ? _a : null;
40048
- }
40049
- }
40050
- return null;
40730
+ const versionMatches = Array.from(output.matchAll(NPM_PACKAGE_VERSION_PATTERN));
40731
+ const versionMatch = isLastMatchPreferred ? versionMatches[versionMatches.length - 1] : versionMatches[0];
40732
+ return (_a = versionMatch === null || versionMatch === void 0 ? void 0 : versionMatch[1]) !== null && _a !== void 0 ? _a : null;
40051
40733
  }
40052
- // Note: [🟡] Code for CLI harness version parsing [extractHarnessVersionFromOutput](src/cli/cli-commands/common/harness/extractHarnessVersionFromOutput.ts) should never be published outside of `@promptbook/cli`
40734
+ // Note: [🟡] Code for CLI npm package version parsing [extractNpmPackageVersionFromOutput](src/cli/cli-commands/common/npm/extractNpmPackageVersionFromOutput.ts) should never be published outside of `@promptbook/cli`
40053
40735
 
40054
40736
  /**
40055
40737
  * Time limit for asking the globally installed harness command for its version.
@@ -40070,46 +40752,60 @@ async function $resolveInstalledHarnessVersion(definition) {
40070
40752
  timeout: HARNESS_VERSION_COMMAND_TIMEOUT_MS,
40071
40753
  isVerbose: false,
40072
40754
  }).catch(() => '');
40073
- return extractHarnessVersionFromOutput(output);
40755
+ return extractNpmPackageVersionFromOutput(output);
40074
40756
  }
40075
40757
  // Note: [🟡] Code for CLI harness version detection [$resolveInstalledHarnessVersion](src/cli/cli-commands/common/harness/$resolveInstalledHarnessVersion.ts) should never be published outside of `@promptbook/cli`
40076
40758
 
40077
40759
  /**
40078
- * Time limit for the whole `npm view` lookup of the newest published harness version.
40760
+ * Time limit for the whole `npm view` lookup of the newest published package version.
40079
40761
  */
40080
- const HARNESS_LATEST_VERSION_COMMAND_TIMEOUT_MS = 60 * 1000;
40762
+ const NPM_PACKAGE_LATEST_VERSION_COMMAND_TIMEOUT_MS = 60 * 1000;
40081
40763
  /**
40082
40764
  * Time limit for one npm registry request, so an unreachable registry never blocks the command.
40083
40765
  */
40084
40766
  const NPM_REGISTRY_FETCH_TIMEOUT_MS = 20 * 1000;
40085
40767
  /**
40086
- * Reads the newest version of the harness published to npm.
40768
+ * Pattern matching npm's JSON-encoded package version result.
40769
+ */
40770
+ const NPM_VIEW_JSON_VERSION_PATTERN = /"(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/u;
40771
+ /**
40772
+ * Reads the newest version of one npm package.
40087
40773
  *
40088
40774
  * Note: `$` is used to indicate that this function is not a pure function - it queries the npm registry
40089
40775
  *
40090
40776
  * @returns The newest published version or `null` when the registry could not be reached
40091
40777
  * @private internal utility of `promptbookCli`
40092
40778
  */
40093
- async function $resolveLatestHarnessVersion(definition) {
40779
+ async function $resolveLatestNpmPackageVersion(npmPackageName) {
40094
40780
  const output = await $execCommand({
40095
- command: `npm view ${definition.npmPackageName} version --fetch-timeout=${NPM_REGISTRY_FETCH_TIMEOUT_MS} --fetch-retries=1`,
40781
+ command: `npm view ${npmPackageName} version --json --loglevel=error --fetch-timeout=${NPM_REGISTRY_FETCH_TIMEOUT_MS} --fetch-retries=1`,
40096
40782
  crashOnError: true,
40097
- timeout: HARNESS_LATEST_VERSION_COMMAND_TIMEOUT_MS,
40783
+ timeout: NPM_PACKAGE_LATEST_VERSION_COMMAND_TIMEOUT_MS,
40098
40784
  isVerbose: false,
40099
40785
  }).catch(() => '');
40100
- return extractHarnessVersionFromOutput(output);
40786
+ return extractLatestNpmPackageVersionFromNpmViewOutput(output);
40787
+ }
40788
+ /**
40789
+ * Extracts the version from npm's JSON result while tolerating warnings emitted before or after that result.
40790
+ *
40791
+ * @private internal utility of `$resolveLatestNpmPackageVersion`
40792
+ */
40793
+ function extractLatestNpmPackageVersionFromNpmViewOutput(output) {
40794
+ var _a;
40795
+ const jsonVersionMatch = NPM_VIEW_JSON_VERSION_PATTERN.exec(output);
40796
+ return (_a = jsonVersionMatch === null || jsonVersionMatch === void 0 ? void 0 : jsonVersionMatch[1]) !== null && _a !== void 0 ? _a : extractNpmPackageVersionFromOutput(output, { isLastMatchPreferred: true });
40101
40797
  }
40102
- // Note: [🟡] Code for CLI harness registry lookup [$resolveLatestHarnessVersion](src/cli/cli-commands/common/harness/$resolveLatestHarnessVersion.ts) should never be published outside of `@promptbook/cli`
40798
+ // Note: [🟡] Code for CLI npm package registry lookup [$resolveLatestNpmPackageVersion](src/cli/cli-commands/common/npm/$resolveLatestNpmPackageVersion.ts) should never be published outside of `@promptbook/cli`
40103
40799
 
40104
40800
  /**
40105
- * Compares the installed harness version with the newest version published to npm.
40801
+ * Compares an installed npm package version with the newest published version.
40106
40802
  *
40107
40803
  * Only the numeric `major.minor.patch` segments are compared and pre-release suffixes are ignored,
40108
- * so a harness installed from a pre-release build of the newest version is not reported as outdated.
40804
+ * so a package installed from a pre-release build of the newest version is not reported as outdated.
40109
40805
  *
40110
40806
  * @private internal utility of `promptbookCli`
40111
40807
  */
40112
- function isHarnessVersionOutdated(installedVersion, latestVersion) {
40808
+ function isNpmPackageVersionOutdated(installedVersion, latestVersion) {
40113
40809
  var _a, _b;
40114
40810
  const installedSegments = parseVersionSegments(installedVersion);
40115
40811
  const latestSegments = parseVersionSegments(latestVersion);
@@ -40125,6 +40821,8 @@ function isHarnessVersionOutdated(installedVersion, latestVersion) {
40125
40821
  }
40126
40822
  /**
40127
40823
  * Splits one version into its numeric segments, dropping the pre-release and build suffix.
40824
+ *
40825
+ * @private internal utility of `isNpmPackageVersionOutdated`
40128
40826
  */
40129
40827
  function parseVersionSegments(version) {
40130
40828
  const [numericVersion = ''] = version.trim().split(/[-+]/u);
@@ -40133,7 +40831,7 @@ function parseVersionSegments(version) {
40133
40831
  return Number.isNaN(segmentNumber) ? 0 : segmentNumber;
40134
40832
  });
40135
40833
  }
40136
- // Note: [🟡] Code for CLI harness version comparison [isHarnessVersionOutdated](src/cli/cli-commands/common/harness/isHarnessVersionOutdated.ts) should never be published outside of `@promptbook/cli`
40834
+ // 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`
40137
40835
 
40138
40836
  /**
40139
40837
  * Detects whether one CLI coding harness is installed globally and whether it is up to date.
@@ -40145,7 +40843,7 @@ function parseVersionSegments(version) {
40145
40843
  async function $checkHarnessInstallation(definition) {
40146
40844
  const [installedVersion, latestVersion] = await Promise.all([
40147
40845
  $resolveInstalledHarnessVersion(definition),
40148
- $resolveLatestHarnessVersion(definition),
40846
+ $resolveLatestNpmPackageVersion(definition.npmPackageName),
40149
40847
  ]);
40150
40848
  return {
40151
40849
  definition,
@@ -40164,7 +40862,7 @@ function resolveHarnessInstallationState(installedVersion, latestVersion) {
40164
40862
  if (latestVersion === null) {
40165
40863
  return 'unknown';
40166
40864
  }
40167
- return isHarnessVersionOutdated(installedVersion, latestVersion) ? 'outdated' : 'up-to-date';
40865
+ return isNpmPackageVersionOutdated(installedVersion, latestVersion) ? 'outdated' : 'up-to-date';
40168
40866
  }
40169
40867
  // Note: [🟡] Code for CLI harness installation check [$checkHarnessInstallation](src/cli/cli-commands/common/harness/$checkHarnessInstallation.ts) should never be published outside of `@promptbook/cli`
40170
40868
 
@@ -40396,11 +41094,12 @@ async function ensureCoderGitignoreFile(projectPath) {
40396
41094
  * Default npm scripts initialized by `ptbk coder init`.
40397
41095
  */
40398
41096
  const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
40399
- 'coder:generate-boilerplates': `ptbk coder generate-boilerplates --count ${DEFAULT_BOILERPLATE_COUNT_OPTION_VALUE} --template ./prompts/templates/common.md`,
40400
- 'coder:add': 'ptbk coder add --template ./prompts/templates/common.md',
40401
- 'coder:run': 'ptbk coder run --harness openai-codex --model gpt-5.6-terra --thinking-level max --agent agents/developer.book --context AGENTS.md --test-before yes-and-fix',
41097
+ // Note: Using NPX because `ptbk` can be installed globally or locally, and NPX will resolve it correctly in either case.
41098
+ 'coder:generate-boilerplates': `npx ptbk coder generate-boilerplates --count ${DEFAULT_BOILERPLATE_COUNT_OPTION_VALUE} --template ./prompts/templates/common.md`,
41099
+ 'coder:add': 'npx ptbk coder add --template ./prompts/templates/common.md',
41100
+ 'coder:run': 'npx ptbk coder run --harness openai-codex --model gpt-5.6-terra --thinking-level max --agent agents/developer.book --context AGENTS.md --test-before yes-and-fix',
40402
41101
  // 'coder:find-refactor-candidates': 'npx ptbk coder find-refactor-candidates',
40403
- 'coder:verify': 'ptbk coder verify',
41102
+ 'coder:verify': 'npx ptbk coder verify',
40404
41103
  };
40405
41104
  /**
40406
41105
  * Lists the default npm scripts initialized by `ptbk coder init`.
@@ -40770,14 +41469,14 @@ function $initializeCoderInitCommand(program) {
40770
41469
  const gitSync = normalizeCoderGitSyncCliOptions(cliOptions);
40771
41470
  const projectPath = process.cwd();
40772
41471
  // Note: Import the git synchronization dynamically to keep the CLI fast for runs without `--commit`
40773
- const { $commitCoderChanges, $pullCoderChanges } = await Promise.resolve().then(function () { return coderGitSync; });
40774
- await $pullCoderChanges({ gitSync, projectPath });
41472
+ const { $commitCoderChanges, $startCoderGitSync } = await Promise.resolve().then(function () { return coderGitSync; });
41473
+ const commitScope = await $startCoderGitSync({ gitSync, projectPath });
40775
41474
  const summary = await initializeCoderProjectConfiguration(projectPath);
40776
41475
  printInitializationSummary(summary);
40777
41476
  await generatePromptBoilerplate({ projectPath, boilerplateCount: DEFAULT_BOILERPLATE_COUNT });
40778
41477
  await $commitCoderChanges({
40779
41478
  gitSync,
40780
- projectPath,
41479
+ commitScope,
40781
41480
  commitMessage: 'Initialize Promptbook Coder',
40782
41481
  });
40783
41482
  await $ensureHarnessInstallations(CODER_INIT_CHECKED_HARNESS_NAMES);
@@ -40866,6 +41565,370 @@ function createNonNegativeIntegerOptionParser(optionName) {
40866
41565
  }
40867
41566
  // Note: [🟡] Code for CLI option parser [createNonNegativeIntegerOptionParser](src/cli/cli-commands/common/createNonNegativeIntegerOptionParser.ts) should never be published outside of `@promptbook/cli`
40868
41567
 
41568
+ /**
41569
+ * Npm packages which provide an executable Promptbook CLI.
41570
+ *
41571
+ * `ptbk` is the preferred package and forwards to `@promptbook/cli`; the latter can also be installed directly.
41572
+ *
41573
+ * @private internal utility of `promptbookCli`
41574
+ */
41575
+ const PROMPTBOOK_CLI_NPM_PACKAGE_NAMES = ['ptbk', '@promptbook/cli'];
41576
+ // Note: [🟡] Code for Promptbook CLI installation definitions [PromptbookCliInstallation](src/cli/cli-commands/common/promptbook-cli/PromptbookCliInstallation.ts) should never be published outside of `@promptbook/cli`
41577
+
41578
+ /**
41579
+ * Time limit for resolving npm's global `node_modules` directory.
41580
+ */
41581
+ const NPM_GLOBAL_NODE_MODULES_PATH_COMMAND_TIMEOUT_MS = 30 * 1000;
41582
+ /**
41583
+ * Finds every locally or globally installed Promptbook CLI package which can be updated.
41584
+ *
41585
+ * A local package must be declared directly in the current project's `package.json`; transitive packages are not
41586
+ * updated because this command must not change dependencies chosen by another package.
41587
+ *
41588
+ * Note: `$` is used to indicate that this function is not a pure function - it reads package manifests and runs npm
41589
+ *
41590
+ * @private internal utility of `promptbookCli`
41591
+ */
41592
+ async function $resolvePromptbookCliInstallations() {
41593
+ const [localInstallations, globalInstallations] = await Promise.all([
41594
+ $resolveLocalPromptbookCliInstallations(),
41595
+ $resolveGlobalPromptbookCliInstallations(),
41596
+ ]);
41597
+ return [...localInstallations, ...globalInstallations];
41598
+ }
41599
+ /**
41600
+ * Finds Promptbook CLI packages installed directly in the current project's dependencies.
41601
+ *
41602
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41603
+ */
41604
+ async function $resolveLocalPromptbookCliInstallations() {
41605
+ const projectPath = process.cwd();
41606
+ const projectPackageJson = await $readJsonObject(join(projectPath, 'package.json'));
41607
+ if (projectPackageJson === null) {
41608
+ return [];
41609
+ }
41610
+ const installations = await Promise.all(PROMPTBOOK_CLI_NPM_PACKAGE_NAMES.map(async (npmPackageName) => {
41611
+ const installationLocation = resolveLocalPromptbookCliInstallationLocation(projectPackageJson, npmPackageName);
41612
+ if (installationLocation === undefined) {
41613
+ return undefined;
41614
+ }
41615
+ const installedVersion = await $resolveInstalledNpmPackageVersion(join(projectPath, 'node_modules'), npmPackageName);
41616
+ if (installedVersion === undefined) {
41617
+ return undefined;
41618
+ }
41619
+ return { npmPackageName, installedVersion, installationLocation };
41620
+ }));
41621
+ return installations.filter(isDefined);
41622
+ }
41623
+ /**
41624
+ * Finds Promptbook CLI packages installed in npm's global `node_modules` directory.
41625
+ *
41626
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41627
+ */
41628
+ async function $resolveGlobalPromptbookCliInstallations() {
41629
+ const globalNodeModulesPath = await $resolveNpmGlobalNodeModulesPath();
41630
+ if (globalNodeModulesPath === null) {
41631
+ return [];
41632
+ }
41633
+ const installations = await Promise.all(PROMPTBOOK_CLI_NPM_PACKAGE_NAMES.map(async (npmPackageName) => {
41634
+ const installedVersion = await $resolveInstalledNpmPackageVersion(globalNodeModulesPath, npmPackageName);
41635
+ if (installedVersion === undefined) {
41636
+ return undefined;
41637
+ }
41638
+ return {
41639
+ npmPackageName,
41640
+ installedVersion,
41641
+ installationLocation: 'global',
41642
+ };
41643
+ }));
41644
+ return installations.filter(isDefined);
41645
+ }
41646
+ /**
41647
+ * Resolves the current project's manifest section that declares a Promptbook CLI package.
41648
+ *
41649
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41650
+ */
41651
+ function resolveLocalPromptbookCliInstallationLocation(packageJson, npmPackageName) {
41652
+ if (readNestedStringProperty(packageJson, 'dependencies', npmPackageName) !== undefined) {
41653
+ return 'local-dependency';
41654
+ }
41655
+ if (readNestedStringProperty(packageJson, 'devDependencies', npmPackageName) !== undefined) {
41656
+ return 'local-development-dependency';
41657
+ }
41658
+ return undefined;
41659
+ }
41660
+ /**
41661
+ * Reads an installed npm package version from a `node_modules` directory.
41662
+ *
41663
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41664
+ */
41665
+ async function $resolveInstalledNpmPackageVersion(nodeModulesPath, npmPackageName) {
41666
+ const installedPackageJson = await $readJsonObject(join(nodeModulesPath, npmPackageName, 'package.json'));
41667
+ return installedPackageJson === null ? undefined : readStringProperty(installedPackageJson, 'version');
41668
+ }
41669
+ /**
41670
+ * Resolves npm's global `node_modules` directory without querying the registry.
41671
+ *
41672
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41673
+ */
41674
+ async function $resolveNpmGlobalNodeModulesPath() {
41675
+ const output = await $execCommand({
41676
+ command: 'npm root --global',
41677
+ crashOnError: true,
41678
+ timeout: NPM_GLOBAL_NODE_MODULES_PATH_COMMAND_TIMEOUT_MS,
41679
+ isVerbose: false,
41680
+ }).catch(() => '');
41681
+ const globalNodeModulesPath = output.trim();
41682
+ return globalNodeModulesPath === '' ? null : globalNodeModulesPath;
41683
+ }
41684
+ /**
41685
+ * Reads a JSON object, returning `null` for missing or malformed files because update checks must stay advisory.
41686
+ *
41687
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41688
+ */
41689
+ async function $readJsonObject(filePath) {
41690
+ try {
41691
+ const parsedValue = JSON.parse(await readFile(filePath, 'utf8'));
41692
+ return isJsonObject(parsedValue) ? parsedValue : null;
41693
+ }
41694
+ catch (_a) {
41695
+ return null;
41696
+ }
41697
+ }
41698
+ /**
41699
+ * Checks whether an unknown value can be read as a JSON object.
41700
+ *
41701
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41702
+ */
41703
+ function isJsonObject(value) {
41704
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
41705
+ }
41706
+ /**
41707
+ * Reads one string property from a JSON object.
41708
+ *
41709
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41710
+ */
41711
+ function readStringProperty(jsonObject, propertyName) {
41712
+ const value = jsonObject[propertyName];
41713
+ return typeof value === 'string' ? value : undefined;
41714
+ }
41715
+ /**
41716
+ * Reads one string property nested under another JSON object property.
41717
+ *
41718
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41719
+ */
41720
+ function readNestedStringProperty(jsonObject, parentPropertyName, propertyName) {
41721
+ const nestedValue = jsonObject[parentPropertyName];
41722
+ return isJsonObject(nestedValue) ? readStringProperty(nestedValue, propertyName) : undefined;
41723
+ }
41724
+ /**
41725
+ * Narrows an array after `undefined` values were removed.
41726
+ *
41727
+ * @private internal utility of `$resolvePromptbookCliInstallations`
41728
+ */
41729
+ function isDefined(value) {
41730
+ return value !== undefined;
41731
+ }
41732
+ // Note: [🟡] Code for Promptbook CLI installation detection [$resolvePromptbookCliInstallations](src/cli/cli-commands/common/promptbook-cli/$resolvePromptbookCliInstallations.ts) should never be published outside of `@promptbook/cli`
41733
+
41734
+ /**
41735
+ * Checks every local and global Promptbook CLI installation against the newest npm version.
41736
+ *
41737
+ * Note: `$` is used to indicate that this function is not a pure function - it reads installed packages and queries npm
41738
+ *
41739
+ * @private internal utility of `promptbookCli`
41740
+ */
41741
+ async function $checkPromptbookCliInstallations() {
41742
+ const installations = await $resolvePromptbookCliInstallations();
41743
+ if (installations.length === 0) {
41744
+ return [];
41745
+ }
41746
+ const latestVersionsByNpmPackageName = await $resolveLatestVersionsByNpmPackageName(installations);
41747
+ return installations.map((installation) => {
41748
+ var _a;
41749
+ const latestVersion = (_a = latestVersionsByNpmPackageName.get(installation.npmPackageName)) !== null && _a !== void 0 ? _a : null;
41750
+ return {
41751
+ installation,
41752
+ installationState: resolvePromptbookCliInstallationState(installation.installedVersion, latestVersion),
41753
+ latestVersion,
41754
+ };
41755
+ });
41756
+ }
41757
+ /**
41758
+ * Resolves each package's newest version once, even when it is installed locally and globally.
41759
+ *
41760
+ * @private internal utility of `$checkPromptbookCliInstallations`
41761
+ */
41762
+ async function $resolveLatestVersionsByNpmPackageName(installations) {
41763
+ const npmPackageNames = Array.from(new Set(installations.map(({ npmPackageName }) => npmPackageName)));
41764
+ const latestVersionEntries = await Promise.all(npmPackageNames.map(async (npmPackageName) => {
41765
+ const latestVersion = await $resolveLatestNpmPackageVersion(npmPackageName);
41766
+ return [npmPackageName, latestVersion];
41767
+ }));
41768
+ return new Map(latestVersionEntries);
41769
+ }
41770
+ /**
41771
+ * Derives the update state of one installed Promptbook CLI package.
41772
+ *
41773
+ * @private internal utility of `$checkPromptbookCliInstallations`
41774
+ */
41775
+ function resolvePromptbookCliInstallationState(installedVersion, latestVersion) {
41776
+ if (latestVersion === null) {
41777
+ return 'unknown';
41778
+ }
41779
+ return isNpmPackageVersionOutdated(installedVersion, latestVersion) ? 'outdated' : 'up-to-date';
41780
+ }
41781
+ // Note: [🟡] Code for Promptbook CLI installation check [$checkPromptbookCliInstallations](src/cli/cli-commands/common/promptbook-cli/$checkPromptbookCliInstallations.ts) should never be published outside of `@promptbook/cli`
41782
+
41783
+ /**
41784
+ * Builds the npm command that updates one existing Promptbook CLI installation.
41785
+ *
41786
+ * @private internal utility of `promptbookCli`
41787
+ */
41788
+ function buildPromptbookCliInstallCommand(installation) {
41789
+ const { npmPackageName, installationLocation } = installation;
41790
+ if (installationLocation === 'global') {
41791
+ return `npm install --global ${npmPackageName}@latest`;
41792
+ }
41793
+ if (installationLocation === 'local-development-dependency') {
41794
+ return `npm install --save-dev ${npmPackageName}@latest`;
41795
+ }
41796
+ return `npm install --save ${npmPackageName}@latest`;
41797
+ }
41798
+ // Note: [🟡] Code for Promptbook CLI installation command [buildPromptbookCliInstallCommand](src/cli/cli-commands/common/promptbook-cli/buildPromptbookCliInstallCommand.ts) should never be published outside of `@promptbook/cli`
41799
+
41800
+ /**
41801
+ * Updates one Promptbook CLI package in its existing local or global location.
41802
+ *
41803
+ * Note: `$` is used to indicate that this function is not a pure function - it installs an npm package
41804
+ *
41805
+ * @returns `true` when the package update succeeds, `false` when it fails
41806
+ * @private internal utility of `promptbookCli`
41807
+ */
41808
+ async function $updatePromptbookCliInstallation(status) {
41809
+ const { npmPackageName } = status.installation;
41810
+ const installCommand = buildPromptbookCliInstallCommand(status.installation);
41811
+ console.info(colors.cyan(`Updating Promptbook CLI package \`${npmPackageName}\` with \`${installCommand}\`...`));
41812
+ try {
41813
+ await $execCommand({
41814
+ command: installCommand,
41815
+ crashOnError: true,
41816
+ isVerbose: true,
41817
+ });
41818
+ }
41819
+ catch (error) {
41820
+ assertsError(error);
41821
+ const updateErrorMessage = error.message;
41822
+ console.error(colors.red(spaceTrim$1((block) => `
41823
+ Could not update **Promptbook CLI** package \`${npmPackageName}\`.
41824
+
41825
+ Update it manually with \`${installCommand}\` and run the command again.
41826
+
41827
+ ${block(updateErrorMessage)}
41828
+ `)));
41829
+ return false;
41830
+ }
41831
+ console.info(colors.green(`Promptbook CLI package \`${npmPackageName}\` has been updated.`));
41832
+ return true;
41833
+ }
41834
+ // Note: [🟡] Code for Promptbook CLI installation update [$updatePromptbookCliInstallation](src/cli/cli-commands/common/promptbook-cli/$updatePromptbookCliInstallation.ts) should never be published outside of `@promptbook/cli`
41835
+
41836
+ /**
41837
+ * Formats the warning shown when one or more Promptbook CLI installations are outdated.
41838
+ *
41839
+ * @private internal utility of `promptbookCli`
41840
+ */
41841
+ function formatPromptbookCliInstallationWarning(statuses) {
41842
+ const statusLines = statuses.map((status) => {
41843
+ const { npmPackageName, installedVersion, installationLocation } = status.installation;
41844
+ return `- ${formatPromptbookCliInstallationLocation(installationLocation)} \`${npmPackageName}\`: installed \`${installedVersion}\`, newest \`${status.latestVersion}\``;
41845
+ });
41846
+ return spaceTrim$1((block) => `
41847
+ **Promptbook CLI** is outdated.
41848
+
41849
+ ${block(statusLines.join('\n'))}
41850
+ `);
41851
+ }
41852
+ /**
41853
+ * Describes an installation location in text suitable for an update warning.
41854
+ *
41855
+ * @private internal utility of `formatPromptbookCliInstallationWarning`
41856
+ */
41857
+ function formatPromptbookCliInstallationLocation(installationLocation) {
41858
+ if (installationLocation === 'global') {
41859
+ return 'Globally installed';
41860
+ }
41861
+ if (installationLocation === 'local-development-dependency') {
41862
+ return 'Locally in `devDependencies`';
41863
+ }
41864
+ return 'Locally in `dependencies`';
41865
+ }
41866
+ // Note: [🟡] Code for Promptbook CLI installation warning [formatPromptbookCliInstallationWarning](src/cli/cli-commands/common/promptbook-cli/formatPromptbookCliInstallationWarning.ts) should never be published outside of `@promptbook/cli`
41867
+
41868
+ /**
41869
+ * Checks local and global Promptbook CLI installations before an interactive coder run and offers to update them.
41870
+ *
41871
+ * Once an update is approved, the caller must stop the current run and let the user start a fresh process. A local
41872
+ * npm update can modify `package.json` and a lockfile, which would make the coder's working-tree preflight fail;
41873
+ * more importantly, this Node.js process has already loaded the old CLI implementation.
41874
+ *
41875
+ * Note: `$` is used to indicate that this function is not a pure function - it reads package manifests, queries npm,
41876
+ * asks the user, and may install npm packages
41877
+ *
41878
+ * @returns `true` when an update was approved and the current coder run should stop
41879
+ * @private internal utility of `promptbookCli`
41880
+ */
41881
+ async function $ensurePromptbookCliInstallations() {
41882
+ if (!process.stdin.isTTY) {
41883
+ // Note: Non-interactive runs cannot approve an update and should not wait for registry checks.
41884
+ return false;
41885
+ }
41886
+ const statuses = await $checkPromptbookCliInstallations();
41887
+ const outdatedStatuses = statuses.filter(({ installationState }) => installationState === 'outdated');
41888
+ if (outdatedStatuses.length === 0) {
41889
+ reportPromptbookCliInstallationStatuses(statuses);
41890
+ return false;
41891
+ }
41892
+ console.warn(colors.yellow(formatPromptbookCliInstallationWarning(outdatedStatuses)));
41893
+ const isUpdateApproved = await $askForNpmPackageInstallationApproval('Update Promptbook CLI now?');
41894
+ if (!isUpdateApproved) {
41895
+ const manualInstallCommands = outdatedStatuses
41896
+ .map((status) => `- \`${buildPromptbookCliInstallCommand(status.installation)}\``)
41897
+ .join('\n');
41898
+ console.info(colors.gray(spaceTrim$1((block) => `
41899
+ Skipped updating Promptbook CLI.
41900
+
41901
+ Run one of these commands manually:
41902
+
41903
+ ${block(manualInstallCommands)}
41904
+ `)));
41905
+ return false;
41906
+ }
41907
+ for (const status of outdatedStatuses) {
41908
+ await $updatePromptbookCliInstallation(status);
41909
+ }
41910
+ console.info(colors.green('Promptbook CLI update is complete. Run the command again to use the new version.'));
41911
+ return true;
41912
+ }
41913
+ /**
41914
+ * Reports successful and unverifiable Promptbook CLI checks when no update is required.
41915
+ *
41916
+ * @private internal utility of `$ensurePromptbookCliInstallations`
41917
+ */
41918
+ function reportPromptbookCliInstallationStatuses(statuses) {
41919
+ for (const status of statuses) {
41920
+ const { npmPackageName, installedVersion } = status.installation;
41921
+ if (status.installationState === 'up-to-date') {
41922
+ console.info(colors.gray(`✔ Promptbook CLI \`${npmPackageName}\` ${installedVersion} is up to date.`));
41923
+ continue;
41924
+ }
41925
+ if (status.installationState === 'unknown') {
41926
+ console.info(colors.gray(`✔ Promptbook CLI \`${npmPackageName}\` ${installedVersion} is installed; the newest version could not be checked.`));
41927
+ }
41928
+ }
41929
+ }
41930
+ // Note: [🟡] Code for Promptbook CLI installation orchestration [$ensurePromptbookCliInstallations](src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.ts) should never be published outside of `@promptbook/cli`
41931
+
40869
41932
  /**
40870
41933
  * Modes supported by `ptbk coder run --test-before`.
40871
41934
  */
@@ -40918,12 +41981,14 @@ function $initializeCoderRunCommand(program) {
40918
41981
 
40919
41982
  Features:
40920
41983
  - Automatically stages and commits changes with agent identity unless --no-commit is used
41984
+ - Commits only the prompt file and the files the coding agent has changed, leaving unrelated changes alone
40921
41985
  - Optional post-commit git push with explicit --auto-push opt-in
40922
41986
  - Optional pre-prompt git pull with explicit --auto-pull opt-in
40923
41987
  - Optional --isolate runs every prompt in its own temporary git worktree and merges it back when verified
40924
41988
  - Optional --preserve-logs keeps temp prompt/log artifacts after successful rounds
40925
41989
  - Optional --no-ui keeps plain streaming console output for logging and debugging
40926
41990
  - Checks that the selected harness is installed globally and up to date before the first prompt
41991
+ - In interactive mode, checks local and global Promptbook CLI installations and offers to update them
40927
41992
  - Supports GPG signing of commits
40928
41993
  - Optional pre-coding test run that can stop or repair pre-existing failures
40929
41994
  - Optional post-prompt verification with test-feedback retries
@@ -40973,7 +42038,6 @@ function $initializeCoderRunCommand(program) {
40973
42038
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
40974
42039
  isAgentRequired: !dryRun,
40975
42040
  });
40976
- await $ensureHarnessInstallations([runnerOptions.agentName]);
40977
42041
  // [1] Parse the wait options and --no-auto:
40978
42042
  // default: run automatically through the queue (no waiting between prompts)
40979
42043
  // --no-auto: wait for user confirmation before each prompt (interactive mode)
@@ -40981,6 +42045,10 @@ function $initializeCoderRunCommand(program) {
40981
42045
  // --wait-between-prompts: pace from start of one prompt to start of next
40982
42046
  // --wait-after-error: wait before retrying after an error (default 10m)
40983
42047
  const waitForUser = !auto;
42048
+ if (waitForUser && (await $ensurePromptbookCliInstallations())) {
42049
+ return process.exit(0);
42050
+ }
42051
+ await $ensureHarnessInstallations([runnerOptions.agentName]);
40984
42052
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
40985
42053
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
40986
42054
  const waitAfterError = parseOptionalWaitDuration(waitAfterErrorValue, DEFAULT_WAIT_AFTER_ERROR_MS$1);
@@ -44057,8 +45125,8 @@ const formatCommandParser = {
44057
45125
  */
44058
45126
  $applyToTaskJson(command, $taskJson) {
44059
45127
  if ($taskJson.format !== undefined && command.format !== $taskJson.format) {
44060
- throw new ParseError(`Format format is already defined to "${$taskJson.format}".
44061
- Now you try to redefine it by "${command.format}"`);
45128
+ throw new ParseError(spaceTrim$1(`Format format is already defined to "${$taskJson.format}".
45129
+ Now you try to redefine it by "${command.format}"`));
44062
45130
  }
44063
45131
  $taskJson.format = command.format;
44064
45132
  },
@@ -56901,28 +57969,28 @@ function parseToolExecutionEnvelope(rawValue) {
56901
57969
  */
56902
57970
  function buildToolInvocationScript(options) {
56903
57971
  const { functionName, functionArgsExpression } = options;
56904
- return `
56905
- const args = ${functionArgsExpression};
56906
- const runtimeContextRaw =
56907
- typeof ${TOOL_RUNTIME_CONTEXT_PARAMETER} === 'undefined'
56908
- ? undefined
56909
- : ${TOOL_RUNTIME_CONTEXT_PARAMETER};
57972
+ return spaceTrim$1((block) => `
57973
+ const args = ${block(functionArgsExpression)};
57974
+ const runtimeContextRaw =
57975
+ typeof ${TOOL_RUNTIME_CONTEXT_PARAMETER} === 'undefined'
57976
+ ? undefined
57977
+ : ${TOOL_RUNTIME_CONTEXT_PARAMETER};
56910
57978
 
56911
- if (runtimeContextRaw !== undefined && args && typeof args === 'object' && !Array.isArray(args)) {
56912
- args.${TOOL_RUNTIME_CONTEXT_ARGUMENT} = runtimeContextRaw;
56913
- }
57979
+ if (runtimeContextRaw !== undefined && args && typeof args === 'object' && !Array.isArray(args)) {
57980
+ args.${TOOL_RUNTIME_CONTEXT_ARGUMENT} = runtimeContextRaw;
57981
+ }
56914
57982
 
56915
- const toolProgressTokenRaw =
56916
- typeof ${TOOL_PROGRESS_TOKEN_PARAMETER} === 'undefined'
56917
- ? undefined
56918
- : ${TOOL_PROGRESS_TOKEN_PARAMETER};
57983
+ const toolProgressTokenRaw =
57984
+ typeof ${TOOL_PROGRESS_TOKEN_PARAMETER} === 'undefined'
57985
+ ? undefined
57986
+ : ${TOOL_PROGRESS_TOKEN_PARAMETER};
56919
57987
 
56920
- if (toolProgressTokenRaw !== undefined && args && typeof args === 'object' && !Array.isArray(args)) {
56921
- args.${TOOL_PROGRESS_TOKEN_ARGUMENT} = toolProgressTokenRaw;
56922
- }
57988
+ if (toolProgressTokenRaw !== undefined && args && typeof args === 'object' && !Array.isArray(args)) {
57989
+ args.${TOOL_PROGRESS_TOKEN_ARGUMENT} = toolProgressTokenRaw;
57990
+ }
56923
57991
 
56924
- return await ${functionName}(args);
56925
- `;
57992
+ return await ${functionName}(args);
57993
+ `);
56926
57994
  }
56927
57995
 
56928
57996
  /**
@@ -68868,6 +69936,28 @@ var RemoteAgent$1 = /*#__PURE__*/Object.freeze({
68868
69936
  RemoteAgent: RemoteAgent
68869
69937
  });
68870
69938
 
69939
+ /**
69940
+ * Captures the working tree state before one `ptbk coder` operation changes anything.
69941
+ *
69942
+ * The captured scope is passed to the commit of the very same operation, which then commits exactly the files
69943
+ * this operation has created, changed, moved or deleted.
69944
+ */
69945
+ async function captureCoderCommitScope(projectPath) {
69946
+ return {
69947
+ projectPath,
69948
+ snapshotBeforeOperation: await captureWorkingTreeChangesSnapshot(projectPath),
69949
+ };
69950
+ }
69951
+ /**
69952
+ * Resolves the repository-relative paths which one `ptbk coder` operation has really changed.
69953
+ *
69954
+ * Files which were already changed before the operation started and which the operation did not touch are
69955
+ * never part of the result, so they stay in the working tree instead of being swept into the commit.
69956
+ */
69957
+ async function resolveCoderCommitScopePaths(scope) {
69958
+ return listFilesChangedSinceSnapshot(scope.projectPath, scope.snapshotBeforeOperation);
69959
+ }
69960
+
68871
69961
  /**
68872
69962
  * Git synchronization which leaves the repository completely untouched.
68873
69963
  *
@@ -68878,6 +69968,24 @@ const DISABLED_CODER_GIT_SYNC_OPTIONS = Object.freeze({
68878
69968
  isAutoPushEnabled: false,
68879
69969
  isAutoPullEnabled: false,
68880
69970
  });
69971
+ /**
69972
+ * Pulls the latest repository changes and captures the working tree state before a `ptbk coder` command
69973
+ * changes the project.
69974
+ *
69975
+ * The returned scope is handed over to `$commitCoderChanges` of the very same command, which then commits
69976
+ * exactly the files this command has changed.
69977
+ */
69978
+ async function $startCoderGitSync(options) {
69979
+ const { gitSync, projectPath = process.cwd() } = options;
69980
+ await $pullCoderChanges({ gitSync, projectPath });
69981
+ if (!gitSync.isCommitEnabled) {
69982
+ // Note: A command which does not commit must not touch git at all, so that it also works in a project
69983
+ // which is not a git repository
69984
+ return { projectPath, snapshotBeforeOperation: { changedFileHashes: new Map() } };
69985
+ }
69986
+ // Note: The scope is captured after pulling, so files brought in by the pull are not committed again
69987
+ return captureCoderCommitScope(projectPath);
69988
+ }
68881
69989
  /**
68882
69990
  * Pulls the latest repository changes before a `ptbk coder` command changes the project.
68883
69991
  */
@@ -68892,38 +70000,31 @@ async function $pullCoderChanges(options) {
68892
70000
  /**
68893
70001
  * Commits - and when requested also pushes - the changes one `ptbk coder` command has just made.
68894
70002
  *
68895
- * Note: A repository without any change is left alone instead of creating an empty commit.
70003
+ * Note: Only the files this very command has changed are committed, everything else is left in the working tree.
70004
+ * Note: A command which changed nothing is left alone instead of creating an empty commit.
68896
70005
  */
68897
70006
  async function $commitCoderChanges(options) {
68898
- const { gitSync, commitMessage, projectPath = process.cwd() } = options;
70007
+ const { gitSync, commitMessage, commitScope } = options;
68899
70008
  if (!gitSync.isCommitEnabled) {
68900
70009
  return;
68901
70010
  }
68902
- if (!(await hasChangesToCommit(projectPath))) {
68903
- console.info(colors.gray('Nothing to commit, the working tree is clean'));
70011
+ const relevantPaths = await resolveCoderCommitScopePaths(commitScope);
70012
+ if (relevantPaths.length === 0) {
70013
+ console.info(colors.gray('Nothing to commit, this command has not changed any file'));
68904
70014
  return;
68905
70015
  }
68906
70016
  await commitChanges(commitMessage, {
68907
- projectPath,
70017
+ projectPath: commitScope.projectPath,
70018
+ relevantPaths,
68908
70019
  autoPush: gitSync.isAutoPushEnabled,
68909
70020
  });
68910
70021
  console.info(colors.green(`✓ ${gitSync.isAutoPushEnabled ? 'Committed and pushed' : 'Committed'}: ${commitMessage}`));
68911
70022
  }
68912
- /**
68913
- * Checks whether the repository holds any change which can be committed.
68914
- */
68915
- async function hasChangesToCommit(projectPath) {
68916
- const gitStatus = await runGitCommand({
68917
- command: 'git status --porcelain',
68918
- cwd: projectPath,
68919
- isVerbose: false,
68920
- });
68921
- return gitStatus.trim() !== '';
68922
- }
68923
70023
 
68924
70024
  var coderGitSync = /*#__PURE__*/Object.freeze({
68925
70025
  __proto__: null,
68926
70026
  DISABLED_CODER_GIT_SYNC_OPTIONS: DISABLED_CODER_GIT_SYNC_OPTIONS,
70027
+ $startCoderGitSync: $startCoderGitSync,
68927
70028
  $pullCoderChanges: $pullCoderChanges,
68928
70029
  $commitCoderChanges: $commitCoderChanges
68929
70030
  });
@@ -72261,34 +73362,34 @@ async function migratePrefix(options) {
72261
73362
  * @private function of runDatabaseMigrations
72262
73363
  */
72263
73364
  async function ensureMigrationsTableSchema(options) {
72264
- await options.client.query(`
73365
+ await options.client.query(spaceTrim$1(`
72265
73366
  CREATE TABLE IF NOT EXISTS ${options.migrationsTableIdentifier} (
72266
73367
  "filename" TEXT PRIMARY KEY,
72267
73368
  "appliedAt" TIMESTAMP WITH TIME ZONE DEFAULT now(),
72268
73369
  "appliedBy" TEXT NOT NULL DEFAULT '${options.manualAppliedByDefault}'
72269
73370
  );
72270
- `);
72271
- await options.client.query(`
73371
+ `));
73372
+ await options.client.query(spaceTrim$1(`
72272
73373
  ALTER TABLE ${options.migrationsTableIdentifier}
72273
73374
  ADD COLUMN IF NOT EXISTS "appliedBy" TEXT;
72274
- `);
72275
- await options.client.query(`
73375
+ `));
73376
+ await options.client.query(spaceTrim$1(`
72276
73377
  UPDATE ${options.migrationsTableIdentifier}
72277
73378
  SET "appliedBy" = $1
72278
73379
  WHERE "appliedBy" IS NULL;
72279
- `, [options.manualAppliedByDefault]);
72280
- await options.client.query(`
73380
+ `), [options.manualAppliedByDefault]);
73381
+ await options.client.query(spaceTrim$1(`
72281
73382
  ALTER TABLE ${options.migrationsTableIdentifier}
72282
73383
  ALTER COLUMN "appliedBy" SET DEFAULT '${options.manualAppliedByDefault}';
72283
- `);
72284
- await options.client.query(`
73384
+ `));
73385
+ await options.client.query(spaceTrim$1(`
72285
73386
  ALTER TABLE ${options.migrationsTableIdentifier}
72286
73387
  ALTER COLUMN "appliedBy" SET NOT NULL;
72287
- `);
72288
- await options.client.query(`
73388
+ `));
73389
+ await options.client.query(spaceTrim$1(`
72289
73390
  ALTER TABLE ${options.migrationsTableIdentifier}
72290
73391
  ENABLE ROW LEVEL SECURITY;
72291
- `);
73392
+ `));
72292
73393
  }
72293
73394
  /**
72294
73395
  * Rolls back an opened transaction and logs failures without masking original errors.
@@ -73246,9 +74347,9 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
73246
74347
  });
73247
74348
  const promptExecutionStartedDate = moment();
73248
74349
  let attemptCount = 1;
73249
- const roundChangedFilesSnapshot = options.normalizeLineEndings
73250
- ? await captureChangedFilesSnapshot(roundProjectPath)
73251
- : undefined;
74350
+ // Note: The very same snapshot tells which files this round has changed, both for normalizing their line
74351
+ // endings and for committing only them instead of everything which is changed in the project
74352
+ const roundCommitScope = await captureRoundCommitScopeIfNeeded(options, roundProjectPath);
73252
74353
  await withPromptRuntimeLog(scriptPath, async (logPath) => {
73253
74354
  let lastError;
73254
74355
  for (let errorRetryAttempt = 0; errorRetryAttempt <= MAX_RETRY_ATTEMPTS_AFTER_ERROR; errorRetryAttempt++) {
@@ -73284,7 +74385,7 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
73284
74385
  result,
73285
74386
  commitMessage,
73286
74387
  logPath,
73287
- roundChangedFilesSnapshot,
74388
+ roundCommitScope,
73288
74389
  isRichUiEnabled,
73289
74390
  progressDisplay,
73290
74391
  uiHandle,
@@ -73317,7 +74418,7 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
73317
74418
  attemptCount,
73318
74419
  error: lastError,
73319
74420
  options,
73320
- roundChangedFilesSnapshot,
74421
+ roundCommitScope,
73321
74422
  uiHandle,
73322
74423
  waitForRequestedPause,
73323
74424
  roundProjectPath,
@@ -73398,7 +74499,7 @@ function setPromptRoundRunningState(options) {
73398
74499
  * Finalizes a successful prompt round, including prompt bookkeeping and commit flow.
73399
74500
  */
73400
74501
  async function finalizeSuccessfulPromptRound(options) {
73401
- const { options: runOptions, nextPrompt, runnerMetadata, promptExecutionStartedDate, result, commitMessage, logPath, roundChangedFilesSnapshot, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
74502
+ const { options: runOptions, nextPrompt, runnerMetadata, promptExecutionStartedDate, result, commitMessage, logPath, roundCommitScope, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
73402
74503
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
73403
74504
  await waitForRequestedPause({
73404
74505
  checkpointLabel: 'recording the successful prompt result',
@@ -73409,7 +74510,7 @@ async function finalizeSuccessfulPromptRound(options) {
73409
74510
  // Note: The prompt status is always written into the original project, an isolated round transports
73410
74511
  // its own changes back through the merge instead
73411
74512
  await writePromptFile(nextPrompt.file);
73412
- await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundChangedFilesSnapshot);
74513
+ await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundCommitScope);
73413
74514
  await recordPromptDurationInEstimateCache({
73414
74515
  options: runOptions,
73415
74516
  runnerMetadata,
@@ -73430,6 +74531,9 @@ async function finalizeSuccessfulPromptRound(options) {
73430
74531
  });
73431
74532
  await commitChanges(commitMessage, {
73432
74533
  autoPush: runOptions.autoPush,
74534
+ // Note: Only the prompt file and the files the coding agent has changed belong to this round,
74535
+ // everything which was already changed before the round started stays in the working tree
74536
+ relevantPaths: roundCommitScope && (await resolveCoderCommitScopePaths(roundCommitScope)),
73433
74537
  // Keep the live runtime log out of default commits because it is deleted after a successful round.
73434
74538
  excludePaths: runOptions.preserveLogs ? undefined : [logPath],
73435
74539
  projectPath: roundProjectPath,
@@ -73453,7 +74557,7 @@ async function finalizeSuccessfulPromptRound(options) {
73453
74557
  * Finalizes a failed prompt round, persisting prompt failure metadata before rethrowing.
73454
74558
  */
73455
74559
  async function finalizeFailedPromptRound(options) {
73456
- const { nextPrompt, runnerMetadata, promptExecutionStartedDate, attemptCount, error, options: runOptions, roundChangedFilesSnapshot, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
74560
+ const { nextPrompt, runnerMetadata, promptExecutionStartedDate, attemptCount, error, options: runOptions, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
73457
74561
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
73458
74562
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
73459
74563
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
@@ -73471,7 +74575,7 @@ async function finalizeFailedPromptRound(options) {
73471
74575
  modelName: runnerMetadata.modelName,
73472
74576
  error,
73473
74577
  });
73474
- await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundChangedFilesSnapshot);
74578
+ await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundCommitScope);
73475
74579
  }
73476
74580
  /**
73477
74581
  * Waits for the optional user confirmation immediately before creating the commit.
@@ -73537,17 +74641,29 @@ async function recordPromptDurationInEstimateCache(options) {
73537
74641
  thinkingLevel: runOptions.thinkingLevel,
73538
74642
  }, promptDurationMs);
73539
74643
  }
74644
+ /**
74645
+ * Captures which files are already changed before the round starts, when the round needs to know it later.
74646
+ *
74647
+ * The scope is needed to commit only the files of this round and to normalize the line endings of exactly
74648
+ * those files, so a round which does neither of them does not pay for hashing the working tree.
74649
+ */
74650
+ async function captureRoundCommitScopeIfNeeded(options, roundProjectPath) {
74651
+ if (options.noCommit && !options.normalizeLineEndings) {
74652
+ return undefined;
74653
+ }
74654
+ return captureCoderCommitScope(roundProjectPath);
74655
+ }
73540
74656
  /**
73541
74657
  * Normalizes line endings in files modified during the current coding round.
73542
74658
  */
73543
- async function normalizeLineEndingsForCurrentRound(options, roundProjectPath, roundChangedFilesSnapshot) {
73544
- if (!options.normalizeLineEndings || !roundChangedFilesSnapshot) {
74659
+ async function normalizeLineEndingsForCurrentRound(options, roundProjectPath, roundCommitScope) {
74660
+ if (!options.normalizeLineEndings || !roundCommitScope) {
73545
74661
  return;
73546
74662
  }
73547
74663
  try {
73548
74664
  const result = await normalizeLineEndingsInFilesChangedSinceSnapshot({
73549
74665
  projectPath: roundProjectPath,
73550
- snapshot: roundChangedFilesSnapshot,
74666
+ snapshot: roundCommitScope.snapshotBeforeOperation,
73551
74667
  });
73552
74668
  if (result.normalizedFiles > 0) {
73553
74669
  console.info(colors.gray(`Normalized line endings to LF in ${result.normalizedFiles} changed file(s).`));
@@ -73608,11 +74724,11 @@ function buildCoderIsolationMergeFailureError(worktree, failureDetails) {
73608
74724
  * Builds the markdown list of commands which merge one isolated task manually and clean it up.
73609
74725
  */
73610
74726
  function buildManualRecoveryHints(worktree) {
73611
- return [
73612
- `- \`git merge ${worktree.branchName}\` and resolve the conflicts`,
73613
- `- \`git worktree remove ${worktree.worktreeDisplayPath}\``,
73614
- `- \`git branch -d ${worktree.branchName}\``,
73615
- ].join('\n');
74727
+ return spaceTrim$1(`
74728
+ - \`git merge ${worktree.branchName}\` and resolve the conflicts
74729
+ - \`git worktree remove ${worktree.worktreeDisplayPath}\`
74730
+ - \`git branch -d ${worktree.branchName}\`
74731
+ `);
73616
74732
  }
73617
74733
 
73618
74734
  /**
@@ -74068,6 +75184,9 @@ async function runIsolatedPromptRound(options) {
74068
75184
  var _a;
74069
75185
  const { nextPrompt, promptLabel, isRichUiEnabled, uiHandle, waitForRequestedPause } = options;
74070
75186
  const projectPath = (_a = options.projectPath) !== null && _a !== void 0 ? _a : process.cwd();
75187
+ // Note: The original project is left untouched by the isolated round itself, so its scope covers exactly
75188
+ // the prompt status update and the changes the merge brings back from the worktree
75189
+ const originalProjectCommitScope = await captureCoderCommitScope(projectPath);
74071
75190
  const worktree = await createCoderIsolationWorktree({
74072
75191
  projectPath,
74073
75192
  taskName: buildCoderIsolationTaskName(nextPrompt.file, nextPrompt.section),
@@ -74099,6 +75218,7 @@ async function runIsolatedPromptRound(options) {
74099
75218
  // Note: The merge only stages the isolated changes, so this commit joins them with the prompt status update
74100
75219
  await commitChanges(buildCommitMessage(nextPrompt.file, nextPrompt.section), {
74101
75220
  autoPush: options.options.autoPush,
75221
+ relevantPaths: await resolveCoderCommitScopePaths(originalProjectCommitScope),
74102
75222
  projectPath,
74103
75223
  });
74104
75224
  await removeCoderIsolationWorktree(worktree);
@@ -74122,7 +75242,7 @@ async function recordIsolationMergeFailure(options, worktree, failureDetails) {
74122
75242
  await commitChanges(buildCoderIsolationMergeFailureCommitMessage(worktree), {
74123
75243
  autoPush: options.options.autoPush,
74124
75244
  projectPath: worktree.projectPath,
74125
- includePaths: [nextPrompt.file.path, errorLogPath].map((path) => toProjectRelativeGitPath(worktree.projectPath, path)),
75245
+ relevantPaths: [nextPrompt.file.path, errorLogPath].map((path) => toProjectRelativeGitPath(worktree.projectPath, path)),
74126
75246
  });
74127
75247
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(mergeFailureError.message);
74128
75248
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Merging \`${worktree.taskName}\` failed, worktree kept for a manual merge`);
@@ -74277,26 +75397,22 @@ function $detectTerminalAnsiColorDepth() {
74277
75397
  * Builds the ANSI ASCII-art visual of the `--agent` book shown above the coder-run dashboard.
74278
75398
  *
74279
75399
  * The agent's avatar visual is resolved the same way as on the website - the `META AVATAR`
74280
- * commitment wins, then the `META VISUAL` commitment, then the shared default visual - and is
74281
- * rendered dynamically through the shared canvas avatar pipeline into terminal ASCII art.
74282
- * The terminal variant uses a transparent horizontal canvas instead of the website's framed 1:1 surface.
75400
+ * commitment wins, then the `META VISUAL` commitment, then the shared default visual.
75401
+ * Character-based visuals such as `AsciiOctopus` paint the terminal grid directly, while pixel-based
75402
+ * visuals are rendered through the shared canvas avatar pipeline into terminal ASCII art, using a
75403
+ * transparent horizontal canvas instead of the website's framed 1:1 surface.
74283
75404
  *
74284
- * The visual is decorative, so any failure (for example when the optional `@napi-rs/canvas`
74285
- * module is not installed) returns `null` and the caller keeps the default brand banner.
75405
+ * The visual is decorative, so any failure returns `null` and the caller keeps the default brand banner.
74286
75406
  *
74287
75407
  * @param agentSource Source of the `--agent` book file.
74288
75408
  * @returns ANSI-colored ASCII-art renderer or `null` when the visual cannot be rendered.
74289
75409
  */
74290
75410
  async function buildCoderRunAgentVisual(agentSource) {
74291
75411
  try {
74292
- // Note: `@napi-rs/canvas` is an optional native module, so it is imported dynamically and lazily
74293
- const { createCanvas } = await import('@napi-rs/canvas');
74294
- const colorDepth = $detectTerminalAnsiColorDepth();
74295
- const createCanvasForAsciiArt = (width, height) => createCanvas(width, height);
74296
75412
  const agentVisual = createTerminalAgentAvatarVisual({
74297
75413
  agentSource,
74298
- colorDepth,
74299
- createCanvas: createCanvasForAsciiArt,
75414
+ colorDepth: $detectTerminalAnsiColorDepth(),
75415
+ createCanvas: await createOptionalNodeCanvasFactory(),
74300
75416
  });
74301
75417
  return {
74302
75418
  isAnimated: agentVisual.isAnimated,
@@ -74315,6 +75431,23 @@ async function buildCoderRunAgentVisual(agentSource) {
74315
75431
  return null;
74316
75432
  }
74317
75433
  }
75434
+ /**
75435
+ * Loads the optional Node.js canvas factory which rasterizes pixel-based avatar visuals.
75436
+ *
75437
+ * @returns Canvas factory or `undefined` when the optional native module is not installed.
75438
+ *
75439
+ * @private helper of `buildCoderRunAgentVisual`
75440
+ */
75441
+ async function createOptionalNodeCanvasFactory() {
75442
+ try {
75443
+ // Note: `@napi-rs/canvas` is an optional native module, so it is imported dynamically and lazily
75444
+ const { createCanvas } = await import('@napi-rs/canvas');
75445
+ return (width, height) => createCanvas(width, height);
75446
+ }
75447
+ catch (error) {
75448
+ return undefined;
75449
+ }
75450
+ }
74318
75451
 
74319
75452
  /**
74320
75453
  * Creates the one queue prompt used to repair a pre-existing test failure.
@@ -75293,7 +76426,7 @@ async function updatePromptSection(filePath, sectionIndex, newContent) {
75293
76426
  *
75294
76427
  * @private internal constant of `ptbk coder server`
75295
76428
  */
75296
- const CODER_SERVER_HTML = `<!DOCTYPE html>
76429
+ const CODER_SERVER_HTML = spaceTrim$1(`<!DOCTYPE html>
75297
76430
  <html lang="en">
75298
76431
  <head>
75299
76432
  <meta charset="UTF-8">
@@ -75979,7 +77112,7 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
75979
77112
  </script>
75980
77113
  </body>
75981
77114
  </html>
75982
- `;
77115
+ `);
75983
77116
  // Note: [🟡] Code for CLI command [coder server](scripts/run-codex-prompts/server/coderServerHtml.ts) should never be published outside of `@promptbook/cli`
75984
77117
  // Note: Keep in sync with apps/coder-server/index.html
75985
77118
 
@@ -76171,8 +77304,7 @@ function resolveEditablePromptFilePath(filePath, promptsDir) {
76171
77304
  async function commitPromptEdit(promptFilePath, sectionIndex) {
76172
77305
  const relativePromptFilePath = relative(process.cwd(), promptFilePath).replace(/\\/gu, '/');
76173
77306
  await commitChanges(`Edit coder prompt ${relativePromptFilePath}#${sectionIndex + 1}`, {
76174
- includePaths: [relativePromptFilePath],
76175
- onlyPaths: [relativePromptFilePath],
77307
+ relevantPaths: [relativePromptFilePath],
76176
77308
  });
76177
77309
  }
76178
77310
  /**
@@ -76317,8 +77449,9 @@ async function verifyPrompts(options = DEFAULT_VERIFY_PROMPTS_OPTIONS) {
76317
77449
  let promptFiles = initialFiles;
76318
77450
  const skippedFiles = new Set();
76319
77451
  while (true) {
76320
- // Note: The git synchronization is applied around each single verification, not once per whole run
76321
- await $pullCoderChanges({ gitSync: normalizedOptions.gitSync });
77452
+ // Note: The git synchronization is applied around each single verification, not once per whole run,
77453
+ // so each verification commits only the prompt file it has archived or repaired
77454
+ const commitScope = await $startCoderGitSync({ gitSync: normalizedOptions.gitSync });
76322
77455
  if (normalizedOptions.gitSync.isAutoPullEnabled) {
76323
77456
  // Note: The pull can bring in prompt file changes, so the queue is reloaded before it is used
76324
77457
  promptFiles = (await loadPromptFilesForVerification(normalizedOptions)).promptFiles;
@@ -76331,7 +77464,7 @@ async function verifyPrompts(options = DEFAULT_VERIFY_PROMPTS_OPTIONS) {
76331
77464
  if (outcome.wasSkipped) {
76332
77465
  skippedFiles.add(fileWithAllDone.path);
76333
77466
  }
76334
- await $commitVerificationOutcome(normalizedOptions.gitSync, outcome);
77467
+ await $commitVerificationOutcome(normalizedOptions.gitSync, commitScope, outcome);
76335
77468
  promptFiles = (await loadPromptFilesForVerification(normalizedOptions)).promptFiles;
76336
77469
  continue;
76337
77470
  }
@@ -76342,18 +77475,18 @@ async function verifyPrompts(options = DEFAULT_VERIFY_PROMPTS_OPTIONS) {
76342
77475
  break;
76343
77476
  }
76344
77477
  const outcome = await resolvePrompt(nextPrompt);
76345
- await $commitVerificationOutcome(normalizedOptions.gitSync, outcome);
77478
+ await $commitVerificationOutcome(normalizedOptions.gitSync, commitScope, outcome);
76346
77479
  promptFiles = (await loadPromptFilesForVerification(normalizedOptions)).promptFiles;
76347
77480
  }
76348
77481
  }
76349
77482
  /**
76350
77483
  * Commits and pushes one applied verification when the git synchronization is enabled.
76351
77484
  */
76352
- async function $commitVerificationOutcome(gitSync, outcome) {
77485
+ async function $commitVerificationOutcome(gitSync, commitScope, outcome) {
76353
77486
  if (outcome.commitMessage === null) {
76354
77487
  return;
76355
77488
  }
76356
- await $commitCoderChanges({ gitSync, commitMessage: outcome.commitMessage });
77489
+ await $commitCoderChanges({ gitSync, commitScope, commitMessage: outcome.commitMessage });
76357
77490
  }
76358
77491
  /**
76359
77492
  * Parses supported command-line arguments for the standalone verification script.