@promptbook/cli 0.112.0-126 → 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 +681 -837
  156. package/src/utils/chat/chatAttachments/resolveChatAttachmentContent.ts +8 -1
  157. package/src/version.ts +2 -2
  158. package/src/versions.txt +1 -0
  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
package/esm/index.es.js CHANGED
@@ -37,10 +37,10 @@ import * as ts from 'typescript';
37
37
  import ignore from 'ignore';
38
38
  import * as readline from 'readline';
39
39
  import { emitKeypressEvents, clearLine, cursorTo, createInterface } from 'readline';
40
+ import { EventEmitter } from 'events';
40
41
  import { lookup, extension } from 'mime-types';
41
42
  import papaparse from 'papaparse';
42
43
  import { pathToFileURL } from 'url';
43
- import { EventEmitter } from 'events';
44
44
  import { Client } from 'pg';
45
45
  import '@supabase/supabase-js';
46
46
 
@@ -58,7 +58,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
58
58
  * @generated
59
59
  * @see https://github.com/webgptorg/promptbook
60
60
  */
61
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-126';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-127';
62
62
  /**
63
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
64
64
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2692,8 +2692,16 @@ function createAgentsServerEnvVariable(name, value) {
2692
2692
  * Builds a `.env` block containing missing Agents Server variables and documentation links.
2693
2693
  */
2694
2694
  function buildMissingEnvVariablesBlock$1(variables) {
2695
- const variableBlocks = variables.map(({ documentationUrl, name, value }) => [AGENTS_SERVER_ENV_CREATED_COMMENT, `# Documentation: ${documentationUrl}`, `${name}=${value}`].join('\n'));
2696
- return ['# Promptbook Agents Server', ...variableBlocks].join('\n\n');
2695
+ const variableBlocks = variables.map(({ documentationUrl, name, value }) => spaceTrim$1(`
2696
+ ${AGENTS_SERVER_ENV_CREATED_COMMENT}
2697
+ # Documentation: ${documentationUrl}
2698
+ ${name}=${value}
2699
+ `));
2700
+ return spaceTrim$1((block) => `
2701
+ # Promptbook Agents Server
2702
+
2703
+ ${block(variableBlocks.join('\n\n'))}
2704
+ `);
2697
2705
  }
2698
2706
  // Note: [🟡] Code for Agents Server environment bootstrapping [ensureAgentsServerEnvFile](src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts) should never be published outside of `@promptbook/cli`
2699
2707
 
@@ -4265,6 +4273,13 @@ function buildScriptPathSessionRows(scriptPaths, bodyWidth) {
4265
4273
  },
4266
4274
  ];
4267
4275
  }
4276
+ /**
4277
+ * Builds one OSC 8 terminal hyperlink to an HTTP URL.
4278
+ */
4279
+ function buildTerminalUrlLink(url, availableWidth) {
4280
+ const displayUrl = fitPlainText(url, availableWidth);
4281
+ return `\u001B]8;;${url}\u0007${displayUrl}\u001B]8;;\u0007`;
4282
+ }
4268
4283
  /**
4269
4284
  * Builds the colored phase badge shown in the session box.
4270
4285
  */
@@ -4455,6 +4470,9 @@ function buildSessionRows(options, bodyWidth, pausePresentation) {
4455
4470
  }
4456
4471
  const configurationRows = [
4457
4472
  ...buildOptionalSessionRow('Context', options.config.context),
4473
+ ...buildOptionalSessionRow('Server', options.config.serverUrl
4474
+ ? buildTerminalUrlLink(options.config.serverUrl, bodyWidth - SESSION_LABEL_WIDTH - 1)
4475
+ : undefined),
4458
4476
  ...buildOptionalSessionRow('Test', options.config.testCommand),
4459
4477
  ];
4460
4478
  return [
@@ -4897,7 +4915,8 @@ function getTerminalWidth() {
4897
4915
  * @private internal entry point of coder run UI
4898
4916
  */
4899
4917
  function renderCoderRunUi(startTime, options = {}) {
4900
- const state = new CoderRunUiState(startTime);
4918
+ var _a;
4919
+ const state = (_a = options.state) !== null && _a !== void 0 ? _a : new CoderRunUiState(startTime);
4901
4920
  const buildFrameLinesFromState = options.buildFrameLines || buildCoderRunUiFrame;
4902
4921
  if (!process.stdout.isTTY) {
4903
4922
  return {
@@ -7876,8 +7895,11 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
7876
7895
  if (!trimmedContent) {
7877
7896
  return requirements;
7878
7897
  }
7879
- // Add goal as a proper h2 section to the system message
7880
- const goalSection = `## Goal\n\n${trimmedContent}`;
7898
+ const goalSection = spaceTrim$1((block) => `
7899
+ ## Goal
7900
+
7901
+ ${block(trimmedContent)}
7902
+ `);
7881
7903
  const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
7882
7904
  return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
7883
7905
  }
@@ -8399,8 +8421,11 @@ class LanguageCommitmentDefinition extends BaseCommitmentDefinition {
8399
8421
  if (!trimmedContent) {
8400
8422
  return requirements;
8401
8423
  }
8402
- // Add language as a bullet under a ## Language section
8403
- const languageSection = `## Language\n\n- Your language is ${trimmedContent}`;
8424
+ const languageSection = spaceTrim$1((block) => `
8425
+ ## Language
8426
+
8427
+ - Your language is ${block(trimmedContent)}
8428
+ `);
8404
8429
  return this.appendToSystemMessage(requirements, languageSection, '\n\n');
8405
8430
  }
8406
8431
  }
@@ -13365,41 +13390,91 @@ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, mo
13365
13390
  context.fill();
13366
13391
  context.restore();
13367
13392
  }
13393
+ /**
13394
+ * Number of latitude segments used by the single blobby octopus mesh.
13395
+ *
13396
+ * @private helper of `octopus3d2AvatarVisual`
13397
+ */
13398
+ const LATITUDE_PATCH_COUNT$1 = 12;
13399
+ /**
13400
+ * Number of longitude segments used by the single blobby octopus mesh.
13401
+ *
13402
+ * @private helper of `octopus3d2AvatarVisual`
13403
+ */
13404
+ const LONGITUDE_PATCH_COUNT$1 = 24;
13368
13405
  /**
13369
13406
  * Resolves all visible projected patches for the single blobby octopus mesh.
13370
13407
  *
13408
+ * Within a single frame, mesh corner samples and longitude-only lobe-wave values are
13409
+ * quantized to the patch grid and computed once each rather than re-evaluated for every
13410
+ * patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
13411
+ * `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
13412
+ * corners are shared between neighboring patches.
13413
+ *
13371
13414
  * @private helper of `octopus3d2AvatarVisual`
13372
13415
  */
13373
13416
  function resolveVisibleBlobbyOctopusPatches(options) {
13374
13417
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
13375
- const latitudePatchCount = 12;
13376
- const longitudePatchCount = 24;
13418
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
13419
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
13377
13420
  const surfacePatches = [];
13421
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
13422
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
13423
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
13424
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
13425
+ }
13426
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
13427
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
13428
+ }
13429
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
13430
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
13431
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
13432
+ }
13433
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
13434
+ const transformedCornerSamples = new Array(cornerCount);
13435
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
13436
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
13437
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
13438
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
13439
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
13440
+ const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
13441
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
13442
+ }
13443
+ }
13378
13444
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
13379
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
13380
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
13445
+ const startLatitude = latitudeBoundaries[latitudeIndex];
13446
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
13381
13447
  const centerLatitude = (startLatitude + endLatitude) / 2;
13382
13448
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
13449
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
13450
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
13383
13451
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
13384
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
13385
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
13452
+ const startLongitude = longitudeBoundaries[longitudeIndex];
13453
+ const endLongitude = longitudeBoundaries[longitudeIndex + 1];
13386
13454
  const centerLongitude = (startLongitude + endLongitude) / 2;
13387
- const localCorners = [
13388
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
13389
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
13390
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
13391
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
13455
+ const transformedCorners = [
13456
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
13457
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
13458
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
13459
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
13392
13460
  ];
13393
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
13394
13461
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
13395
13462
  if (surfaceNormal.z <= 0.01) {
13396
13463
  continue;
13397
13464
  }
13398
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
13465
+ const projectedCorners = [
13466
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
13467
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
13468
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
13469
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
13470
+ ];
13399
13471
  surfacePatches.push({
13400
13472
  corners: projectedCorners,
13401
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
13402
- transformedCorners.length,
13473
+ averageDepth: (transformedCorners[0].z +
13474
+ transformedCorners[1].z +
13475
+ transformedCorners[2].z +
13476
+ transformedCorners[3].z) /
13477
+ 4,
13403
13478
  lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
13404
13479
  fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
13405
13480
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
@@ -13417,12 +13492,21 @@ function resolveVisibleBlobbyOctopusPatches(options) {
13417
13492
  * @private helper of `octopus3d2AvatarVisual`
13418
13493
  */
13419
13494
  function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
13495
+ const { morphologyProfile, animationPhase, timeMs } = options;
13496
+ return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
13497
+ }
13498
+ /**
13499
+ * Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
13500
+ * to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
13501
+ *
13502
+ * @private helper of `octopus3d2AvatarVisual`
13503
+ */
13504
+ function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
13420
13505
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
13421
13506
  const cosineLatitude = Math.max(0, Math.cos(latitude));
13422
13507
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
13423
13508
  const upperBlend = Math.pow(1 - verticalProgress, 1.2);
13424
13509
  const lowerBlend = Math.pow(verticalProgress, 1.42);
13425
- const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
13426
13510
  const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
13427
13511
  const horizontalScale = 1.02 +
13428
13512
  skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
@@ -13719,45 +13803,102 @@ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs
13719
13803
  context.fill();
13720
13804
  context.restore();
13721
13805
  }
13806
+ /**
13807
+ * Number of latitude segments used by the continuous Octopus 3D 3 mesh.
13808
+ *
13809
+ * @private helper of `octopus3d3AvatarVisual`
13810
+ */
13811
+ const LATITUDE_PATCH_COUNT = 16;
13812
+ /**
13813
+ * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
13814
+ *
13815
+ * @private helper of `octopus3d3AvatarVisual`
13816
+ */
13817
+ const LONGITUDE_PATCH_COUNT = 40;
13722
13818
  /**
13723
13819
  * Resolves visible projected patches for the continuous octopus mesh.
13724
13820
  *
13821
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
13822
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
13823
+ * than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
13824
+ * for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
13825
+ * triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
13826
+ *
13725
13827
  * @private helper of `octopus3d3AvatarVisual`
13726
13828
  */
13727
13829
  function resolveVisibleContinuousOctopusPatches(options) {
13728
13830
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
13729
- const latitudePatchCount = 16;
13730
- const longitudePatchCount = 40;
13831
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
13832
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
13731
13833
  const surfacePatches = [];
13834
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
13835
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
13836
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
13837
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
13838
+ }
13839
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
13840
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
13841
+ }
13842
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
13843
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
13844
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
13845
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
13846
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
13847
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
13848
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
13849
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
13850
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
13851
+ }
13852
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
13853
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
13854
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
13855
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
13856
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
13857
+ }
13858
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
13859
+ const transformedCornerSamples = new Array(cornerCount);
13860
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
13861
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
13862
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
13863
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
13864
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
13865
+ const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
13866
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
13867
+ }
13868
+ }
13732
13869
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
13733
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
13734
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
13870
+ const startLatitude = latitudeBoundaries[latitudeIndex];
13871
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
13735
13872
  const centerLatitude = (startLatitude + endLatitude) / 2;
13736
13873
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
13874
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
13875
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
13737
13876
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
13738
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
13739
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
13740
- const centerLongitude = (startLongitude + endLongitude) / 2;
13741
- const localCorners = [
13742
- sampleContinuousOctopusSurfacePoint(options, startLatitude, startLongitude),
13743
- sampleContinuousOctopusSurfacePoint(options, startLatitude, endLongitude),
13744
- sampleContinuousOctopusSurfacePoint(options, endLatitude, endLongitude),
13745
- sampleContinuousOctopusSurfacePoint(options, endLatitude, startLongitude),
13877
+ const transformedCorners = [
13878
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
13879
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
13880
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
13881
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
13746
13882
  ];
13747
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
13748
13883
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
13749
13884
  if (surfaceNormal.z <= 0.008) {
13750
13885
  continue;
13751
13886
  }
13752
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
13753
- const tentacleInfluence = resolveContinuousTentacleInfluence(options, centerLongitude);
13754
- const lowerLobeWave = resolveContinuousLobeWave(options, centerLongitude);
13887
+ const projectedCorners = [
13888
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
13889
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
13890
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
13891
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
13892
+ ];
13755
13893
  surfacePatches.push({
13756
13894
  corners: projectedCorners,
13757
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
13758
- transformedCorners.length,
13895
+ averageDepth: (transformedCorners[0].z +
13896
+ transformedCorners[1].z +
13897
+ transformedCorners[2].z +
13898
+ transformedCorners[3].z) /
13899
+ 4,
13759
13900
  lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
13760
- fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), tentacleInfluence.core, lowerLobeWave),
13901
+ fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
13761
13902
  outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
13762
13903
  });
