@promptbook/cli 0.112.0-125 → 0.112.0-127

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 (190) hide show
  1. package/README.md +5 -5
  2. package/apps/agents-server/README.md +3 -0
  3. package/apps/agents-server/playwright.config.ts +1 -0
  4. package/apps/agents-server/public/promptbook-logo-blue.png +0 -0
  5. package/apps/agents-server/public/promptbook-logo-white.png +0 -0
  6. package/apps/agents-server/src/app/[agentName]/layout.tsx +1 -0
  7. package/apps/agents-server/src/app/actions.ts +2 -2
  8. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +68 -36
  9. package/apps/agents-server/src/app/agents/[agentName]/ActiveAgentBreadcrumbBinder.tsx +30 -0
  10. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +2 -0
  11. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatHistoryPayloadState.ts +38 -4
  12. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatHistorySyncOperations.ts +4 -1
  13. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +19 -15
  14. package/apps/agents-server/src/app/agents/[agentName]/chat/useCanonicalAgentChatPanelState.ts +58 -7
  15. package/apps/agents-server/src/app/agents/[agentName]/layout.tsx +47 -1
  16. package/apps/agents-server/src/app/api/chat/citation-label/route.ts +133 -0
  17. package/apps/agents-server/src/app/globals.css +78 -0
  18. package/apps/agents-server/src/components/Header/ActiveAgentBreadcrumbContext.tsx +82 -0
  19. package/apps/agents-server/src/components/Header/useHeaderActiveAgent.ts +26 -7
  20. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +35 -32
  21. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/ManGoNewAgentWizard.tsx +151 -0
  22. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/ManGoOnboardingNavigation.tsx +50 -0
  23. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/BookLanguagePanel.tsx +125 -0
  24. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/DropZone.tsx +83 -0
  25. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/EmailTestRun.tsx +216 -0
  26. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/KnowledgeList.tsx +79 -0
  27. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/Logo.tsx +20 -0
  28. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/MarkdownBookEditor.tsx +154 -0
  29. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/MarkdownPreview.tsx +188 -0
  30. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/RailStepper.tsx +115 -0
  31. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/StepFrame.tsx +47 -0
  32. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/TestChat.tsx +159 -0
  33. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/WizardShell.tsx +114 -0
  34. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/brand/RailArtwork.tsx +60 -0
  35. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/BookStep.tsx +135 -0
  36. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/DoneStep.tsx +215 -0
  37. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/KnowledgeStep.tsx +152 -0
  38. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/TestStep.tsx +262 -0
  39. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/ZadaniStep.tsx +64 -0
  40. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Badge.tsx +41 -0
  41. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Banner.tsx +69 -0
  42. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Button.tsx +77 -0
  43. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Card.tsx +33 -0
  44. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Field.tsx +103 -0
  45. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/IconButton.tsx +40 -0
  46. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Spinner.tsx +14 -0
  47. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/tokens.ts +37 -0
  48. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/config/bookSections.ts +62 -0
  49. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/config/emailScenarios.ts +22 -0
  50. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/config/steps.ts +46 -0
  51. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/index.ts +11 -0
  52. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/lib/cn.ts +4 -0
  53. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/lib/format.ts +9 -0
  54. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/lib/id.ts +11 -0
  55. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/agentEvalService.ts +30 -0
  56. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/agentTestService.ts +69 -0
  57. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/bookService.ts +21 -0
  58. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/createManGoAgentSource.ts +107 -0
  59. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/draftService.ts +45 -0
  60. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/uploadService.ts +27 -0
  61. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/state/OnboardingProvider.tsx +121 -0
  62. package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/types.ts +52 -0
  63. package/apps/agents-server/src/components/NewAgentDialog/createNewAgentWizardSource.ts +1 -1
  64. package/apps/agents-server/src/components/NewAgentDialog/trackNewAgentCreationEvent.ts +1 -1
  65. package/apps/agents-server/src/components/NewAgentDialog/useNewAgentDialog.tsx +76 -0
  66. package/apps/agents-server/src/constants/newAgentWizard.ts +17 -8
  67. package/apps/agents-server/src/database/getMetadata.ts +37 -1
  68. package/apps/agents-server/src/database/metadataDefaults.ts +18 -7
  69. package/apps/agents-server/src/database/migrations/2026-06-2200-new-agent-wizard-metadata-key.sql +27 -0
  70. package/apps/agents-server/src/database/migrations/2026-06-2201-new-agent-wizard-mango-default.sql +11 -0
  71. package/apps/agents-server/src/generated/reservedPaths.ts +2 -0
  72. package/apps/agents-server/src/languages/translations/english.yaml +1 -1
  73. package/apps/agents-server/src/tools/createChatAttachmentToolFunctions.ts +8 -4
  74. package/apps/agents-server/src/utils/chat/resolveAgentsServerCitationLabel.ts +130 -0
  75. package/apps/agents-server/src/utils/chat/resolveCitationSourceLabel.ts +436 -0
  76. package/apps/agents-server/src/utils/knowledge/resolveWebsiteKnowledgeSourcesForServer.ts +10 -1
  77. package/apps/agents-server/src/utils/session.ts +62 -5
  78. package/apps/agents-server/src/utils/transpilers/resolveTranspiledTeamExport.ts +10 -10
  79. package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +198 -63
  80. package/apps/agents-server/src/utils/vpsConfiguration.ts +1 -0
  81. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +81 -8
  82. package/esm/index.es.js +1052 -455
  83. package/esm/index.es.js.map +1 -1
  84. package/esm/scripts/run-codex-prompts/git/commitChanges.d.ts +3 -1
  85. package/esm/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -2
  86. package/esm/scripts/run-codex-prompts/server/buildCoderServerPromptResponse.d.ts +50 -0
  87. package/esm/scripts/run-codex-prompts/server/buildCoderServerRunState.d.ts +23 -0
  88. package/esm/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
  89. package/esm/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +13 -1
  90. package/esm/scripts/run-codex-prompts/server/updatePromptSection.d.ts +1 -1
  91. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +1 -0
  92. package/esm/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +4 -0
  93. package/esm/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +1 -0
  94. package/esm/src/_packages/components.index.d.ts +2 -0
  95. package/esm/src/_packages/types.index.d.ts +2 -0
  96. package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
  97. package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  98. package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  99. package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  100. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  101. package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  102. package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  103. package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  104. package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  105. package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  106. package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  107. package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  108. package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  109. package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  110. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  111. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  112. package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  113. package/esm/src/version.d.ts +1 -1
  114. package/package.json +1 -1
  115. package/src/_packages/components.index.ts +2 -0
  116. package/src/_packages/types.index.ts +2 -0
  117. package/src/avatars/avatarAnimationScheduler.ts +61 -20
  118. package/src/avatars/visuals/octopus3d2AvatarVisual.ts +106 -21
  119. package/src/avatars/visuals/octopus3d3AvatarVisual.ts +131 -28
  120. package/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments/augmentAgentModelRequirementsFromSource.ts +18 -2
  121. package/src/book-2.0/book-language-documentation/renderGroupedCommitmentDocumentationMarkdown.ts +7 -1
  122. package/src/book-components/Chat/Chat/Chat.tsx +4 -0
  123. package/src/book-components/Chat/Chat/ChatCitationModal.tsx +17 -3
  124. package/src/book-components/Chat/Chat/ChatMessageItem.tsx +22 -2
  125. package/src/book-components/Chat/Chat/ChatMessageList.tsx +6 -0
  126. package/src/book-components/Chat/Chat/ChatProps.tsx +6 -0
  127. package/src/book-components/Chat/Chat/ChatToolCallModal.tsx +4 -0
  128. package/src/book-components/Chat/Chat/ChatToolCallModalContent.tsx +4 -0
  129. package/src/book-components/Chat/Chat/TeamToolCallModalContent.tsx +16 -3
  130. package/src/book-components/Chat/Chat/useChatPostprocessedMessages.ts +13 -2
  131. package/src/book-components/Chat/SourceChip/SourceChip.module.css +8 -0
  132. package/src/book-components/Chat/SourceChip/SourceChip.tsx +19 -5
  133. package/src/book-components/Chat/hooks/useResolvedCitationLabel.ts +65 -0
  134. package/src/book-components/Chat/save/html/htmlSaveFormatDefinition.ts +315 -88
  135. package/src/book-components/Chat/save/react/reactSaveFormatDefinition.ts +7 -1
  136. package/src/book-components/Chat/types/ChatMessage.ts +5 -0
  137. package/src/book-components/Chat/types/ChatParticipant.ts +1 -1
  138. package/src/book-components/Chat/types/CitationLabelResolver.ts +9 -0
  139. package/src/book-components/Chat/utils/citationHelpers.ts +90 -2
  140. package/src/book-components/Chat/utils/createCitationFootnoteRenderModel.ts +3 -0
  141. package/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.ts +178 -0
  142. package/src/book-components/Chat/utils/parseCitationsFromContent.ts +6 -0
  143. package/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts +13 -2
  144. package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +1 -1
  145. package/src/cli/cli-commands/coder/run.ts +17 -20
  146. package/src/cli/cli-commands/coder/server.ts +13 -16
  147. package/src/cli/common/$deprecateCliCommand.ts +10 -1
  148. package/src/commitments/GOAL/GOAL.ts +7 -2
  149. package/src/commitments/LANGUAGE/LANGUAGE.ts +7 -2
  150. package/src/commitments/TEAM/TEAM.ts +12 -1
  151. package/src/commitments/USE_PROJECT/createUseProjectToolFunctions.ts +8 -1
  152. package/src/import-plugins/JsonFileImportPlugin.ts +15 -2
  153. package/src/import-plugins/TextFileImportPlugin.ts +8 -1
  154. package/src/llm-providers/_common/register/$registeredLlmToolsMessage.ts +6 -1
  155. package/src/other/templates/getTemplatesPipelineCollection.ts +696 -941
  156. package/src/utils/chat/chatAttachments/resolveChatAttachmentContent.ts +8 -1
  157. package/src/version.ts +2 -2
  158. package/src/versions.txt +2 -1
  159. package/umd/index.umd.js +1055 -458
  160. package/umd/index.umd.js.map +1 -1
  161. package/umd/scripts/run-codex-prompts/git/commitChanges.d.ts +3 -1
  162. package/umd/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -2
  163. package/umd/scripts/run-codex-prompts/server/buildCoderServerPromptResponse.d.ts +50 -0
  164. package/umd/scripts/run-codex-prompts/server/buildCoderServerRunState.d.ts +23 -0
  165. package/umd/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
  166. package/umd/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +13 -1
  167. package/umd/scripts/run-codex-prompts/server/updatePromptSection.d.ts +1 -1
  168. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +1 -0
  169. package/umd/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +4 -0
  170. package/umd/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +1 -0
  171. package/umd/src/_packages/components.index.d.ts +2 -0
  172. package/umd/src/_packages/types.index.d.ts +2 -0
  173. package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
  174. package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  175. package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  176. package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  177. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  178. package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  179. package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  180. package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  181. package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  182. package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  183. package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  184. package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  185. package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  186. package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  187. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  188. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  189. package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  190. package/umd/src/version.d.ts +1 -1
@@ -19,12 +19,20 @@ const FILE_EXTENSION_REGEX = /\.([a-z0-9]{1,10})$/i;
19
19
  * Pattern matching punctuation that should be trimmed from citation tails.
20
20
  */
21
21
  const TRAILING_PUNCTUATION_REGEX = /[.,;:!?)+\]]+$/;
22
+ /**
23
+ * Pattern matching filename separators that should become spaces in source labels.
24
+ */
25
+ const FILENAME_SEPARATOR_REGEX = /[-_]+/g;
26
+ /**
27
+ * Pattern matching consecutive whitespace in display labels.
28
+ */
29
+ const WHITESPACE_REGEX = /\s+/g;
22
30
 
23
31
  /**
24
32
  * Collapses consecutive whitespace into single spaces.
25
33
  */
26
34
  function collapseWhitespace(value: string): string {
27
- return value.replace(/\s+/g, ' ');
35
+ return value.replace(WHITESPACE_REGEX, ' ');
28
36
  }
29
37
 
30
38
  /**
@@ -105,6 +113,11 @@ export function isPlainTextCitation(citation: ParsedCitation): boolean {
105
113
  * @private utility of `<Chat/>` citation rendering
106
114
  */
107
115
  export function getCitationLabel(citation: ParsedCitation): string {
116
+ const title = normalizeCitationDisplayLabel(citation.title);
117
+ if (title) {
118
+ return title;
119
+ }
120
+
108
121
  const trimmed = trimToNormalized(citation.source);
109
122
  if (!trimmed) {
110
123
  return citation.source;
@@ -119,7 +132,7 @@ export function getCitationLabel(citation: ParsedCitation): string {
119
132
  return collapsed.slice(0, TEXT_LABEL_LENGTH) + LABEL_ELLIPSIS;
120
133
  }
121
134
 
122
- return simplifyKnowledgeLabel(trimmed);
135
+ return createReadableCitationSourceLabel(trimmed);
123
136
  }
124
137
 
125
138
  /**
@@ -143,4 +156,79 @@ export function resolveCitationPreviewUrl(
143
156
  return explicitUrl || literalUrl || knowledgeUrl || null;
144
157
  }
145
158
 
159
+ /**
160
+ * Creates a readable fallback label from a citation source when no title metadata is available.
161
+ *
162
+ * @param source - Raw citation source value.
163
+ * @returns Human-friendly source label.
164
+ *
165
+ * @private utility of `<Chat/>` citation rendering
166
+ */
167
+ export function createReadableCitationSourceLabel(source: string): string {
168
+ const trimmed = trimToNormalized(source);
169
+ if (!trimmed) {
170
+ return source;
171
+ }
172
+
173
+ const parsedUrl = parseCitationUrl(trimmed);
174
+ const filenameCandidate = parsedUrl
175
+ ? getUrlLabelCandidate(parsedUrl) || parsedUrl.hostname.replace(/^www\./i, '')
176
+ : simplifyKnowledgeLabel(trimmed);
177
+
178
+ return normalizeCitationDisplayLabel(filenameCandidate) || simplifyKnowledgeLabel(trimmed);
179
+ }
180
+
181
+ /**
182
+ * Normalizes a citation label candidate for display.
183
+ *
184
+ * @param label - Raw candidate label.
185
+ * @returns Cleaned label or `null` when empty.
186
+ *
187
+ * @private utility of `<Chat/>` citation rendering
188
+ */
189
+ function normalizeCitationDisplayLabel(label: string | undefined): string | null {
190
+ const normalized = collapseWhitespace((label || '').replace(FILENAME_SEPARATOR_REGEX, ' ')).trim();
191
+
192
+ return normalized || null;
193
+ }
194
+
195
+ /**
196
+ * Parses one HTTP(S) citation URL, returning null for non-URL values.
197
+ *
198
+ * @param value - Candidate URL value.
199
+ * @returns Parsed URL or null.
200
+ *
201
+ * @private utility of `<Chat/>` citation rendering
202
+ */
203
+ function parseCitationUrl(value: string): URL | null {
204
+ try {
205
+ const url = new URL(value);
206
+ return url.protocol === 'http:' || url.protocol === 'https:' ? url : null;
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Extracts a readable candidate from a URL path.
214
+ *
215
+ * @param url - Parsed citation URL.
216
+ * @returns URL path label candidate or null.
217
+ *
218
+ * @private utility of `<Chat/>` citation rendering
219
+ */
220
+ function getUrlLabelCandidate(url: URL): string | null {
221
+ const pathSegments = url.pathname.split('/').filter(Boolean);
222
+ const lastPathSegment = pathSegments[pathSegments.length - 1];
223
+ if (!lastPathSegment) {
224
+ return null;
225
+ }
226
+
227
+ try {
228
+ return simplifyKnowledgeLabel(decodeURIComponent(lastPathSegment));
229
+ } catch {
230
+ return simplifyKnowledgeLabel(lastPathSegment);
231
+ }
232
+ }
233
+
146
234
  // TODO: [💞] Spread into multiple files
@@ -191,6 +191,7 @@ function normalizeParsedCitation(citation: ParsedCitation): ParsedCitation {
191
191
  id: citation.id.trim(),
192
192
  source: citation.source.trim(),
193
193
  url: citation.url?.trim() || undefined,
194
+ title: citation.title?.trim() || undefined,
194
195
  excerpt: citation.excerpt?.trim() || undefined,
195
196
  };
196
197
  }
@@ -209,6 +210,7 @@ function mergeParsedCitations(currentCitation: ParsedCitation, incomingCitation:
209
210
  id: currentCitation.id || incomingCitation.id,
210
211
  source: resolvePreferredCitationSource(currentCitation, incomingCitation),
211
212
  url: currentCitation.url || incomingCitation.url,
213
+ title: currentCitation.title || incomingCitation.title,
212
214
  excerpt: currentCitation.excerpt || incomingCitation.excerpt,
213
215
  } satisfies ParsedCitation;
214
216
 
@@ -216,6 +218,7 @@ function mergeParsedCitations(currentCitation: ParsedCitation, incomingCitation:
216
218
  mergedCitation.id === currentCitation.id &&
217
219
  mergedCitation.source === currentCitation.source &&
218
220
  mergedCitation.url === currentCitation.url &&
221
+ mergedCitation.title === currentCitation.title &&
219
222
  mergedCitation.excerpt === currentCitation.excerpt
220
223
  ) {
221
224
  return currentCitation;
@@ -0,0 +1,178 @@
1
+ import type { string_markdown } from '../../../types/string_markdown';
2
+
3
+ /**
4
+ * Pattern for detecting JSON-style Unicode escape sequences in model text.
5
+ *
6
+ * @private utility of chat message postprocessing
7
+ */
8
+ const JSON_UNICODE_ESCAPE_DETECTION_PATTERN = /\\u[0-9a-fA-F]{4}/;
9
+
10
+ /**
11
+ * Pattern for decoding JSON-style Unicode escape sequences in model text.
12
+ *
13
+ * @private utility of chat message postprocessing
14
+ */
15
+ const JSON_UNICODE_ESCAPE_PATTERN = /\\u([0-9a-fA-F]{4})/g;
16
+
17
+ /**
18
+ * Pattern for detecting fenced markdown code-block boundaries.
19
+ *
20
+ * @private utility of chat message postprocessing
21
+ */
22
+ const MARKDOWN_CODE_FENCE_PATTERN = /^[ \t]{0,3}(`{3,}|~{3,})/;
23
+
24
+ /**
25
+ * Minimum code point decoded by the chat Unicode escape normalizer.
26
+ *
27
+ * ASCII escapes can be intentional in JSON and security-sensitive examples, so this
28
+ * helper only decodes non-ASCII characters that were visibly broken in chat text.
29
+ *
30
+ * @private utility of chat message postprocessing
31
+ */
32
+ const FIRST_NON_ASCII_CODE_POINT = 0x80;
33
+
34
+ /**
35
+ * Markdown fence state while scanning chat text line-by-line.
36
+ *
37
+ * @private utility of chat message postprocessing
38
+ */
39
+ type MarkdownFenceState = {
40
+ readonly marker: '`' | '~';
41
+ readonly length: number;
42
+ };
43
+
44
+ /**
45
+ * Decodes JSON-style Unicode escape sequences in markdown prose while preserving code.
46
+ *
47
+ * Model/tool output can occasionally arrive as already JSON-escaped text, which makes
48
+ * natural-language replies render `\u00fd` instead of the decoded character. This
49
+ * keeps code examples intact by skipping fenced code blocks and inline code spans.
50
+ *
51
+ * @param markdown - Markdown chat content to normalize for display.
52
+ * @returns Markdown chat content with non-ASCII JSON Unicode escapes decoded.
53
+ *
54
+ * @private internal utility of `<Chat/>`
55
+ */
56
+ export function decodeJsonUnicodeEscapesInMarkdownText(markdown: string_markdown): string_markdown {
57
+ if (!JSON_UNICODE_ESCAPE_DETECTION_PATTERN.test(markdown)) {
58
+ return markdown;
59
+ }
60
+
61
+ const parts = markdown.split(/(\r\n|\r|\n)/);
62
+ let fenceState: MarkdownFenceState | null = null;
63
+ let normalizedMarkdown = '';
64
+
65
+ for (let index = 0; index < parts.length; index += 2) {
66
+ const line = parts[index] ?? '';
67
+ const newline = parts[index + 1] ?? '';
68
+ const fenceBoundary = resolveMarkdownFenceBoundary(line);
69
+
70
+ if (fenceState) {
71
+ normalizedMarkdown += line + newline;
72
+ if (
73
+ fenceBoundary &&
74
+ fenceBoundary.marker === fenceState.marker &&
75
+ fenceBoundary.length >= fenceState.length
76
+ ) {
77
+ fenceState = null;
78
+ }
79
+ continue;
80
+ }
81
+
82
+ if (fenceBoundary) {
83
+ fenceState = fenceBoundary;
84
+ normalizedMarkdown += line + newline;
85
+ continue;
86
+ }
87
+
88
+ normalizedMarkdown += decodeJsonUnicodeEscapesOutsideInlineCode(line) + newline;
89
+ }
90
+
91
+ return normalizedMarkdown as string_markdown;
92
+ }
93
+
94
+ /**
95
+ * Resolves one markdown code-fence boundary from a line.
96
+ *
97
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
98
+ */
99
+ function resolveMarkdownFenceBoundary(line: string): MarkdownFenceState | null {
100
+ const match = line.match(MARKDOWN_CODE_FENCE_PATTERN);
101
+ const fenceMarker = match?.[1];
102
+
103
+ if (!fenceMarker) {
104
+ return null;
105
+ }
106
+
107
+ return {
108
+ marker: fenceMarker[0] as MarkdownFenceState['marker'],
109
+ length: fenceMarker.length,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Decodes Unicode escapes in one markdown line while skipping inline code spans.
115
+ *
116
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
117
+ */
118
+ function decodeJsonUnicodeEscapesOutsideInlineCode(line: string): string {
119
+ let normalizedLine = '';
120
+ let cursor = 0;
121
+
122
+ while (cursor < line.length) {
123
+ const openingDelimiterStart = line.indexOf('`', cursor);
124
+ if (openingDelimiterStart === -1) {
125
+ normalizedLine += decodeJsonUnicodeEscapesInText(line.slice(cursor));
126
+ break;
127
+ }
128
+
129
+ const openingDelimiterEnd = findBacktickRunEnd(line, openingDelimiterStart);
130
+ const openingDelimiter = line.slice(openingDelimiterStart, openingDelimiterEnd);
131
+ const closingDelimiterStart = line.indexOf(openingDelimiter, openingDelimiterEnd);
132
+
133
+ normalizedLine += decodeJsonUnicodeEscapesInText(line.slice(cursor, openingDelimiterStart));
134
+
135
+ if (closingDelimiterStart === -1) {
136
+ normalizedLine += line.slice(openingDelimiterStart);
137
+ break;
138
+ }
139
+
140
+ const closingDelimiterEnd = closingDelimiterStart + openingDelimiter.length;
141
+ normalizedLine += line.slice(openingDelimiterStart, closingDelimiterEnd);
142
+ cursor = closingDelimiterEnd;
143
+ }
144
+
145
+ return normalizedLine;
146
+ }
147
+
148
+ /**
149
+ * Finds the first character after one contiguous run of backticks.
150
+ *
151
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
152
+ */
153
+ function findBacktickRunEnd(line: string, startIndex: number): number {
154
+ let cursor = startIndex;
155
+
156
+ while (cursor < line.length && line[cursor] === '`') {
157
+ cursor++;
158
+ }
159
+
160
+ return cursor;
161
+ }
162
+
163
+ /**
164
+ * Decodes non-ASCII JSON Unicode escapes in plain text.
165
+ *
166
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
167
+ */
168
+ function decodeJsonUnicodeEscapesInText(text: string): string {
169
+ return text.replace(JSON_UNICODE_ESCAPE_PATTERN, (match, hexadecimalCodePoint: string) => {
170
+ const codePoint = Number.parseInt(hexadecimalCodePoint, 16);
171
+
172
+ if (codePoint < FIRST_NON_ASCII_CODE_POINT) {
173
+ return match;
174
+ }
175
+
176
+ return String.fromCharCode(codePoint);
177
+ });
178
+ }
@@ -20,6 +20,11 @@ export type ParsedCitation = {
20
20
  */
21
21
  url?: string;
22
22
 
23
+ /**
24
+ * Optional human-readable source title.
25
+ */
26
+ title?: string;
27
+
23
28
  /**
24
29
  * Optional preview/excerpt from the source
25
30
  */
@@ -100,6 +105,7 @@ export function dedupeCitationsBySource(citations: ReadonlyArray<ParsedCitation>
100
105
  id: existing.id,
101
106
  source: existing.source,
102
107
  url: existing.url || citation.url,
108
+ title: existing.title || citation.title,
103
109
  excerpt: existing.excerpt || citation.excerpt,
104
110
  };
105
111
  }
@@ -1,3 +1,4 @@
1
+ import { spaceTrim } from 'spacetrim';
1
2
  import { ensureProjectEnvFile, type EnsureProjectEnvFileResult } from '../common/projectInitialization';
2
3
 
3
4
  /**
@@ -70,10 +71,20 @@ function createAgentsServerEnvVariable(name: string, value: string): RequiredAge
70
71
  */
71
72
  function buildMissingEnvVariablesBlock(variables: ReadonlyArray<RequiredAgentsServerEnvVariable>): string {
72
73
  const variableBlocks = variables.map(({ documentationUrl, name, value }) =>
73
- [AGENTS_SERVER_ENV_CREATED_COMMENT, `# Documentation: ${documentationUrl}`, `${name}=${value}`].join('\n'),
74
+ spaceTrim(`
75
+ ${AGENTS_SERVER_ENV_CREATED_COMMENT}
76
+ # Documentation: ${documentationUrl}
77
+ ${name}=${value}
78
+ `),
74
79
  );
75
80
 
76
- return ['# Promptbook Agents Server', ...variableBlocks].join('\n\n');
81
+ return spaceTrim(
82
+ (block) => `
83
+ # Promptbook Agents Server
84
+
85
+ ${block(variableBlocks.join('\n\n'))}
86
+ `,
87
+ );
77
88
  }
78
89
 
79
90
  // Note: [🟡] Code for Agents Server environment bootstrapping [ensureAgentsServerEnvFile](src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts) should never be published outside of `@promptbook/cli`
@@ -4,7 +4,7 @@
4
4
  const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
5
5
  'coder:generate-boilerplates': 'ptbk coder generate-boilerplates --template ./prompts/templates/common.md',
6
6
  'coder:run':
7
- 'ptbk coder run --harness openai-codex --model gpt-5.4 --thinking-level xhigh --agent agents/developer.book --context AGENTS.md --no-wait',
7
+ 'ptbk coder run --harness openai-codex --model gpt-5.4 --thinking-level xhigh --agent agents/developer.book --context AGENTS.md',
8
8
  // 'coder:find-refactor-candidates': 'ptbk coder find-refactor-candidates',
9
9
  'coder:verify': 'ptbk coder verify',
10
10
  } as const satisfies Readonly<Record<string, string>>;
@@ -63,16 +63,17 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
63
63
  addPromptRunnerExecutionOptions(command);
64
64
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
65
65
  command.option(
66
- '--wait [duration]',
66
+ '--wait <duration>',
67
67
  spaceTrim(`
68
- Wait between prompt rounds.
69
- Without a value (default): waits for user confirmation before each prompt (interactive mode).
70
- With a duration like 1h, 30m, 5s: waits that long between prompts to avoid hitting rate limits of the harness.
68
+ Wait this long between prompt rounds to avoid hitting rate limits of the harness.
69
+ Accepts durations like 1h, 30m, 5s.
71
70
  `),
72
- true,
73
71
  );
74
- // Note: --no-wait disables the default interactive wait-for-user behaviour
75
- command.option('--no-wait', 'Skip all waiting between prompts and run non-interactively');
72
+ // Note: --no-auto disables the default auto behaviour and waits for user confirmation before each prompt
73
+ command.option(
74
+ '--no-auto',
75
+ 'Wait for user confirmation before each prompt instead of running automatically through the queue',
76
+ );
76
77
  command.option(
77
78
  '--auto-migrate',
78
79
  'Run testing-server database migrations automatically after each successfully processed prompt',
@@ -92,6 +93,7 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
92
93
  preserveLogs,
93
94
  priority,
94
95
  wait,
96
+ auto,
95
97
  autoMigrate,
96
98
  allowDestructiveAutoMigrate,
97
99
  } = cliOptions as {
@@ -101,7 +103,8 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
101
103
  readonly test?: string | string[];
102
104
  readonly preserveLogs: boolean;
103
105
  readonly priority: number;
104
- readonly wait: boolean | string;
106
+ readonly wait?: string;
107
+ readonly auto: boolean;
105
108
  readonly autoMigrate: boolean;
106
109
  readonly allowDestructiveAutoMigrate: boolean;
107
110
  } & PromptRunnerCliOptions;
@@ -111,18 +114,12 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
111
114
  isAgentRequired: !dryRun,
112
115
  });
113
116
 
114
- // [1] Parse the --wait option:
115
- // true (default or --wait without value): wait for user confirmation
116
- // false (--no-wait): no waiting at all
117
- // string (--wait 1h): wait that long between prompt rounds
118
- let waitForUser = false;
119
- let waitBetweenPrompts = 0;
120
-
121
- if (wait === true) {
122
- waitForUser = true;
123
- } else if (typeof wait === 'string' && wait !== '') {
124
- waitBetweenPrompts = parseDuration(wait);
125
- }
117
+ // [1] Parse the --wait and --no-auto options:
118
+ // default: run automatically through the queue (no waiting)
119
+ // --no-auto: wait for user confirmation before each prompt (interactive mode)
120
+ // --wait <duration>: wait that long between prompt rounds to avoid rate limits
121
+ const waitForUser = !auto;
122
+ const waitBetweenPrompts = typeof wait === 'string' && wait !== '' ? parseDuration(wait) : 0;
126
123
 
127
124
  // Convert commander options to RunOptions format
128
125
  const runOptions = {
@@ -79,15 +79,16 @@ export function $initializeCoderServerCommand(program: Program): $side_effect {
79
79
  addPromptRunnerExecutionOptions(command);
80
80
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
81
81
  command.option(
82
- '--wait [duration]',
82
+ '--wait <duration>',
83
83
  spaceTrim(`
84
- Wait between prompt rounds.
85
- Without a value (default): waits for user confirmation before each prompt (interactive mode).
86
- With a duration like 1h, 30m, 5s: waits that long between prompts to avoid hitting rate limits of the harness.
84
+ Wait this long between prompt rounds to avoid hitting rate limits of the harness.
85
+ Accepts durations like 1h, 30m, 5s.
87
86
  `),
88
- true,
89
87
  );
90
- command.option('--no-wait', 'Skip all waiting between prompts and run non-interactively');
88
+ command.option(
89
+ '--no-auto',
90
+ 'Wait for user confirmation before each prompt instead of running automatically through the queue',
91
+ );
91
92
  command.option(
92
93
  '--auto-migrate',
93
94
  'Run testing-server database migrations automatically after each successfully processed prompt',
@@ -108,6 +109,7 @@ export function $initializeCoderServerCommand(program: Program): $side_effect {
108
109
  preserveLogs,
109
110
  priority,
110
111
  wait,
112
+ auto,
111
113
  autoMigrate,
112
114
  allowDestructiveAutoMigrate,
113
115
  } = cliOptions as {
@@ -118,7 +120,8 @@ export function $initializeCoderServerCommand(program: Program): $side_effect {
118
120
  readonly test?: string | string[];
119
121
  readonly preserveLogs: boolean;
120
122
  readonly priority: number;
121
- readonly wait: boolean | string;
123
+ readonly wait?: string;
124
+ readonly auto: boolean;
122
125
  readonly autoMigrate: boolean;
123
126
  readonly allowDestructiveAutoMigrate: boolean;
124
127
  } & PromptRunnerCliOptions;
@@ -129,15 +132,9 @@ export function $initializeCoderServerCommand(program: Program): $side_effect {
129
132
  isAgentRequired: !dryRun,
130
133
  });
131
134
 
132
- // [1] Parse the --wait option (same logic as `coder run`)
133
- let waitForUser = false;
134
- let waitBetweenPrompts = 0;
135
-
136
- if (wait === true) {
137
- waitForUser = true;
138
- } else if (typeof wait === 'string' && wait !== '') {
139
- waitBetweenPrompts = parseDuration(wait);
140
- }
135
+ // [1] Parse the --wait and --no-auto options (same logic as `coder run`)
136
+ const waitForUser = !auto;
137
+ const waitBetweenPrompts = typeof wait === 'string' && wait !== '' ? parseDuration(wait) : 0;
141
138
 
142
139
  const runOptions = {
143
140
  port,
@@ -1,5 +1,6 @@
1
1
  import colors from 'colors';
2
2
  import { Command } from 'commander';
3
+ import { spaceTrim } from 'spacetrim';
3
4
  import type { $side_effect } from '../../utils/organization/$side_effect';
4
5
 
5
6
  /**
@@ -8,7 +9,15 @@ import type { $side_effect } from '../../utils/organization/$side_effect';
8
9
  * @private utility of CLI
9
10
  */
10
11
  export function $deprecateCliCommand(command: Command, deprecationMessage: string): $side_effect {
11
- command.description(`${command.description()}\n\nDeprecated: ${deprecationMessage}`);
12
+ command.description(
13
+ spaceTrim(
14
+ (block) => `
15
+ ${block(command.description())}
16
+
17
+ Deprecated: ${block(deprecationMessage)}
18
+ `,
19
+ ),
20
+ );
12
21
  command.hook('preAction', () => {
13
22
  console.warn(colors.yellow(createDeprecatedCliCommandWarning(command, deprecationMessage)));
14
23
  });
@@ -101,8 +101,13 @@ export class GoalCommitmentDefinition extends BaseCommitmentDefinition<'GOAL' |
101
101
  return requirements;
102
102
  }
103
103
 
104
- // Add goal as a proper h2 section to the system message
105
- const goalSection = `## Goal\n\n${trimmedContent}`;
104
+ const goalSection = spaceTrim(
105
+ (block) => `
106
+ ## Goal
107
+
108
+ ${block(trimmedContent)}
109
+ `,
110
+ );
106
111
  const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
107
112
 
108
113
  return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
@@ -70,8 +70,13 @@ export class LanguageCommitmentDefinition extends BaseCommitmentDefinition<'LANG
70
70
  return requirements;
71
71
  }
72
72
 
73
- // Add language as a bullet under a ## Language section
74
- const languageSection = `## Language\n\n- Your language is ${trimmedContent}`;
73
+ const languageSection = spaceTrim(
74
+ (block) => `
75
+ ## Language
76
+
77
+ - Your language is ${block(trimmedContent)}
78
+ `,
79
+ );
75
80
 
76
81
  return this.appendToSystemMessage(requirements, languageSection, '\n\n');
77
82
  }
@@ -448,7 +448,18 @@ function buildTeammateMetadata(entry: TeamToolEntry): TeamToolResult['teammate']
448
448
  * Builds the teammate request text, optionally including context.
449
449
  */
450
450
  function buildTeammateRequest(message: string, context?: string): string {
451
- return context ? `${message}\n\nContext:\n${context}` : message;
451
+ if (!context) {
452
+ return message;
453
+ }
454
+
455
+ return spaceTrim(
456
+ (block) => `
457
+ ${block(message)}
458
+
459
+ Context:
460
+ ${block(context)}
461
+ `,
462
+ );
452
463
  }
453
464
 
454
465
  /**
@@ -1,3 +1,4 @@
1
+ import { spaceTrim } from 'spacetrim';
1
2
  import type { ToolFunction } from '../../scripting/javascript/JavascriptExecutionToolsOptions';
2
3
  import type { string_javascript_name } from '../../types/string_person_fullname';
3
4
  import { decodeAttachmentAsText, DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES } from '../../utils/files/decodeAttachmentAsText';
@@ -197,7 +198,13 @@ export function createUseProjectToolFunctions(): Record<string_javascript_name,
197
198
  const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
198
199
  const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
199
200
  const contentToReturn = wasCharacterTruncated
200
- ? `${lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS)}\n\n[...truncated...]`
201
+ ? spaceTrim(
202
+ (block) => `
203
+ ${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
204
+
205
+ [...truncated...]
206
+ `,
207
+ )
201
208
  : lineRangedContent;
202
209
 
203
210
  const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
@@ -1,3 +1,4 @@
1
+ import { spaceTrim } from 'spacetrim';
1
2
  import type { FileImportPlugin } from './FileImportPlugin';
2
3
 
3
4
  /**
@@ -14,10 +15,22 @@ export const JsonFileImportPlugin: FileImportPlugin = {
14
15
  try {
15
16
  const json = JSON.parse(content);
16
17
  const formattedJson = JSON.stringify(json, null, 4);
17
- return `\`\`\`json\n${formattedJson}\n\`\`\``;
18
+ return spaceTrim(
19
+ (block) => `
20
+ \`\`\`json
21
+ ${block(formattedJson)}
22
+ \`\`\`
23
+ `,
24
+ );
18
25
  } catch (error) {
19
26
  // If JSON is invalid, still import it but maybe not as pretty JSON
20
- return `\`\`\`json\n${content}\n\`\`\``;
27
+ return spaceTrim(
28
+ (block) => `
29
+ \`\`\`json
30
+ ${block(content)}
31
+ \`\`\`
32
+ `,
33
+ );
21
34
  }
22
35
  },
23
36
  };
@@ -1,3 +1,4 @@
1
+ import { spaceTrim } from 'spacetrim';
1
2
  import { mimeTypeToExtension } from '../utils/files/mimeTypeToExtension';
2
3
  import type { FileImportPlugin } from './FileImportPlugin';
3
4
 
@@ -23,6 +24,12 @@ export const TextFileImportPlugin: FileImportPlugin = {
23
24
  const extension = mimeTypeToExtension(mimeType);
24
25
  const codeBlockType = extension || 'txt';
25
26
 
26
- return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
27
+ return spaceTrim(
28
+ (block) => `
29
+ \`\`\`${codeBlockType}
30
+ ${block(content)}
31
+ \`\`\`
32
+ `,
33
+ );
27
34
  },
28
35
  };
@@ -589,7 +589,12 @@ function colorizeAvailableProviderMessage(
589
589
  * @private internal function of `$registeredLlmToolsMessage`
590
590
  */
591
591
  function createUsedEnvMessage(): string {
592
- return $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
592
+ return $usedEnvFilename === null
593
+ ? `Unknown \`.env\` file`
594
+ : spaceTrim(`
595
+ Used \`.env\` file:
596
+ ${$usedEnvFilename}
597
+ `);
593
598
  }
594
599
 
595
600
  // TODO: [®] DRY Register logic