13763
13904
  }
@@ -13773,16 +13914,27 @@ function resolveVisibleContinuousOctopusPatches(options) {
13773
13914
  * @private helper of `octopus3d3AvatarVisual`
13774
13915
  */
13775
13916
  function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
13917
+ return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
13918
+ }
13919
+ /**
13920
+ * Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
13921
+ * values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
13922
+ *
13923
+ * The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
13924
+ * corner grid, so the same longitude is reused across every latitude row and each
13925
+ * tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
13926
+ *
13927
+ * @private helper of `octopus3d3AvatarVisual`
13928
+ */
13929
+ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
13776
13930
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
13777
13931
  const cosineLatitude = Math.max(0, Math.cos(latitude));
13778
13932
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
13779
13933
  const upperBlend = Math.pow(1 - verticalProgress, 1.28);
13780
13934
  const lowerBlend = smoothStep(0.38, 1, verticalProgress);
13781
13935
  const tipBlend = smoothStep(0.68, 1, verticalProgress);
13782
- const tentacleInfluence = resolveContinuousTentacleInfluence(options, longitude);
13783
13936
  const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
13784
13937
  const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
13785
- const lowerLobeWave = resolveContinuousLobeWave(options, longitude);
13786
13938
  const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
13787
13939
  animationPhase * 0.6 +
13788
13940
  timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
@@ -17187,7 +17339,15 @@ function buildTeammateMetadata(entry) {
17187
17339
  * Builds the teammate request text, optionally including context.
17188
17340
  */
17189
17341
  function buildTeammateRequest(message, context) {
17190
- return context ? `${message}\n\nContext:\n${context}` : message;
17342
+ if (!context) {
17343
+ return message;
17344
+ }
17345
+ return spaceTrim$1((block) => `
17346
+ ${block(message)}
17347
+
17348
+ Context:
17349
+ ${block(context)}
17350
+ `);
17191
17351
  }
17192
17352
  /**
17193
17353
  * Builds a minimal chat prompt for teammate calls.
@@ -21210,7 +21370,11 @@ function createUseProjectToolFunctions() {
21210
21370
  const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
21211
21371
  const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
21212
21372
  const contentToReturn = wasCharacterTruncated
21213
- ? `${lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS)}\n\n[...truncated...]`
21373
+ ? spaceTrim$1((block) => `
21374
+ ${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
21375
+
21376
+ [...truncated...]
21377
+ `)
21214
21378
  : lineRangedContent;
21215
21379
  const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
21216
21380
  if (wasCharacterTruncated) {
@@ -26085,7 +26249,8 @@ function readOptionalSigningKeyValue() {
26085
26249
  /**
26086
26250
  * Commits staged changes with the provided message using the dedicated coding-agent identity when configured,
26087
26251
  * otherwise falls back to the default Git configuration. Remote pushing is opt-in via `options.autoPush`,
26088
- * `options.includePaths` can restrict staging, and `options.excludePaths` can keep temporary artifacts out of the created commit.
26252
+ * `options.includePaths` can restrict staging, `options.onlyPaths` can restrict the commit pathspec,
26253
+ * and `options.excludePaths` can keep temporary artifacts out of the created commit.
26089
26254
  */
26090
26255
  async function commitChanges(message, options) {
26091
26256
  const projectPath = (options === null || options === void 0 ? void 0 : options.projectPath) || process.cwd();
@@ -26097,7 +26262,7 @@ async function commitChanges(message, options) {
26097
26262
  const signingFlag = buildAgentGitSigningFlag();
26098
26263
  await stageCommitChanges(projectPath, agentEnv, options === null || options === void 0 ? void 0 : options.includePaths, options === null || options === void 0 ? void 0 : options.excludePaths);
26099
26264
  await runGitCommand({
26100
- command: buildGitCommitCommand(commitMessagePath, signingFlag),
26265
+ command: buildGitCommitCommand(commitMessagePath, signingFlag, options === null || options === void 0 ? void 0 : options.onlyPaths),
26101
26266
  cwd: projectPath,
26102
26267
  env: agentEnv,
26103
26268
  });
@@ -26277,12 +26442,15 @@ async function executeGitPushCommand(command, projectPath, agentEnv) {
26277
26442
  /**
26278
26443
  * Builds the `git commit` command with an optional signing flag.
26279
26444
  */
26280
- function buildGitCommitCommand(commitMessagePath, signingFlag) {
26445
+ function buildGitCommitCommand(commitMessagePath, signingFlag, onlyPaths) {
26281
26446
  const commandParts = ['git commit'];
26282
26447
  if (signingFlag) {
26283
26448
  commandParts.push(signingFlag);
26284
26449
  }
26285
26450
  commandParts.push(`--file "${commitMessagePath}"`);
26451
+ if (onlyPaths && onlyPaths.length > 0) {
26452
+ commandParts.push('--', ...onlyPaths.map(quoteShellPath$1));
26453
+ }
26286
26454
  return commandParts.join(' ');
26287
26455
  }
26288
26456
  /**
@@ -27780,7 +27948,11 @@ function createOutputSnippet(output) {
27780
27948
  if (normalized.length <= OUTPUT_SNIPPET_MAX_CHARS) {
27781
27949
  return normalized;
27782
27950
  }
27783
- return `${normalized.slice(0, OUTPUT_SNIPPET_MAX_CHARS)}\n\n...[truncated]`;
27951
+ return spaceTrim$1((block) => `
27952
+ ${block(normalized.slice(0, OUTPUT_SNIPPET_MAX_CHARS))}
27953
+
27954
+ ...[truncated]
27955
+ `);
27784
27956
  }
27785
27957
  /**
27786
27958
  * Builds one command failure message that keeps a useful snippet of CLI output.
@@ -27790,7 +27962,11 @@ function buildCommandFailureMessage(scriptPathPosix, code, fullOutput) {
27790
27962
  if (!outputSnippet) {
27791
27963
  return `Command "bash ${scriptPathPosix}" exited with code ${code !== null && code !== void 0 ? code : 'unknown'}`;
27792
27964
  }
27793
- return `Command "bash ${scriptPathPosix}" exited with code ${code !== null && code !== void 0 ? code : 'unknown'}\n\n${outputSnippet}`;
27965
+ return spaceTrim$1((block) => `
27966
+ Command "bash ${scriptPathPosix}" exited with code ${code !== null && code !== void 0 ? code : 'unknown'}
27967
+
27968
+ ${block(outputSnippet)}
27969
+ `);
27794
27970
  }
27795
27971
  /**
27796
27972
  * Runs a script until a completion marker is observed and output is idle for a set timeout.
@@ -27907,7 +28083,9 @@ async function runScriptUntilMarkerIdle(options) {
27907
28083
  * Handles process exit and resolves or rejects accordingly.
27908
28084
  */
27909
28085
  const handleExit = (code) => {
27910
- const failure = code === 0 || markerSeen ? undefined : new Error(buildCommandFailureMessage(scriptPathPosix, code, fullOutput));
28086
+ const failure = code === 0 || markerSeen
28087
+ ? undefined
28088
+ : new Error(buildCommandFailureMessage(scriptPathPosix, code, fullOutput));
27911
28089
  const status = failure ? `failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}` : 'succeeded';
27912
28090
  settleWithLog(status, () => {
27913
28091
  if (!failure) {
@@ -27925,8 +28103,14 @@ async function runScriptUntilMarkerIdle(options) {
27925
28103
  });
27926
28104
  commandProcess.on('error', (error) => {
27927
28105
  const outputSnippet = createOutputSnippet(fullOutput);
27928
- const details = outputSnippet ? `\n\n${outputSnippet}` : '';
27929
- const failure = new Error(`Command "bash ${scriptPathPosix}" failed: ${error.message}${details}`);
28106
+ const failureMessage = outputSnippet
28107
+ ? spaceTrim$1((block) => `
28108
+ Command "bash ${scriptPathPosix}" failed: ${error.message}
28109
+
28110
+ ${block(outputSnippet)}
28111
+ `)
28112
+ : `Command "bash ${scriptPathPosix}" failed: ${error.message}`;
28113
+ const failure = new Error(failureMessage);
27930
28114
  settleWithLog('failed before completion', () => reject(failure), failure);
27931
28115
  });
27932
28116
  });
@@ -28265,7 +28449,11 @@ function limitErrorDetails(details, maxChars = 4000) {
28265
28449
  if (normalized.length <= maxChars) {
28266
28450
  return normalized;
28267
28451
  }
28268
- return `${normalized.slice(0, maxChars)}\n\n...[truncated]`;
28452
+ return spaceTrim((block) => `
28453
+ ${block(normalized.slice(0, maxChars))}
28454
+
28455
+ ...[truncated]
28456
+ `);
28269
28457
  }
28270
28458
  /**
28271
28459
  * Returns true if at least one regex matches the provided text.
@@ -34914,11 +35102,19 @@ const JsonFileImportPlugin = {
34914
35102
  try {
34915
35103
  const json = JSON.parse(content);
34916
35104
  const formattedJson = JSON.stringify(json, null, 4);
34917
- return `\`\`\`json\n${formattedJson}\n\`\`\``;
35105
+ return spaceTrim$1((block) => `
35106
+ \`\`\`json
35107
+ ${block(formattedJson)}
35108
+ \`\`\`
35109
+ `);
34918
35110
  }
34919
35111
  catch (error) {
34920
35112
  // If JSON is invalid, still import it but maybe not as pretty JSON
34921
- return `\`\`\`json\n${content}\n\`\`\``;
35113
+ return spaceTrim$1((block) => `
35114
+ \`\`\`json
35115
+ ${block(content)}
35116
+ \`\`\`
35117
+ `);
34922
35118
  }
34923
35119
  },
34924
35120
  };
@@ -34942,7 +35138,11 @@ const TextFileImportPlugin = {
34942
35138
  import(content, mimeType) {
34943
35139
  const extension = mimeTypeToExtension(mimeType);
34944
35140
  const codeBlockType = extension || 'txt';
34945
- return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
35141
+ return spaceTrim$1((block) => `
35142
+ \`\`\`${codeBlockType}
35143
+ ${block(content)}
35144
+ \`\`\`
35145
+ `);
34946
35146
  },
34947
35147
  };
34948
35148
 
@@ -35189,11 +35389,19 @@ function collectExampleInteractionLines(parseResult, samples) {
35189
35389
  const examples = [];
35190
35390
  const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
35191
35391
  if (initialMessage) {
35192
- examples.push(`**Agent:**\n${initialMessage}`);
35392
+ examples.push(spaceTrim$1((block) => `
35393
+ **Agent:**
35394
+ ${block(initialMessage)}
35395
+ `));
35193
35396
  }
35194
35397
  if (samples && samples.length > 0) {
35195
35398
  for (const sample of samples) {
35196
- examples.push(`**User:** ${sample.question}\n\n**Agent:**\n${sample.answer}`);
35399
+ examples.push(spaceTrim$1((block) => `
35400
+ **User:** ${block(String(sample.question))}
35401
+
35402
+ **Agent:**
35403
+ ${block(sample.answer)}
35404
+ `));
35197
35405
  }
35198
35406
  }
35199
35407
  return examples;
@@ -35628,10 +35836,14 @@ async function createAgentRunnerSystemMessage(agentSource) {
35628
35836
  * Formats prepared model requirements for a text-only CLI harness prompt.
35629
35837
  */
35630
35838
  function formatAgentModelRequirementsForRunner(modelRequirements) {
35839
+ const promptSuffix = modelRequirements.promptSuffix.trim();
35631
35840
  return [
35632
35841
  modelRequirements.systemMessage.trim(),
35633
- modelRequirements.promptSuffix.trim()
35634
- ? `## Prompt suffix\n${modelRequirements.promptSuffix.trim()}`
35842
+ promptSuffix
35843
+ ? spaceTrim$1((block) => `
35844
+ ## Prompt suffix
35845
+ ${block(promptSuffix)}
35846
+ `)
35635
35847
  : '',
35636
35848
  ]
35637
35849
  .filter(Boolean)
@@ -39284,7 +39496,7 @@ async function ensureCoderGitignoreFile(projectPath) {
39284
39496
  */
39285
39497
  const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
39286
39498
  'coder:generate-boilerplates': 'ptbk coder generate-boilerplates --template ./prompts/templates/common.md',
39287
- 'coder:run': 'ptbk coder run --harness openai-codex --model gpt-5.4 --thinking-level xhigh --agent agents/developer.book --context AGENTS.md --no-wait',
39499
+ 'coder:run': 'ptbk coder run --harness openai-codex --model gpt-5.4 --thinking-level xhigh --agent agents/developer.book --context AGENTS.md',
39288
39500
  // 'coder:find-refactor-candidates': 'ptbk coder find-refactor-candidates',
39289
39501
  'coder:verify': 'ptbk coder verify',
39290
39502
  };
@@ -39743,33 +39955,26 @@ function $initializeCoderRunCommand(program) {
39743
39955
  command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
39744
39956
  addPromptRunnerExecutionOptions(command);
39745
39957
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption$1, 0);
39746
- command.option('--wait [duration]', spaceTrim$1(`
39747
- Wait between prompt rounds.
39748
- Without a value (default): waits for user confirmation before each prompt (interactive mode).
39749
- With a duration like 1h, 30m, 5s: waits that long between prompts to avoid hitting rate limits of the harness.
39750
- `), true);
39751
- // Note: --no-wait disables the default interactive wait-for-user behaviour
39752
- command.option('--no-wait', 'Skip all waiting between prompts and run non-interactively');
39958
+ command.option('--wait <duration>', spaceTrim$1(`
39959
+ Wait this long between prompt rounds to avoid hitting rate limits of the harness.
39960
+ Accepts durations like 1h, 30m, 5s.
39961
+ `));
39962
+ // Note: --no-auto disables the default auto behaviour and waits for user confirmation before each prompt
39963
+ command.option('--no-auto', 'Wait for user confirmation before each prompt instead of running automatically through the queue');
39753
39964
  command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
39754
39965
  command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
39755
39966
  command.action(handleActionErrors(async (cliOptions) => {
39756
- const { dryRun, agent, context, test, preserveLogs, priority, wait, autoMigrate, allowDestructiveAutoMigrate, } = cliOptions;
39967
+ const { dryRun, agent, context, test, preserveLogs, priority, wait, auto, autoMigrate, allowDestructiveAutoMigrate, } = cliOptions;
39757
39968
  const testCommand = normalizeCommandOptionValue$1(test);
39758
39969
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
39759
39970
  isAgentRequired: !dryRun,
39760
39971
  });
39761
- // [1] Parse the --wait option:
39762
- // true (default or --wait without value): wait for user confirmation
39763
- // false (--no-wait): no waiting at all
39764
- // string (--wait 1h): wait that long between prompt rounds
39765
- let waitForUser = false;
39766
- let waitBetweenPrompts = 0;
39767
- if (wait === true) {
39768
- waitForUser = true;
39769
- }
39770
- else if (typeof wait === 'string' && wait !== '') {
39771
- waitBetweenPrompts = parseDuration(wait);
39772
- }
39972
+ // [1] Parse the --wait and --no-auto options:
39973
+ // default: run automatically through the queue (no waiting)
39974
+ // --no-auto: wait for user confirmation before each prompt (interactive mode)
39975
+ // --wait <duration>: wait that long between prompt rounds to avoid rate limits
39976
+ const waitForUser = !auto;
39977
+ const waitBetweenPrompts = typeof wait === 'string' && wait !== '' ? parseDuration(wait) : 0;
39773
39978
  // Convert commander options to RunOptions format
39774
39979
  const runOptions = {
39775
39980
  dryRun,
@@ -39882,30 +40087,23 @@ function $initializeCoderServerCommand(program) {
39882
40087
  command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
39883
40088
  addPromptRunnerExecutionOptions(command);
39884
40089
  command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
39885
- command.option('--wait [duration]', spaceTrim$1(`
39886
- Wait between prompt rounds.
39887
- Without a value (default): waits for user confirmation before each prompt (interactive mode).
39888
- With a duration like 1h, 30m, 5s: waits that long between prompts to avoid hitting rate limits of the harness.
39889
- `), true);
39890
- command.option('--no-wait', 'Skip all waiting between prompts and run non-interactively');
40090
+ command.option('--wait <duration>', spaceTrim$1(`
40091
+ Wait this long between prompt rounds to avoid hitting rate limits of the harness.
40092
+ Accepts durations like 1h, 30m, 5s.
40093
+ `));
40094
+ command.option('--no-auto', 'Wait for user confirmation before each prompt instead of running automatically through the queue');
39891
40095
  command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
39892
40096
  command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
39893
40097
  command.action(handleActionErrors(async (cliOptions) => {
39894
- const { port: rawPort, dryRun, agent, context, test, preserveLogs, priority, wait, autoMigrate, allowDestructiveAutoMigrate, } = cliOptions;
40098
+ const { port: rawPort, dryRun, agent, context, test, preserveLogs, priority, wait, auto, autoMigrate, allowDestructiveAutoMigrate, } = cliOptions;
39895
40099
  const port = parseCoderServerPort(rawPort);
39896
40100
  const testCommand = normalizeCommandOptionValue(test);
39897
40101
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
39898
40102
  isAgentRequired: !dryRun,
39899
40103
  });
39900
- // [1] Parse the --wait option (same logic as `coder run`)
39901
- let waitForUser = false;
39902
- let waitBetweenPrompts = 0;
39903
- if (wait === true) {
39904
- waitForUser = true;
39905
- }
39906
- else if (typeof wait === 'string' && wait !== '') {
39907
- waitBetweenPrompts = parseDuration(wait);
39908
- }
40104
+ // [1] Parse the --wait and --no-auto options (same logic as `coder run`)
40105
+ const waitForUser = !auto;
40106
+ const waitBetweenPrompts = typeof wait === 'string' && wait !== '' ? parseDuration(wait) : 0;
39909
40107
  const runOptions = {
39910
40108
  port,
39911
40109
  dryRun,
@@ -40511,7 +40709,12 @@ function colorizeAvailableProviderMessage(providerMessage, { isInstalled, isFull
40511
40709
  * @private internal function of `$registeredLlmToolsMessage`
40512
40710
  */
40513
40711
  function createUsedEnvMessage() {
40514
- return $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
40712
+ return $usedEnvFilename === null
40713
+ ? `Unknown \`.env\` file`
40714
+ : spaceTrim$1(`
40715
+ Used \`.env\` file:
40716
+ ${$usedEnvFilename}
40717
+ `);
40515
40718
  }
40516
40719
  // TODO: [®] DRY Register logic
40517
40720
  // TODO: [🧠][⚛] Maybe pass env as argument
@@ -53101,7 +53304,11 @@ function $addGlobalOptionsToCommand(command) {
53101
53304
  * @private utility of CLI
53102
53305
  */
53103
53306
  function $deprecateCliCommand(command, deprecationMessage) {
53104
- command.description(`${command.description()}\n\nDeprecated: ${deprecationMessage}`);
53307
+ command.description(spaceTrim$1((block) => `
53308
+ ${block(command.description())}
53309
+
53310
+ Deprecated: ${block(deprecationMessage)}
53311
+ `));
53105
53312
  command.hook('preAction', () => {
53106
53313
  console.warn(colors.yellow(createDeprecatedCliCommandWarning(command, deprecationMessage)));
53107
53314
  });
@@ -65910,7 +66117,11 @@ function truncateAttachmentInlineText(content, maxCharacters) {
65910
66117
  };
65911
66118
  }
65912
66119
  return {
65913
- content: `${content.slice(0, Math.max(0, maxCharacters))}\n\n[...truncated...]`,
66120
+ content: spaceTrim$1((block) => `
66121
+ ${block(content.slice(0, Math.max(0, maxCharacters)))}
66122
+
66123
+ [...truncated...]
66124
+ `),
65914
66125
  isTruncated: true,
65915
66126
  };
65916
66127
  }
@@ -69210,7 +69421,7 @@ var findUnwrittenPrompts$1 = /*#__PURE__*/Object.freeze({
69210
69421
  /**
69211
69422
  * CLI usage text for this script.
69212
69423
  */
69213
- const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--preserve-logs] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--wait [duration]] [--no-wait] [--no-commit] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push] [--auto-pull]';
69424
+ const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--preserve-logs] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--wait <duration>] [--no-auto] [--no-commit] [--ignore-git-changes] [--no-normalize-line-endings] [--auto-push] [--auto-pull]';
69214
69425
  /**
69215
69426
  * Top-level flags supported by this command.
69216
69427
  */
@@ -69228,7 +69439,7 @@ const KNOWN_OPTION_FLAGS = new Set([
69228
69439
  '--auto-migrate',
69229
69440
  '--allow-destructive-auto-migrate',
69230
69441
  '--wait',
69231
- '--no-wait',
69442
+ '--no-auto',
69232
69443
  '--no-commit',
69233
69444
  '--ignore-git-changes',
69234
69445
  '--no-normalize-line-endings',
@@ -69266,22 +69477,20 @@ function parseRunOptions(args) {
69266
69477
  const allowDestructiveAutoMigrate = args.includes('--allow-destructive-auto-migrate');
69267
69478
  const autoPush = args.includes('--auto-push');
69268
69479
  const autoPull = args.includes('--auto-pull');
69269
- // [1] Parse --wait [duration]:
69270
- // absent or --no-wait: no waiting
69271
- // --wait (no value): wait for user confirmation
69480
+ // [1] Parse --wait <duration> and --no-auto:
69481
+ // default: run automatically through the queue (no waiting)
69482
+ // --no-auto: wait for user confirmation before each prompt (interactive mode)
69272
69483
  // --wait 1h: wait 1h between prompt rounds to avoid rate limits
69273
- const waitOptionalValue = readOptionalOptionValue(args, '--wait');
69274
- let waitForUser = false;
69484
+ const waitForUser = args.includes('--no-auto');
69275
69485
  let waitBetweenPrompts = 0;
69276
- if (waitOptionalValue === null) {
69277
- // --wait present without a duration value: interactive mode
69278
- waitForUser = true;
69279
- }
69280
- else if (waitOptionalValue !== undefined) {
69281
- // --wait <duration>: time-based wait between rounds
69282
- waitBetweenPrompts = parseDuration(waitOptionalValue);
69486
+ const hasWaitFlag = args.includes('--wait');
69487
+ const waitValue = readOptionValue(args, '--wait');
69488
+ if (hasWaitFlag) {
69489
+ if (waitValue === undefined || waitValue.startsWith('-')) {
69490
+ exitWithUsageError('Missing value for --wait. Use a duration like 1h, 30m, 5s.');
69491
+ }
69492
+ waitBetweenPrompts = parseDuration(waitValue);
69283
69493
  }
69284
- // --no-wait (or no flag): both waitForUser and waitBetweenPrompts remain false/0
69285
69494
  let thinkingLevel;
69286
69495
  if (hasTestCommandFlag && testCommand === undefined) {
69287
69496
  exitWithUsageError('Missing value for --test. Use a shell command such as `npm run test` and quote it when it contains top-level CLI flags.');
@@ -69320,23 +69529,6 @@ function parseRunOptions(args) {
69320
69529
  priority,
69321
69530
  };
69322
69531
  }
69323
- /**
69324
- * Reads an optional value for a flag that may appear with or without a following value.
69325
- *
69326
- * Returns `undefined` when the flag is absent, `null` when the flag is present but has no value
69327
- * (the next token is another flag or the end of args), and the value string otherwise.
69328
- */
69329
- function readOptionalOptionValue(args, flag) {
69330
- if (!args.includes(flag)) {
69331
- return undefined;
69332
- }
69333
- const index = args.indexOf(flag);
69334
- const nextArg = args[index + 1];
69335
- if (nextArg === undefined || nextArg.startsWith('-')) {
69336
- return null;
69337
- }
69338
- return nextArg;
69339
- }
69340
69532
  /**
69341
69533
  * Reads a value of a CLI option that follows a given flag.
69342
69534
  */
@@ -71313,7 +71505,13 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
71313
71505
  const taskPrompt = buildCodexPrompt(nextPrompt.file, nextPrompt.section);
71314
71506
  // Prepend agent system message before the task so the harness sees agent instructions first
71315
71507
  const promptWithAgent = resolvedAgentSystemMessage
71316
- ? `${resolvedAgentSystemMessage.trim()}\n\n${taskPrompt}`
71508
+ ? spaceTrim$1((block) => `
71509
+ ${block(resolvedAgentSystemMessage.trim())}
71510
+
71511
+ ## Your Task
71512
+
71513
+ ${block(taskPrompt)}
71514
+ `)
71317
71515
  : taskPrompt;
71318
71516
  const codexPrompt = appendCoderContext(promptWithAgent, resolvedCoderContext);
71319
71517
  const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
@@ -71382,11 +71580,11 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
71382
71580
  */
71383
71581
  function setPromptRoundRunningState(options) {
71384
71582
  const { isRichUiEnabled, promptLabel, scriptPath, uiHandle } = options;
71583
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
71584
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentScriptPath(scriptPath);
71585
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('running');
71586
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Running');
71385
71587
  if (isRichUiEnabled) {
71386
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentPrompt(promptLabel);
71387
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentScriptPath(scriptPath);
71388
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('running');
71389
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage('Running');
71390
71588
  return;
71391
71589
  }
71392
71590
  console.info(colors.blue(`Processing ${promptLabel}`));
@@ -71656,7 +71854,7 @@ function validateRunCodexPromptOptions(options) {
71656
71854
  }
71657
71855
  if (options.noCommit && !options.waitForUser && !options.ignoreGitChanges) {
71658
71856
  throw new NotAllowed(spaceTrim$1(`
71659
- Flag \`--no-commit\` requires \`--ignore-git-changes\` when combined with \`--no-wait\`.
71857
+ Flag \`--no-commit\` requires \`--ignore-git-changes\` when running in auto mode (the default; pass \`--no-auto\` for interactive confirmation).
71660
71858
 
71661
71859
  Without commits, the next prompt round would fail the clean working tree check.
71662
71860
  `));
@@ -71688,7 +71886,11 @@ async function pullLatestChangesIfEnabled(options) {
71688
71886
  function createRunDisplays(options, runStartDate) {
71689
71887
  const isRichUiEnabled = !options.dryRun && !options.noUi && Boolean(process.stdout.isTTY);
71690
71888
  const progressDisplay = options.dryRun || options.noUi || isRichUiEnabled ? undefined : new CliProgressDisplay(runStartDate, options.priority);
71691
- const uiHandle = isRichUiEnabled ? renderCoderRunUi(runStartDate) : undefined;
71889
+ const uiHandle = isRichUiEnabled || options.uiState
71890
+ ? renderCoderRunUi(runStartDate, {
71891
+ state: options.uiState,
71892
+ })
71893
+ : undefined;
71692
71894
  return {
71693
71895
  isRichUiEnabled,
71694
71896
  progressDisplay,
@@ -71753,6 +71955,7 @@ function initializeRunUi(uiHandle, runnerName, actualRunnerModel, options) {
71753
71955
  modelName: actualRunnerModel,
71754
71956
  thinkingLevel: options.thinkingLevel,
71755
71957
  context: options.context,
71958
+ serverUrl: options.serverUrl,
71756
71959
  priority: options.priority,
71757
71960
  testCommand: options.testCommand,
71758
71961
  });
@@ -71911,6 +72114,166 @@ var runCodexPrompts$1 = /*#__PURE__*/Object.freeze({
71911
72114
  runCodexPrompts: runCodexPrompts
71912
72115
  });
71913
72116
 
72117
+ /**
72118
+ * Converts parsed prompt files into a web-board response with derived columns and tags.
72119
+ */
72120
+ function buildCoderServerPromptFileResponses(options) {
72121
+ const activePrompt = getActivePrompt(options.uiState);
72122
+ return [
72123
+ ...options.promptFiles.map((promptFile) => buildCoderServerPromptFileResponse({
72124
+ promptFile,
72125
+ isFinished: false,
72126
+ minimumPriority: options.minimumPriority,
72127
+ activePrompt,
72128
+ })),
72129
+ ...options.finishedPromptFiles.map((promptFile) => buildCoderServerPromptFileResponse({
72130
+ promptFile,
72131
+ isFinished: true,
72132
+ minimumPriority: options.minimumPriority,
72133
+ activePrompt,
72134
+ })),
72135
+ ];
72136
+ }
72137
+ /**
72138
+ * Builds one prompt-file response.
72139
+ */
72140
+ function buildCoderServerPromptFileResponse(options) {
72141
+ const { promptFile, isFinished, minimumPriority, activePrompt } = options;
72142
+ return {
72143
+ filePath: promptFile.path,
72144
+ fileName: promptFile.name,
72145
+ relativeFilePath: relative(process.cwd(), promptFile.path).replace(/\\/gu, '/'),
72146
+ isFinished,
72147
+ sections: promptFile.sections.map((section) => buildCoderServerPromptSectionResponse({
72148
+ promptFile,
72149
+ section,
72150
+ isFinished,
72151
+ minimumPriority,
72152
+ activePrompt,
72153
+ })),
72154
+ };
72155
+ }
72156
+ /**
72157
+ * Builds one prompt-section response with derived board metadata.
72158
+ */
72159
+ function buildCoderServerPromptSectionResponse(options) {
72160
+ const { promptFile, section, isFinished, minimumPriority, activePrompt } = options;
72161
+ const isUnwritten = isPromptToBeWritten(promptFile, section);
72162
+ const isActive = isPromptActive(promptFile, section, activePrompt);
72163
+ const column = getPromptColumn({
72164
+ section,
72165
+ isFinished,
72166
+ isUnwritten,
72167
+ isActive,
72168
+ minimumPriority,
72169
+ });
72170
+ return {
72171
+ index: section.index,
72172
+ status: section.status,
72173
+ column,
72174
+ priority: section.priority,
72175
+ summary: buildPromptSummary(promptFile, section),
72176
+ content: buildCodexPrompt(promptFile, section),
72177
+ label: buildPromptLabelForDisplay(promptFile, section),
72178
+ tags: buildPromptTags({
72179
+ section,
72180
+ isUnwritten,
72181
+ isActive,
72182
+ activePrompt,
72183
+ }),
72184
+ };
72185
+ }
72186
+ /**
72187
+ * Chooses the web-board column for one prompt section without changing persisted prompt status.
72188
+ */
72189
+ function getPromptColumn(options) {
72190
+ const { section, isFinished, isUnwritten, isActive, minimumPriority } = options;
72191
+ if (isFinished) {
72192
+ return 'finished';
72193
+ }
72194
+ if (section.status === 'failed') {
72195
+ return 'errors';
72196
+ }
72197
+ if (section.status === 'done') {
72198
+ return 'done';
72199
+ }
72200
+ if (section.status === 'not-ready' || isUnwritten) {
72201
+ return 'backlog';
72202
+ }
72203
+ if (isActive) {
72204
+ return 'in-progress';
72205
+ }
72206
+ if (!hasSufficientPriority(section, minimumPriority)) {
72207
+ return 'low-priority';
72208
+ }
72209
+ return 'todo';
72210
+ }
72211
+ /**
72212
+ * Builds all tags shown on a prompt card.
72213
+ */
72214
+ function buildPromptTags(options) {
72215
+ var _a;
72216
+ const tags = [];
72217
+ if (options.section.status === 'not-ready') {
72218
+ tags.push({ id: 'not-ready', label: '[-]' });
72219
+ }
72220
+ if (options.isUnwritten) {
72221
+ tags.push({ id: 'unwritten', label: '@@@' });
72222
+ }
72223
+ if (options.isActive) {
72224
+ tags.push(((_a = options.activePrompt) === null || _a === void 0 ? void 0 : _a.phase) === 'verifying'
72225
+ ? { id: 'verifying', label: 'Verifying' }
72226
+ : { id: 'implementing', label: 'Implementing' });
72227
+ }
72228
+ return tags;
72229
+ }
72230
+ /**
72231
+ * Extracts an active prompt only while an agent is implementing or verifying it.
72232
+ */
72233
+ function getActivePrompt(uiState) {
72234
+ if (!(uiState === null || uiState === void 0 ? void 0 : uiState.currentPromptLabel)) {
72235
+ return undefined;
72236
+ }
72237
+ if (uiState.phase !== 'running' && uiState.phase !== 'verifying') {
72238
+ return undefined;
72239
+ }
72240
+ return {
72241
+ label: uiState.currentPromptLabel,
72242
+ phase: uiState.phase,
72243
+ };
72244
+ }
72245
+ /**
72246
+ * Checks whether a parsed section is the prompt currently handled by the active agent.
72247
+ */
72248
+ function isPromptActive(promptFile, section, activePrompt) {
72249
+ if (!activePrompt || section.status !== 'todo') {
72250
+ return false;
72251
+ }
72252
+ return buildPromptLabelForDisplay(promptFile, section) === activePrompt.label;
72253
+ }
72254
+ // Note: [🟡] Code for CLI command [coder server](scripts/run-codex-prompts/server/buildCoderServerPromptResponse.ts) should never be published outside of `@promptbook/cli`
72255
+
72256
+ /**
72257
+ * Builds a plain JSON-safe snapshot from the shared terminal UI state.
72258
+ */
72259
+ function buildCoderServerRunState(uiState) {
72260
+ return {
72261
+ config: uiState.config,
72262
+ phase: uiState.phase,
72263
+ currentPromptLabel: uiState.currentPromptLabel,
72264
+ currentAttempt: uiState.currentAttempt,
72265
+ maxAttempts: uiState.maxAttempts,
72266
+ statusMessage: uiState.statusMessage,
72267
+ detailLines: [...uiState.detailLines],
72268
+ agentStatusLines: [...uiState.agentStatusLines],
72269
+ agentStatusTableRows: [...uiState.agentStatusTableRows],
72270
+ agentOutputLines: [...uiState.agentOutputLines],
72271
+ errors: [...uiState.errors],
72272
+ progress: uiState.getProgress(),
72273
+ };
72274
+ }
72275
+ // Note: [🟡] Code for CLI command [coder server](scripts/run-codex-prompts/server/buildCoderServerRunState.ts) should never be published outside of `@promptbook/cli`
72276
+
71914
72277
  /**
71915
72278
  * Overwrites the body of one prompt section with new content, preserving the status line.
71916
72279
  *
@@ -71923,9 +72286,14 @@ async function updatePromptSection(filePath, sectionIndex, newContent) {
71923
72286
  var _a;
71924
72287
  const rawContent = await readFile(filePath, 'utf-8');
71925
72288
  const promptFile = parsePromptFile(filePath, rawContent);
71926
- const section = promptFile.sections.find((s) => s.index === sectionIndex);
72289
+ const section = promptFile.sections.find((sectionCandidate) => sectionCandidate.index === sectionIndex);
71927
72290
  if (!section) {
71928
- throw new Error(`Section ${sectionIndex} not found in file: ${filePath}`);
72291
+ throw new NotFoundError(spaceTrim$1(`
72292
+ Prompt section \`${sectionIndex}\` was not found.
72293
+
72294
+ File:
72295
+ \`${filePath}\`
72296
+ `));
71929
72297
  }
71930
72298
  // Lines before the status line (usually empty lines at section start)
71931
72299
  const linesBeforeStatus = section.statusLineIndex !== undefined
@@ -71952,7 +72320,11 @@ async function updatePromptSection(filePath, sectionIndex, newContent) {
71952
72320
  ...promptFile.lines.slice(section.endLine + 1),
71953
72321
  ];
71954
72322
  const updatedContent = updatedLines.join(promptFile.eol) + (promptFile.hasFinalEol ? promptFile.eol : '');
72323
+ if (updatedContent === rawContent) {
72324
+ return false;
72325
+ }
71955
72326
  await writeFile(filePath, updatedContent, 'utf-8');
72327
+ return true;
71956
72328
  }
71957
72329
  // Note: [🟡] Code for CLI command [coder server](scripts/run-codex-prompts/server/updatePromptSection.ts) should never be published outside of `@promptbook/cli`
71958
72330
 
@@ -71973,38 +72345,37 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
71973
72345
  <style>
71974
72346
  * { box-sizing: border-box; margin: 0; padding: 0; }
71975
72347
  body {
71976
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
71977
- background: #f4f5f7;
71978
- color: #172b4d;
72348
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
72349
+ background: #f6f7f9;
72350
+ color: #1f2933;
71979
72351
  min-height: 100vh;
71980
72352
  }
71981
72353
 
71982
72354
  header {
71983
- background: #0052cc;
72355
+ background: #22313f;
71984
72356
  color: white;
71985
- padding: 12px 20px;
72357
+ padding: 12px 18px;
71986
72358
  display: flex;
71987
72359
  align-items: center;
71988
72360
  gap: 12px;
71989
72361
  position: sticky;
71990
72362
  top: 0;
71991
72363
  z-index: 10;
71992
- box-shadow: 0 2px 8px rgba(0,0,0,0.2);
72364
+ box-shadow: 0 2px 10px rgba(15,23,42,0.22);
71993
72365
  }
71994
- header h1 { font-size: 17px; font-weight: 700; flex-shrink: 0; }
72366
+ header h1 { font-size: 17px; font-weight: 700; flex-shrink: 0; letter-spacing: 0; }
71995
72367
 
71996
72368
  .status-badge {
71997
72369
  padding: 3px 10px;
71998
- border-radius: 12px;
72370
+ border-radius: 999px;
71999
72371
  font-size: 11px;
72000
72372
  font-weight: 700;
72001
- letter-spacing: 0.5px;
72002
72373
  text-transform: uppercase;
72003
72374
  flex-shrink: 0;
72004
72375
  }
72005
- .status-RUNNING { background: #00875a; color: white; }
72006
- .status-PAUSING { background: #ff8b00; color: white; }
72007
- .status-PAUSED { background: #bf2600; color: white; }
72376
+ .status-RUNNING { background: #0b875b; color: white; }
72377
+ .status-PAUSING { background: #d97904; color: white; }
72378
+ .status-PAUSED { background: #b42318; color: white; }
72008
72379
 
72009
72380
  #pause-label {
72010
72381
  font-size: 12px;
@@ -72017,57 +72388,122 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
72017
72388
  }
72018
72389
 
72019
72390
  .btn {
72020
- padding: 6px 14px;
72021
- border: none;
72391
+ padding: 6px 12px;
72392
+ border: 0;
72022
72393
  border-radius: 4px;
72023
72394
  cursor: pointer;
72024
72395
  font-size: 13px;
72025
- font-weight: 600;
72026
- transition: opacity 0.15s;
72396
+ font-weight: 700;
72397
+ transition: background 0.15s, opacity 0.15s;
72027
72398
  flex-shrink: 0;
72028
72399
  }
72029
- .btn:hover { opacity: 0.85; }
72030
- .btn-pause { background: #ffc400; color: #172b4d; }
72031
- .btn-resume { background: #00875a; color: white; }
72400
+ .btn:disabled { opacity: 0.55; cursor: default; }
72401
+ .btn-pause { background: #ffd166; color: #1f2933; }
72402
+ .btn-resume { background: #0b875b; color: white; }
72403
+ .btn-save { background: #2563eb; color: white; }
72404
+ .btn-cancel { background: #edf0f4; color: #1f2933; }
72405
+
72406
+ .run-strip {
72407
+ background: white;
72408
+ border-bottom: 1px solid #d8dee8;
72409
+ padding: 12px 18px;
72410
+ display: grid;
72411
+ grid-template-columns: minmax(220px, 1.2fr) minmax(240px, 1fr) minmax(260px, 1.4fr);
72412
+ gap: 14px;
72413
+ align-items: center;
72414
+ }
72415
+ .run-title {
72416
+ font-size: 14px;
72417
+ font-weight: 700;
72418
+ overflow: hidden;
72419
+ text-overflow: ellipsis;
72420
+ white-space: nowrap;
72421
+ }
72422
+ .run-subtitle,
72423
+ .run-current,
72424
+ .run-output {
72425
+ color: #64748b;
72426
+ font-size: 12px;
72427
+ line-height: 1.45;
72428
+ overflow: hidden;
72429
+ }
72430
+ .run-current,
72431
+ .run-output {
72432
+ display: -webkit-box;
72433
+ -webkit-box-orient: vertical;
72434
+ -webkit-line-clamp: 2;
72435
+ }
72436
+ .progress-shell {
72437
+ display: grid;
72438
+ grid-template-columns: 1fr auto;
72439
+ gap: 8px;
72440
+ align-items: center;
72441
+ }
72442
+ .progress-track {
72443
+ height: 10px;
72444
+ background: #e4e9f0;
72445
+ border-radius: 999px;
72446
+ overflow: hidden;
72447
+ }
72448
+ .progress-fill {
72449
+ display: block;
72450
+ height: 100%;
72451
+ width: 0;
72452
+ background: linear-gradient(90deg, #0b875b, #2563eb);
72453
+ transition: width 0.2s;
72454
+ }
72455
+ .progress-label {
72456
+ color: #475569;
72457
+ font-size: 12px;
72458
+ font-weight: 700;
72459
+ min-width: 72px;
72460
+ text-align: right;
72461
+ }
72032
72462
 
72033
72463
  .board {
72034
72464
  display: flex;
72035
72465
  gap: 14px;
72036
72466
  padding: 16px;
72037
72467
  overflow-x: auto;
72038
- min-height: calc(100vh - 52px);
72468
+ min-height: calc(100vh - 119px);
72039
72469
  align-items: flex-start;
72040
72470
  }
72041
72471
 
72042
72472
  .column {
72043
- background: #ebecf0;
72044
- border-radius: 8px;
72473
+ background: #e9edf3;
72474
+ border-top: 4px solid #94a3b8;
72475
+ border-radius: 6px;
72045
72476
  padding: 10px;
72046
72477
  min-width: 250px;
72047
72478
  width: 270px;
72048
72479
  flex-shrink: 0;
72049
72480
  }
72481
+ .column-backlog { border-top-color: #64748b; }
72482
+ .column-low-priority { border-top-color: #d97904; }
72483
+ .column-todo { border-top-color: #2563eb; }
72484
+ .column-in-progress { border-top-color: #7c3aed; }
72485
+ .column-done { border-top-color: #0b875b; }
72486
+ .column-errors { border-top-color: #b42318; }
72487
+ .column-finished { border-top-color: #0891b2; }
72050
72488
 
72051
72489
  .column-header {
72052
72490
  font-size: 12px;
72053
- font-weight: 700;
72491
+ font-weight: 800;
72054
72492
  text-transform: uppercase;
72055
- letter-spacing: 0.6px;
72056
- color: #5e6c84;
72493
+ color: #475569;
72057
72494
  margin-bottom: 10px;
72058
72495
  display: flex;
72059
72496
  align-items: center;
72060
72497
  justify-content: space-between;
72498
+ gap: 8px;
72061
72499
  }
72062
-
72063
72500
  .column-count {
72064
- background: #dfe1e6;
72065
- border-radius: 10px;
72066
- padding: 1px 7px;
72501
+ background: rgba(255,255,255,0.72);
72502
+ border-radius: 999px;
72503
+ padding: 2px 8px;
72067
72504
  font-size: 11px;
72068
- color: #5e6c84;
72505
+ color: #475569;
72069
72506
  }
72070
-
72071
72507
  .column-cards { min-height: 40px; }
72072
72508
 
72073
72509
  .card {
@@ -72075,57 +72511,61 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
72075
72511
  border-radius: 6px;
72076
72512
  padding: 10px 12px;
72077
72513
  margin-bottom: 8px;
72078
- box-shadow: 0 1px 3px rgba(0,0,0,0.08);
72514
+ box-shadow: 0 1px 3px rgba(15,23,42,0.12);
72079
72515
  cursor: pointer;
72080
- transition: box-shadow 0.15s, transform 0.1s;
72081
- border-left: 3px solid transparent;
72516
+ border-left: 3px solid #94a3b8;
72082
72517
  }
72083
- .card:hover {
72084
- box-shadow: 0 4px 10px rgba(0,0,0,0.14);
72085
- transform: translateY(-1px);
72086
- }
72087
- .card-todo { border-left-color: #0052cc; }
72088
- .card-not-ready { border-left-color: #8993a4; }
72089
- .card-done { border-left-color: #00875a; }
72090
- .card-failed { border-left-color: #bf2600; }
72518
+ .card:hover { box-shadow: 0 4px 12px rgba(15,23,42,0.16); }
72519
+ .card-backlog { border-left-color: #64748b; }
72520
+ .card-low-priority { border-left-color: #d97904; }
72521
+ .card-todo { border-left-color: #2563eb; }
72522
+ .card-in-progress { border-left-color: #7c3aed; }
72523
+ .card-done { border-left-color: #0b875b; }
72524
+ .card-errors { border-left-color: #b42318; }
72525
+ .card-finished { border-left-color: #0891b2; }
72091
72526
 
72092
72527
  .card-file {
72093
72528
  font-size: 10px;
72094
- color: #8993a4;
72095
- margin-bottom: 5px;
72096
- font-weight: 500;
72529
+ color: #7b8794;
72530
+ margin-bottom: 6px;
72531
+ font-weight: 600;
72532
+ overflow: hidden;
72533
+ text-overflow: ellipsis;
72534
+ white-space: nowrap;
72097
72535
  }
72098
-
72099
72536
  .card-summary {
72100
72537
  font-size: 13px;
72101
- line-height: 1.5;
72102
- color: #172b4d;
72538
+ line-height: 1.45;
72539
+ color: #1f2933;
72103
72540
  word-break: break-word;
72104
72541
  white-space: pre-wrap;
72105
- max-height: 78px;
72542
+ max-height: 76px;
72106
72543
  overflow: hidden;
72107
72544
  display: -webkit-box;
72108
72545
  -webkit-line-clamp: 4;
72109
72546
  -webkit-box-orient: vertical;
72110
72547
  }
72111
-
72112
- .card-priority {
72113
- margin-top: 6px;
72114
- color: #ff8b00;
72115
- font-size: 11px;
72116
- font-weight: 700;
72548
+ .card-tags {
72549
+ display: flex;
72550
+ flex-wrap: wrap;
72551
+ gap: 5px;
72552
+ margin-top: 8px;
72117
72553
  }
72118
-
72119
- .card-edit-hint {
72554
+ .card-tag {
72555
+ border-radius: 999px;
72556
+ padding: 2px 7px;
72120
72557
  font-size: 10px;
72121
- color: #c1c7d0;
72122
- margin-top: 6px;
72123
- display: none;
72558
+ font-weight: 800;
72559
+ line-height: 1.35;
72124
72560
  }
72125
- .card:hover .card-edit-hint { display: block; }
72561
+ .tag-not-ready { background: #e2e8f0; color: #475569; }
72562
+ .tag-unwritten { background: #fff4cc; color: #8a5a00; }
72563
+ .tag-implementing { background: #ede9fe; color: #5b21b6; }
72564
+ .tag-verifying { background: #fae8ff; color: #86198f; }
72565
+ .tag-priority { background: #ffedd5; color: #9a3412; }
72126
72566
 
72127
72567
  .empty-column {
72128
- color: #b3bac5;
72568
+ color: #94a3b8;
72129
72569
  font-size: 12px;
72130
72570
  padding: 10px 4px;
72131
72571
  text-align: center;
@@ -72134,29 +72574,23 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
72134
72574
  .modal-overlay {
72135
72575
  position: fixed;
72136
72576
  inset: 0;
72137
- background: rgba(9,30,66,0.54);
72577
+ background: rgba(15,23,42,0.58);
72138
72578
  display: flex;
72139
72579
  align-items: flex-start;
72140
72580
  justify-content: center;
72141
- padding: 60px 16px 16px;
72581
+ padding: 56px 16px 16px;
72142
72582
  z-index: 100;
72143
- animation: fadeIn 0.1s ease;
72144
72583
  }
72145
72584
  .modal-overlay.hidden { display: none; }
72146
72585
 
72147
- @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
72148
-
72149
72586
  .modal {
72150
72587
  background: white;
72151
72588
  border-radius: 8px;
72152
72589
  width: 100%;
72153
- max-width: 620px;
72154
- padding: 20px;
72155
- box-shadow: 0 8px 32px rgba(0,0,0,0.25);
72156
- animation: slideIn 0.15s ease;
72590
+ max-width: 720px;
72591
+ padding: 18px;
72592
+ box-shadow: 0 14px 40px rgba(15,23,42,0.28);
72157
72593
  }
72158
- @keyframes slideIn { from { transform: translateY(-8px); opacity: 0; } to { transform: none; opacity: 1; } }
72159
-
72160
72594
  .modal-header {
72161
72595
  display: flex;
72162
72596
  align-items: flex-start;
@@ -72164,62 +72598,59 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
72164
72598
  margin-bottom: 14px;
72165
72599
  gap: 12px;
72166
72600
  }
72167
-
72168
72601
  .modal-meta { flex: 1; min-width: 0; }
72169
- .modal-file { font-size: 11px; color: #8993a4; margin-bottom: 4px; }
72170
- .modal-section-label { font-size: 13px; font-weight: 600; color: #172b4d; }
72171
-
72602
+ .modal-file {
72603
+ font-size: 11px;
72604
+ color: #7b8794;
72605
+ margin-bottom: 4px;
72606
+ overflow: hidden;
72607
+ text-overflow: ellipsis;
72608
+ white-space: nowrap;
72609
+ }
72610
+ .modal-section-label { font-size: 14px; font-weight: 700; color: #1f2933; }
72172
72611
  .modal-status {
72173
72612
  font-size: 11px;
72174
- font-weight: 700;
72175
- padding: 2px 8px;
72176
- border-radius: 10px;
72613
+ font-weight: 800;
72614
+ padding: 3px 8px;
72615
+ border-radius: 999px;
72177
72616
  text-transform: uppercase;
72178
- letter-spacing: 0.4px;
72179
72617
  flex-shrink: 0;
72180
72618
  }
72181
- .status-todo { background: #deebff; color: #0052cc; }
72182
- .status-not-ready { background: #f4f5f7; color: #5e6c84; }
72183
- .status-done { background: #e3fcef; color: #006644; }
72184
- .status-failed { background: #ffebe6; color: #bf2600; }
72185
-
72619
+ .status-backlog { background: #e2e8f0; color: #475569; }
72620
+ .status-low-priority { background: #ffedd5; color: #9a3412; }
72621
+ .status-todo { background: #dbeafe; color: #1d4ed8; }
72622
+ .status-in-progress { background: #ede9fe; color: #5b21b6; }
72623
+ .status-done { background: #dcfce7; color: #166534; }
72624
+ .status-errors { background: #fee2e2; color: #991b1b; }
72625
+ .status-finished { background: #cffafe; color: #155e75; }
72186
72626
  .modal-close {
72187
- background: none;
72188
- border: none;
72189
- color: #8993a4;
72627
+ background: transparent;
72628
+ border: 0;
72629
+ color: #64748b;
72190
72630
  cursor: pointer;
72191
- font-size: 18px;
72631
+ font-size: 20px;
72192
72632
  padding: 0 4px;
72193
72633
  line-height: 1;
72194
- flex-shrink: 0;
72195
72634
  }
72196
- .modal-close:hover { color: #172b4d; }
72635
+ .modal-close:hover { color: #1f2933; }
72197
72636
 
72198
72637
  textarea {
72199
72638
  width: 100%;
72200
- min-height: 220px;
72201
- font-family: 'SFMono-Regular', 'Consolas', 'Courier New', monospace;
72639
+ min-height: 300px;
72640
+ font-family: "SFMono-Regular", Consolas, "Courier New", monospace;
72202
72641
  font-size: 13px;
72203
- border: 2px solid #dfe1e6;
72642
+ border: 2px solid #d8dee8;
72204
72643
  border-radius: 4px;
72205
72644
  padding: 10px 12px;
72206
72645
  resize: vertical;
72207
- line-height: 1.65;
72208
- color: #172b4d;
72209
- transition: border-color 0.15s;
72646
+ line-height: 1.6;
72647
+ color: #1f2933;
72210
72648
  }
72211
72649
  textarea:focus {
72212
72650
  outline: none;
72213
- border-color: #0052cc;
72214
- box-shadow: 0 0 0 3px rgba(0,82,204,0.12);
72215
- }
72216
-
72217
- .modal-hint {
72218
- font-size: 11px;
72219
- color: #8993a4;
72220
- margin-top: 6px;
72651
+ border-color: #2563eb;
72652
+ box-shadow: 0 0 0 3px rgba(37,99,235,0.12);
72221
72653
  }
72222
-
72223
72654
  .modal-actions {
72224
72655
  display: flex;
72225
72656
  gap: 8px;
@@ -72227,67 +72658,50 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
72227
72658
  justify-content: flex-end;
72228
72659
  }
72229
72660
 
72230
- .btn-save { background: #0052cc; color: white; }
72231
- .btn-cancel { background: #f4f5f7; color: #172b4d; }
72232
- .btn-cancel:hover { background: #ebecf0; opacity: 1; }
72233
-
72234
72661
  .error-banner {
72235
- background: #ffebe6;
72236
- border-bottom: 2px solid #bf2600;
72237
- color: #bf2600;
72238
- padding: 8px 20px;
72662
+ background: #fee2e2;
72663
+ border-bottom: 2px solid #b42318;
72664
+ color: #991b1b;
72665
+ padding: 8px 18px;
72239
72666
  font-size: 13px;
72240
- font-weight: 500;
72667
+ font-weight: 700;
72241
72668
  }
72242
72669
  .error-banner.hidden { display: none; }
72670
+
72671
+ @media (max-width: 860px) {
72672
+ header { flex-wrap: wrap; }
72673
+ #pause-label { order: 4; flex-basis: 100%; }
72674
+ .run-strip { grid-template-columns: 1fr; }
72675
+ .board { min-height: calc(100vh - 212px); }
72676
+ }
72243
72677
  </style>
72244
72678
  </head>
72245
72679
  <body>
72246
-
72247
72680
  <div id="error-banner" class="error-banner hidden"></div>
72248
72681
 
72249
72682
  <header>
72250
- <h1>&#128295; Ptbk Coder Server</h1>
72683
+ <h1>Ptbk Coder Server</h1>
72251
72684
  <span id="status-badge" class="status-badge status-RUNNING">RUNNING</span>
72252
72685
  <span id="pause-label"></span>
72253
- <button id="toggle-btn" class="btn btn-pause">&#9646;&#9646; Pause</button>
72686
+ <button id="toggle-btn" class="btn btn-pause">Pause</button>
72254
72687
  </header>
72255
72688
 
72256
- <div class="board">
72257
- <div class="column">
72258
- <div class="column-header">
72259
- To Do
72260
- <span class="column-count" id="count-todo">0</span>
72261
- </div>
72262
- <div class="column-cards" id="cards-todo">
72263
- <div class="empty-column">Loading&hellip;</div>
72264
- </div>
72689
+ <section class="run-strip">
72690
+ <div>
72691
+ <div class="run-title" id="run-title">Initializing...</div>
72692
+ <div class="run-subtitle" id="run-subtitle">Loading runner state</div>
72265
72693
  </div>
72266
-
72267
- <div class="column">
72268
- <div class="column-header">
72269
- Not Ready
72270
- <span class="column-count" id="count-not-ready">0</span>
72271
- </div>
72272
- <div class="column-cards" id="cards-not-ready"></div>
72694
+ <div class="progress-shell">
72695
+ <div class="progress-track"><span class="progress-fill" id="progress-fill"></span></div>
72696
+ <div class="progress-label" id="progress-label">0%</div>
72273
72697
  </div>
72274
-
72275
- <div class="column">
72276
- <div class="column-header">
72277
- Done
72278
- <span class="column-count" id="count-done">0</span>
72279
- </div>
72280
- <div class="column-cards" id="cards-done"></div>
72698
+ <div>
72699
+ <div class="run-current" id="run-current">No active prompt</div>
72700
+ <div class="run-output" id="run-output"></div>
72281
72701
  </div>
72702
+ </section>
72282
72703
 
72283
- <div class="column">
72284
- <div class="column-header">
72285
- Failed
72286
- <span class="column-count" id="count-failed">0</span>
72287
- </div>
72288
- <div class="column-cards" id="cards-failed"></div>
72289
- </div>
72290
- </div>
72704
+ <div class="board" id="board"></div>
72291
72705
 
72292
72706
  <div class="modal-overlay hidden" id="modal-overlay">
72293
72707
  <div class="modal">
@@ -72297,196 +72711,317 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
72297
72711
  <div class="modal-section-label" id="modal-section-label"></div>
72298
72712
  </div>
72299
72713
  <span class="modal-status" id="modal-status-badge"></span>
72300
- <button class="modal-close" onclick="closeModal()" title="Close (Esc)">&#x2715;</button>
72714
+ <button class="modal-close" id="modal-close" title="Close">&times;</button>
72301
72715
  </div>
72302
72716
 
72303
- <textarea id="modal-content" placeholder="Prompt content&hellip;"></textarea>
72304
- <div class="modal-hint">Tip: press Ctrl+Enter to save, Esc to cancel.</div>
72717
+ <textarea id="modal-content" placeholder="Prompt content"></textarea>
72305
72718
 
72306
72719
  <div class="modal-actions">
72307
- <button class="btn btn-cancel" onclick="closeModal()">Cancel</button>
72308
- <button class="btn btn-save" onclick="saveModal()">Save</button>
72720
+ <button class="btn btn-cancel" id="cancel-button">Cancel</button>
72721
+ <button class="btn btn-save" id="save-button">Save</button>
72309
72722
  </div>
72310
72723
  </div>
72311
72724
  </div>
72312
72725
 
72313
72726
  <script>
72314
- 'use strict';
72727
+ "use strict";
72728
+
72729
+ const BOARD_COLUMNS = [
72730
+ { id: "backlog", title: "Backlog" },
72731
+ { id: "low-priority", title: "Low priority" },
72732
+ { id: "todo", title: "To do" },
72733
+ { id: "in-progress", title: "In progress" },
72734
+ { id: "done", title: "Done" },
72735
+ { id: "errors", title: "Errors" },
72736
+ { id: "finished", title: "Finished" },
72737
+ ];
72315
72738
 
72316
72739
  let modalState = null;
72317
- let lastPauseState = 'RUNNING';
72318
-
72319
- function showError(msg) {
72320
- const banner = document.getElementById('error-banner');
72321
- banner.textContent = '\\u26a0 ' + msg;
72322
- banner.classList.remove('hidden');
72323
- clearTimeout(banner._timer);
72324
- banner._timer = setTimeout(() => banner.classList.add('hidden'), 6000);
72325
- }
72326
-
72327
- function escapeHtml(str) {
72328
- const d = document.createElement('div');
72329
- d.textContent = String(str);
72330
- return d.innerHTML;
72740
+ let lastPauseState = "RUNNING";
72741
+
72742
+ function getColumnTitle(columnId) {
72743
+ const column = BOARD_COLUMNS.find((columnCandidate) => columnCandidate.id === columnId);
72744
+ return column ? column.title : columnId;
72745
+ }
72746
+
72747
+ function showError(message) {
72748
+ const banner = document.getElementById("error-banner");
72749
+ banner.textContent = message;
72750
+ banner.classList.remove("hidden");
72751
+ clearTimeout(banner.timer);
72752
+ banner.timer = setTimeout(() => banner.classList.add("hidden"), 6000);
72753
+ }
72754
+
72755
+ function escapeHtml(value) {
72756
+ const element = document.createElement("div");
72757
+ element.textContent = String(value);
72758
+ return element.innerHTML;
72759
+ }
72760
+
72761
+ function renderBoardSkeleton() {
72762
+ const board = document.getElementById("board");
72763
+ board.innerHTML = "";
72764
+
72765
+ for (const column of BOARD_COLUMNS) {
72766
+ const columnElement = document.createElement("section");
72767
+ columnElement.className = "column column-" + column.id;
72768
+ columnElement.innerHTML =
72769
+ '<div class="column-header">' +
72770
+ '<span>' + escapeHtml(column.title) + '</span>' +
72771
+ '<span class="column-count" id="count-' + column.id + '">0</span>' +
72772
+ '</div>' +
72773
+ '<div class="column-cards" id="cards-' + column.id + '">' +
72774
+ '<div class="empty-column">Loading</div>' +
72775
+ '</div>';
72776
+ board.appendChild(columnElement);
72777
+ }
72331
72778
  }
72332
72779
 
72333
72780
  async function fetchStatus() {
72334
72781
  try {
72335
- const res = await fetch('/api/status');
72336
- if (!res.ok) { showError('Status API error: ' + res.status); return; }
72337
- const data = await res.json();
72338
-
72339
- lastPauseState = data.pauseState;
72340
-
72341
- const badge = document.getElementById('status-badge');
72342
- badge.textContent = data.pauseState;
72343
- badge.className = 'status-badge status-' + data.pauseState;
72344
-
72345
- const btn = document.getElementById('toggle-btn');
72346
- const label = document.getElementById('pause-label');
72347
-
72348
- if (data.pauseState === 'RUNNING') {
72349
- btn.textContent = '\\u23f8 Pause';
72350
- btn.className = 'btn btn-pause';
72351
- label.textContent = '';
72352
- } else if (data.pauseState === 'PAUSING') {
72353
- btn.textContent = '\\u23f5 Resume';
72354
- btn.className = 'btn btn-resume';
72355
- label.textContent = 'Pausing before: ' + (data.pauseTargetLabel || '\\u2026');
72356
- } else {
72357
- btn.textContent = '\\u23f5 Resume';
72358
- btn.className = 'btn btn-resume';
72359
- label.textContent = 'Paused before: ' + (data.pauseTargetLabel || '\\u2026');
72782
+ const response = await fetch("/api/status");
72783
+ if (!response.ok) {
72784
+ showError("Status API error: " + response.status);
72785
+ return;
72360
72786
  }
72361
- } catch (e) {
72362
- showError('Could not reach coder server: ' + e.message);
72787
+
72788
+ const status = await response.json();
72789
+ renderPauseState(status);
72790
+ renderRunState(status.runState);
72791
+ } catch (error) {
72792
+ showError("Could not reach coder server: " + error.message);
72793
+ }
72794
+ }
72795
+
72796
+ function renderPauseState(status) {
72797
+ lastPauseState = status.pauseState;
72798
+
72799
+ const badge = document.getElementById("status-badge");
72800
+ badge.textContent = status.pauseState;
72801
+ badge.className = "status-badge status-" + status.pauseState;
72802
+
72803
+ const toggleButton = document.getElementById("toggle-btn");
72804
+ const pauseLabel = document.getElementById("pause-label");
72805
+
72806
+ if (status.pauseState === "RUNNING") {
72807
+ toggleButton.textContent = "Pause";
72808
+ toggleButton.className = "btn btn-pause";
72809
+ pauseLabel.textContent = "";
72810
+ return;
72811
+ }
72812
+
72813
+ toggleButton.textContent = "Resume";
72814
+ toggleButton.className = "btn btn-resume";
72815
+ pauseLabel.textContent =
72816
+ (status.pauseState === "PAUSING" ? "Pausing before: " : "Paused before: ") +
72817
+ (status.pauseTargetLabel || "next checkpoint");
72818
+ }
72819
+
72820
+ function renderRunState(runState) {
72821
+ if (!runState) {
72822
+ document.getElementById("run-title").textContent = "Waiting for runner state";
72823
+ document.getElementById("run-subtitle").textContent = "";
72824
+ return;
72363
72825
  }
72826
+
72827
+ const progress = runState.progress || {};
72828
+ const percentage = Number(progress.percentage || 0);
72829
+ const runnerParts = [runState.config.agentName, runState.config.modelName, runState.config.thinkingLevel]
72830
+ .filter(Boolean);
72831
+
72832
+ document.getElementById("run-title").textContent = runState.statusMessage || runState.phase;
72833
+ document.getElementById("run-subtitle").textContent = runnerParts.join(" / ");
72834
+ document.getElementById("progress-fill").style.width = Math.max(0, Math.min(100, percentage)) + "%";
72835
+ document.getElementById("progress-label").textContent =
72836
+ percentage + "% " + (progress.sessionDone || 0) + "/" + (progress.sessionTotal || 0);
72837
+ document.getElementById("run-current").textContent = runState.currentPromptLabel
72838
+ ? runState.currentPromptLabel + " - attempt " + runState.currentAttempt + "/" + runState.maxAttempts
72839
+ : "No active prompt";
72840
+
72841
+ renderRunOutput(runState);
72842
+ }
72843
+
72844
+ function renderRunOutput(runState) {
72845
+ const statusLines = (runState.agentStatusTableRows || []).map((row) =>
72846
+ row.status + " - " + row.agentName + (row.url ? " - " + row.url : "")
72847
+ );
72848
+ const outputLines = [
72849
+ ...(runState.agentStatusLines || []),
72850
+ ...statusLines,
72851
+ ...(runState.agentOutputLines || []),
72852
+ ...(runState.errors || []).map((errorLine) => "Error: " + errorLine),
72853
+ ].slice(-3);
72854
+
72855
+ document.getElementById("run-output").textContent = outputLines.join("\\n");
72364
72856
  }
72365
72857
 
72366
72858
  async function fetchPrompts() {
72367
72859
  try {
72368
- const res = await fetch('/api/prompts');
72369
- if (!res.ok) { showError('Prompts API error: ' + res.status); return; }
72370
- renderBoard(await res.json());
72371
- } catch (e) {
72372
- showError('Could not load prompts: ' + e.message);
72860
+ const response = await fetch("/api/prompts");
72861
+ if (!response.ok) {
72862
+ showError("Prompts API error: " + response.status);
72863
+ return;
72864
+ }
72865
+
72866
+ renderBoard(await response.json());
72867
+ } catch (error) {
72868
+ showError("Could not load prompts: " + error.message);
72373
72869
  }
72374
72870
  }
72375
72871
 
72376
72872
  function renderBoard(promptFiles) {
72377
- const columns = { 'todo': [], 'not-ready': [], 'done': [], 'failed': [] };
72873
+ const columns = Object.fromEntries(BOARD_COLUMNS.map((column) => [column.id, []]));
72378
72874
 
72379
72875
  for (const file of promptFiles) {
72380
72876
  for (const section of file.sections) {
72381
- const col = columns[section.status];
72382
- if (col) col.push({ file, section });
72877
+ if (columns[section.column]) {
72878
+ columns[section.column].push({ file, section });
72879
+ }
72383
72880
  }
72384
72881
  }
72385
72882
 
72386
- for (const [status, cards] of Object.entries(columns)) {
72387
- const container = document.getElementById('cards-' + status);
72388
- const countEl = document.getElementById('count-' + status);
72389
- if (!container) continue;
72883
+ for (const column of BOARD_COLUMNS) {
72884
+ renderColumn(column.id, columns[column.id]);
72885
+ }
72886
+ }
72390
72887
 
72391
- countEl.textContent = cards.length;
72392
- container.innerHTML = '';
72888
+ function renderColumn(columnId, cards) {
72889
+ const container = document.getElementById("cards-" + columnId);
72890
+ const countElement = document.getElementById("count-" + columnId);
72393
72891
 
72394
- if (cards.length === 0) {
72395
- container.innerHTML = '<div class="empty-column">Empty</div>';
72396
- continue;
72397
- }
72892
+ countElement.textContent = cards.length;
72893
+ container.innerHTML = "";
72398
72894
 
72399
- for (const { file, section } of cards) {
72400
- const card = document.createElement('div');
72401
- card.className = 'card card-' + section.status;
72402
- card.innerHTML =
72403
- '<div class="card-file">' + escapeHtml(file.fileName) + ' &bull; #' + (section.index + 1) + '</div>' +
72404
- '<div class="card-summary">' + escapeHtml(section.summary) + '</div>' +
72405
- (section.priority > 0
72406
- ? '<div class="card-priority">' + '!'.repeat(section.priority) + ' priority ' + section.priority + '</div>'
72407
- : '') +
72408
- '<div class="card-edit-hint">Click to edit</div>';
72409
- card.onclick = () => openModal(file, section);
72410
- container.appendChild(card);
72411
- }
72895
+ if (cards.length === 0) {
72896
+ container.innerHTML = '<div class="empty-column">Empty</div>';
72897
+ return;
72898
+ }
72899
+
72900
+ for (const cardData of cards) {
72901
+ container.appendChild(createPromptCard(cardData.file, cardData.section));
72412
72902
  }
72413
72903
  }
72414
72904
 
72905
+ function createPromptCard(file, section) {
72906
+ const card = document.createElement("article");
72907
+ card.className = "card card-" + section.column;
72908
+ card.innerHTML =
72909
+ '<div class="card-file">' + escapeHtml(file.relativeFilePath || file.fileName) + " #" + (section.index + 1) + '</div>' +
72910
+ '<div class="card-summary">' + escapeHtml(section.summary) + '</div>' +
72911
+ renderTags(section);
72912
+ card.onclick = () => openModal(file, section);
72913
+ return card;
72914
+ }
72915
+
72916
+ function renderTags(section) {
72917
+ const tags = [...(section.tags || [])];
72918
+ if (section.priority > 0) {
72919
+ tags.push({ id: "priority", label: "P" + section.priority });
72920
+ }
72921
+
72922
+ if (tags.length === 0) {
72923
+ return "";
72924
+ }
72925
+
72926
+ return '<div class="card-tags">' + tags.map((tag) =>
72927
+ '<span class="card-tag tag-' + escapeHtml(tag.id) + '">' + escapeHtml(tag.label) + '</span>'
72928
+ ).join("") + '</div>';
72929
+ }
72930
+
72415
72931
  function openModal(file, section) {
72416
72932
  modalState = { filePath: file.filePath, sectionIndex: section.index };
72417
72933
 
72418
- document.getElementById('modal-file').textContent = file.fileName;
72419
- document.getElementById('modal-section-label').textContent = 'Section ' + (section.index + 1);
72934
+ document.getElementById("modal-file").textContent = file.relativeFilePath || file.fileName;
72935
+ document.getElementById("modal-section-label").textContent = "Section " + (section.index + 1);
72420
72936
 
72421
- const badge = document.getElementById('modal-status-badge');
72422
- badge.textContent = section.status.replace('-', '\\u2011');
72423
- badge.className = 'modal-status status-' + section.status;
72937
+ const statusBadge = document.getElementById("modal-status-badge");
72938
+ statusBadge.textContent = getColumnTitle(section.column);
72939
+ statusBadge.className = "modal-status status-" + section.column;
72424
72940
 
72425
- document.getElementById('modal-content').value = section.content;
72426
- document.getElementById('modal-overlay').classList.remove('hidden');
72427
- setTimeout(() => document.getElementById('modal-content').focus(), 50);
72941
+ document.getElementById("modal-content").value = section.content;
72942
+ document.getElementById("modal-overlay").classList.remove("hidden");
72943
+ setTimeout(() => document.getElementById("modal-content").focus(), 50);
72428
72944
  }
72429
72945
 
72430
72946
  function closeModal() {
72431
- document.getElementById('modal-overlay').classList.add('hidden');
72947
+ document.getElementById("modal-overlay").classList.add("hidden");
72432
72948
  modalState = null;
72433
72949
  }
72434
72950
 
72435
72951
  async function saveModal() {
72436
- if (!modalState) return;
72952
+ if (!modalState) {
72953
+ return;
72954
+ }
72437
72955
 
72438
- const content = document.getElementById('modal-content').value;
72439
- const saveBtn = document.querySelector('.btn-save');
72440
- saveBtn.disabled = true;
72441
- saveBtn.textContent = 'Saving\\u2026';
72956
+ const content = document.getElementById("modal-content").value;
72957
+ const saveButton = document.getElementById("save-button");
72958
+ saveButton.disabled = true;
72959
+ saveButton.textContent = "Saving...";
72442
72960
 
72443
72961
  try {
72444
- const res = await fetch('/api/prompts/update', {
72445
- method: 'PUT',
72446
- headers: { 'Content-Type': 'application/json' },
72962
+ const response = await fetch("/api/prompts/update", {
72963
+ method: "PUT",
72964
+ headers: { "Content-Type": "application/json" },
72447
72965
  body: JSON.stringify({
72448
- filePath: modalState.filePath,
72966
+ filePath: modalState.filePath,
72449
72967
  sectionIndex: modalState.sectionIndex,
72450
72968
  content,
72451
72969
  }),
72452
72970
  });
72453
- if (!res.ok) throw new Error('HTTP ' + res.status);
72971
+
72972
+ if (!response.ok) {
72973
+ throw new Error("HTTP " + response.status);
72974
+ }
72975
+
72454
72976
  closeModal();
72455
- fetchPrompts();
72456
- } catch (e) {
72457
- showError('Save failed: ' + e.message);
72977
+ await fetchPrompts();
72978
+ } catch (error) {
72979
+ showError("Save failed: " + error.message);
72458
72980
  } finally {
72459
- saveBtn.disabled = false;
72460
- saveBtn.textContent = 'Save';
72981
+ saveButton.disabled = false;
72982
+ saveButton.textContent = "Save";
72461
72983
  }
72462
72984
  }
72463
72985
 
72464
- document.getElementById('toggle-btn').onclick = async () => {
72986
+ document.getElementById("toggle-btn").onclick = async () => {
72465
72987
  try {
72466
- const endpoint = lastPauseState === 'RUNNING' ? '/api/pause' : '/api/resume';
72467
- await fetch(endpoint, { method: 'POST' });
72988
+ const endpoint = lastPauseState === "RUNNING" ? "/api/pause" : "/api/resume";
72989
+ await fetch(endpoint, { method: "POST" });
72468
72990
  await fetchStatus();
72469
- } catch (e) {
72470
- showError('Toggle failed: ' + e.message);
72991
+ } catch (error) {
72992
+ showError("Toggle failed: " + error.message);
72471
72993
  }
72472
72994
  };
72473
72995
 
72474
- document.addEventListener('keydown', (e) => {
72475
- if (e.key === 'Escape') { closeModal(); return; }
72476
- if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { saveModal(); return; }
72996
+ document.getElementById("modal-close").onclick = closeModal;
72997
+ document.getElementById("cancel-button").onclick = closeModal;
72998
+ document.getElementById("save-button").onclick = saveModal;
72999
+
73000
+ document.addEventListener("keydown", (event) => {
73001
+ if (event.key === "Escape") {
73002
+ closeModal();
73003
+ return;
73004
+ }
73005
+ if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
73006
+ saveModal();
73007
+ }
72477
73008
  });
72478
73009
 
72479
- document.getElementById('modal-overlay').addEventListener('click', (e) => {
72480
- if (e.target === document.getElementById('modal-overlay')) closeModal();
73010
+ document.getElementById("modal-overlay").addEventListener("click", (event) => {
73011
+ if (event.target === document.getElementById("modal-overlay")) {
73012
+ closeModal();
73013
+ }
72481
73014
  });
72482
73015
 
73016
+ renderBoardSkeleton();
72483
73017
  fetchStatus();
72484
73018
  fetchPrompts();
72485
- setInterval(fetchStatus, 2000);
73019
+ setInterval(fetchStatus, 2000);
72486
73020
  setInterval(fetchPrompts, 5000);
72487
73021
  </script>
72488
73022
  </body>
72489
- </html>`;
73023
+ </html>
73024
+ `;
72490
73025
  // Note: [🟡] Code for CLI command [coder server](scripts/run-codex-prompts/server/coderServerHtml.ts) should never be published outside of `@promptbook/cli`
72491
73026
  // Note: Keep in sync with apps/coder-server/index.html
72492
73027
 
@@ -72496,6 +73031,12 @@ const CODER_SERVER_HTML = `<!DOCTYPE html>
72496
73031
  * @private internal constant of `ptbk coder server`
72497
73032
  */
72498
73033
  const PROMPTS_DIRECTORY_NAME = 'prompts';
73034
+ /**
73035
+ * Directory containing human-verified prompt files, relative to `prompts/`.
73036
+ *
73037
+ * @private internal constant of `ptbk coder server`
73038
+ */
73039
+ const FINISHED_PROMPTS_DIRECTORY_NAME = 'done';
72499
73040
  /**
72500
73041
  * Starts the lightweight HTTP server that serves the coder kanban UI and REST API.
72501
73042
  *
@@ -72509,24 +73050,29 @@ const PROMPTS_DIRECTORY_NAME = 'prompts';
72509
73050
  *
72510
73051
  * @private internal utility of `ptbk coder server`
72511
73052
  */
72512
- function startCoderHttpServer(port) {
73053
+ function startCoderHttpServer(options) {
73054
+ const { port, minimumPriority, serverUrl, uiState } = options;
72513
73055
  const promptsDir = join(process.cwd(), PROMPTS_DIRECTORY_NAME);
72514
- const server = createServer(async (req, res) => {
73056
+ const server = createServer(async (request, response) => {
72515
73057
  try {
72516
- await handleRequest(req, res, promptsDir);
73058
+ await handleRequest(request, response, {
73059
+ promptsDir,
73060
+ minimumPriority,
73061
+ uiState,
73062
+ });
72517
73063
  }
72518
73064
  catch (error) {
72519
73065
  const message = error instanceof Error ? error.message : String(error);
72520
73066
  console.error(colors.red(`Coder server request error: ${message}`));
72521
- if (!res.headersSent) {
72522
- res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
73067
+ if (!response.headersSent) {
73068
+ response.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
72523
73069
  }
72524
- res.end('Internal server error');
73070
+ response.end('Internal server error');
72525
73071
  }
72526
73072
  });
72527
73073
  server.listen(port, () => {
72528
73074
  console.info(spaceTrim$1(`
72529
- Coder server running at ${colors.cyan(`http://localhost:${port}`)}
73075
+ Coder server running at ${colors.cyan(serverUrl)}
72530
73076
  Open the URL above in your browser to see the kanban board.
72531
73077
  Press ${colors.bold('p')} to pause / resume the agent runner.
72532
73078
  `));
@@ -72540,51 +73086,57 @@ function startCoderHttpServer(port) {
72540
73086
  /**
72541
73087
  * Routes one HTTP request to the appropriate handler.
72542
73088
  */
72543
- async function handleRequest(req, res, promptsDir) {
72544
- const urlPath = new URL(req.url || '/', `http://localhost`).pathname;
72545
- const method = (req.method || 'GET').toUpperCase();
73089
+ async function handleRequest(request, response, options) {
73090
+ const { promptsDir, minimumPriority, uiState } = options;
73091
+ const urlPath = new URL(request.url || '/', `http://localhost`).pathname;
73092
+ const method = (request.method || 'GET').toUpperCase();
72546
73093
  // Serve the kanban UI
72547
73094
  if (urlPath === '/' && method === 'GET') {
72548
- res.writeHead(200, {
73095
+ response.writeHead(200, {
72549
73096
  'Content-Type': 'text/html; charset=utf-8',
72550
73097
  'Cache-Control': 'no-cache',
72551
73098
  });
72552
- res.end(CODER_SERVER_HTML);
73099
+ response.end(CODER_SERVER_HTML);
72553
73100
  return;
72554
73101
  }
72555
73102
  // GET /api/status
72556
73103
  if (urlPath === '/api/status' && method === 'GET') {
72557
- res.writeHead(200, jsonHeaders());
72558
- res.end(JSON.stringify({
73104
+ response.writeHead(200, jsonHeaders());
73105
+ response.end(JSON.stringify({
72559
73106
  pauseState: getPauseState(),
72560
73107
  pauseTargetLabel: getPauseTargetLabel(),
73108
+ runState: uiState ? buildCoderServerRunState(uiState) : undefined,
72561
73109
  }));
72562
73110
  return;
72563
73111
  }
72564
73112
  // GET /api/prompts
72565
73113
  if (urlPath === '/api/prompts' && method === 'GET') {
72566
- const promptData = await loadPromptsForApi(promptsDir);
72567
- res.writeHead(200, jsonHeaders());
72568
- res.end(JSON.stringify(promptData));
73114
+ const promptData = await loadPromptsForApi({
73115
+ promptsDir,
73116
+ minimumPriority,
73117
+ uiState,
73118
+ });
73119
+ response.writeHead(200, jsonHeaders());
73120
+ response.end(JSON.stringify(promptData));
72569
73121
  return;
72570
73122
  }
72571
73123
  // POST /api/pause
72572
73124
  if (urlPath === '/api/pause' && method === 'POST') {
72573
73125
  requestPause();
72574
- res.writeHead(200, jsonHeaders());
72575
- res.end(JSON.stringify({ pauseState: getPauseState() }));
73126
+ response.writeHead(200, jsonHeaders());
73127
+ response.end(JSON.stringify({ pauseState: getPauseState() }));
72576
73128
  return;
72577
73129
  }
72578
73130
  // POST /api/resume
72579
73131
  if (urlPath === '/api/resume' && method === 'POST') {
72580
73132
  requestResume();
72581
- res.writeHead(200, jsonHeaders());
72582
- res.end(JSON.stringify({ pauseState: getPauseState() }));
73133
+ response.writeHead(200, jsonHeaders());
73134
+ response.end(JSON.stringify({ pauseState: getPauseState() }));
72583
73135
  return;
72584
73136
  }
72585
73137
  // PUT /api/prompts/update
72586
73138
  if (urlPath === '/api/prompts/update' && method === 'PUT') {
72587
- const body = await readRequestBody(req);
73139
+ const body = await readRequestBody(request);
72588
73140
  const parsed = JSON.parse(body);
72589
73141
  if (typeof parsed.filePath !== 'string' ||
72590
73142
  typeof parsed.sectionIndex !== 'number' ||
@@ -72595,49 +73147,85 @@ async function handleRequest(req, res, promptsDir) {
72595
73147
  Expected: \`{ filePath: string, sectionIndex: number, content: string }\`
72596
73148
  `));
72597
73149
  }
72598
- await updatePromptSection(parsed.filePath, parsed.sectionIndex, parsed.content);
72599
- res.writeHead(200, jsonHeaders());
72600
- res.end(JSON.stringify({ success: true }));
73150
+ const promptFilePath = resolveEditablePromptFilePath(parsed.filePath, promptsDir);
73151
+ const isUpdated = await updatePromptSection(promptFilePath, parsed.sectionIndex, parsed.content);
73152
+ if (isUpdated) {
73153
+ await commitPromptEdit(promptFilePath, parsed.sectionIndex);
73154
+ }
73155
+ response.writeHead(200, jsonHeaders());
73156
+ response.end(JSON.stringify({ success: true, committed: isUpdated }));
72601
73157
  return;
72602
73158
  }
72603
- res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
72604
- res.end('Not found');
73159
+ response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
73160
+ response.end('Not found');
72605
73161
  }
72606
73162
  /**
72607
73163
  * Loads all prompt files and converts them to the API response shape.
72608
73164
  */
72609
- async function loadPromptsForApi(promptsDir) {
73165
+ async function loadPromptsForApi(options) {
73166
+ const { promptsDir, minimumPriority, uiState } = options;
73167
+ const promptFiles = await loadPromptFilesSafely(promptsDir);
73168
+ const finishedPromptFiles = await loadPromptFilesSafely(join(promptsDir, FINISHED_PROMPTS_DIRECTORY_NAME));
73169
+ return buildCoderServerPromptFileResponses({
73170
+ promptFiles,
73171
+ finishedPromptFiles,
73172
+ minimumPriority,
73173
+ uiState,
73174
+ });
73175
+ }
73176
+ /**
73177
+ * Loads prompt files from a directory that may not exist yet.
73178
+ */
73179
+ async function loadPromptFilesSafely(promptsDir) {
72610
73180
  try {
72611
- const promptFiles = await loadPromptFiles(promptsDir);
72612
- return promptFiles.map((file) => ({
72613
- filePath: file.path,
72614
- fileName: file.name,
72615
- sections: file.sections.map((section) => ({
72616
- index: section.index,
72617
- status: section.status,
72618
- priority: section.priority,
72619
- summary: buildPromptSummary(file, section),
72620
- content: buildCodexPrompt(file, section),
72621
- })),
72622
- }));
73181
+ return await loadPromptFiles(promptsDir);
72623
73182
  }
72624
73183
  catch (error) {
72625
- // Prompts directory may not exist yet; return empty list
72626
73184
  if (error.code === 'ENOENT') {
72627
73185
  return [];
72628
73186
  }
72629
73187
  throw error;
72630
73188
  }
72631
73189
  }
73190
+ /**
73191
+ * Resolves and validates one editable prompt file path received from the browser.
73192
+ */
73193
+ function resolveEditablePromptFilePath(filePath, promptsDir) {
73194
+ const promptFilePath = resolve(filePath);
73195
+ const promptsDirectoryPath = resolve(promptsDir);
73196
+ const relativePromptFilePath = relative(promptsDirectoryPath, promptFilePath);
73197
+ const isOutsidePromptsDirectory = relativePromptFilePath === '' ||
73198
+ relativePromptFilePath.startsWith('..') ||
73199
+ isAbsolute(relativePromptFilePath);
73200
+ if (isOutsidePromptsDirectory || !promptFilePath.toLowerCase().endsWith('.md')) {
73201
+ throw new NotAllowed(spaceTrim$1(`
73202
+ Prompt file edits are limited to Markdown files inside \`${promptsDirectoryPath}\`.
73203
+
73204
+ Requested file:
73205
+ \`${filePath}\`
73206
+ `));
73207
+ }
73208
+ return promptFilePath;
73209
+ }
73210
+ /**
73211
+ * Commits a browser prompt edit to Git without sweeping unrelated staged files into the commit.
73212
+ */
73213
+ async function commitPromptEdit(promptFilePath, sectionIndex) {
73214
+ const relativePromptFilePath = relative(process.cwd(), promptFilePath).replace(/\\/gu, '/');
73215
+ await commitChanges(`Edit coder prompt ${relativePromptFilePath}#${sectionIndex + 1}`, {
73216
+ includePaths: [relativePromptFilePath],
73217
+ onlyPaths: [relativePromptFilePath],
73218
+ });
73219
+ }
72632
73220
  /**
72633
73221
  * Reads the full request body as a UTF-8 string.
72634
73222
  */
72635
- function readRequestBody(req) {
73223
+ function readRequestBody(request) {
72636
73224
  return new Promise((resolve, reject) => {
72637
73225
  const chunks = [];
72638
- req.on('data', (chunk) => chunks.push(chunk));
72639
- req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
72640
- req.on('error', reject);
73226
+ request.on('data', (chunk) => chunks.push(chunk));
73227
+ request.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
73228
+ request.on('error', reject);
72641
73229
  });
72642
73230
  }
72643
73231
  /**
@@ -72666,12 +73254,21 @@ function jsonHeaders() {
72666
73254
  */
72667
73255
  async function runCodexPromptsServer(options) {
72668
73256
  const { port, ...runOptions } = options;
72669
- const serverHandle = startCoderHttpServer(port);
73257
+ const serverUrl = `http://localhost:${port}`;
73258
+ const uiState = new CoderRunUiState(moment());
73259
+ const serverHandle = startCoderHttpServer({
73260
+ port,
73261
+ minimumPriority: runOptions.priority,
73262
+ serverUrl,
73263
+ uiState,
73264
+ });
72670
73265
  console.info(colors.gray('Starting prompt runner in server (keep-alive) mode…'));
72671
73266
  try {
72672
73267
  await runCodexPrompts({
72673
73268
  ...runOptions,
72674
73269
  keepAlive: true,
73270
+ serverUrl,
73271
+ uiState,
72675
73272
  });
72676
73273
  }
72677
73274
  finally {