@promptbook/cli 0.114.0-12 → 0.114.0-13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +7 -4
  2. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentGoalChatPlannedMessages.tsx +23 -8
  3. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +14 -3
  4. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +2 -20
  5. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +2 -1
  6. package/apps/agents-server/src/app/agents/[agentName]/projectReferenceActions.ts +61 -0
  7. package/apps/agents-server/src/components/AgentProjects/resolveLatestCompletedAgentMessageKey.ts +32 -0
  8. package/apps/agents-server/src/components/AgentProjects/useAgentProjectMarkdownReferences.ts +124 -2
  9. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +3 -0
  10. package/apps/agents-server/src/languages/translations/czech.yaml +3 -0
  11. package/apps/agents-server/src/languages/translations/english.yaml +3 -0
  12. package/apps/agents-server/src/tools/agentGoalChatTimeoutToolFunctions.ts +24 -2
  13. package/apps/agents-server/src/tools/agentGoalChatTimeoutTools.ts +5 -4
  14. package/apps/agents-server/src/utils/agentGoalChat/agentGoalChatPlannedMessageActions.ts +38 -9
  15. package/apps/agents-server/src/utils/agentGoalChat/createAgentGoalChatNoteContent.ts +15 -6
  16. package/apps/agents-server/src/utils/agentProjects/listAgentProjectNames.ts +35 -0
  17. package/apps/agents-server/src/utils/agentProjects/listAgentProjects.ts +6 -23
  18. package/apps/agents-server/src/utils/agentProjects/resolveAgentChatProjectReferences.ts +25 -0
  19. package/apps/agents-server/src/utils/chatMessageChips/createAnsweredMessageChipToolCalls.ts +37 -0
  20. package/apps/agents-server/src/utils/chatMessageChips/createPlannedMessageChipToolCalls.ts +39 -0
  21. package/apps/agents-server/src/utils/chatMessageChips/createTouchedProjectChipToolCalls.ts +75 -0
  22. package/apps/agents-server/src/utils/localChatRunner/applyLocalAgentPlannedMessageCommands.ts +212 -0
  23. package/apps/agents-server/src/utils/localChatRunner/prepareLocalAgentPlannedMessagesSidecar.ts +66 -0
  24. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +110 -22
  25. package/apps/agents-server/src/utils/userChat/persistUserChatJobTerminalState.ts +5 -0
  26. package/apps/agents-server/src/utils/userChat/userChatJobRunReport.ts +30 -0
  27. package/apps/agents-server/src/utils/userChatTimeout/createTimeoutWakeUpMessage.ts +9 -1
  28. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore/repeatFiredUserChatTimeout.ts +74 -0
  29. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore.ts +1 -0
  30. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutWorker.ts +63 -54
  31. package/esm/index.es.js +639 -80
  32. package/esm/index.es.js.map +1 -1
  33. package/esm/scripts/run-agent-messages/messages/buildAgentGoalChatPromptSection.d.ts +22 -4
  34. package/esm/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +3 -3
  35. package/esm/scripts/run-agent-messages/messages/loadAgentPlannedMessagesSidecar.d.ts +10 -0
  36. package/esm/scripts/run-agent-messages/messages/resolveTouchedAgentProjects.d.ts +15 -0
  37. package/esm/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  38. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  39. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  40. package/esm/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  41. package/esm/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  42. package/esm/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  43. package/esm/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  44. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  45. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  46. package/esm/src/version.d.ts +1 -1
  47. package/package.json +1 -1
  48. package/src/book-3.0/AgentMessageRunReport.ts +76 -0
  49. package/src/book-3.0/AgentPlannedMessagesSidecar.ts +229 -0
  50. package/src/book-3.0/AgentTeamConversationWorkspace.ts +3 -5
  51. package/src/book-3.0/createAgentMessageSidecarBaseName.ts +20 -0
  52. package/src/book-components/Chat/Chat/Chat.module.css +6 -1
  53. package/src/book-components/Chat/Chat/renderToolCallDetails.tsx +1 -12
  54. package/src/book-components/Chat/utils/agentProjectToolCall.ts +74 -0
  55. package/src/book-components/Chat/utils/getToolCallChipletInfo.ts +28 -0
  56. package/src/book-components/Chat/utils/timeoutToolCallPresentation.ts +23 -3
  57. package/src/other/templates/getTemplatesPipelineCollection.ts +784 -738
  58. package/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.ts +105 -0
  59. package/src/utils/agent-message-runtime/resolveAgentMessageRuntimeActivity.ts +6 -54
  60. package/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.ts +147 -0
  61. package/src/version.ts +2 -2
  62. package/src/versions.txt +1 -0
  63. package/umd/index.umd.js +639 -80
  64. package/umd/index.umd.js.map +1 -1
  65. package/umd/scripts/run-agent-messages/messages/buildAgentGoalChatPromptSection.d.ts +22 -4
  66. package/umd/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +3 -3
  67. package/umd/scripts/run-agent-messages/messages/loadAgentPlannedMessagesSidecar.d.ts +10 -0
  68. package/umd/scripts/run-agent-messages/messages/resolveTouchedAgentProjects.d.ts +15 -0
  69. package/umd/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  70. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  71. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  72. package/umd/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  73. package/umd/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  74. package/umd/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  75. package/umd/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  76. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  77. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  78. package/umd/src/version.d.ts +1 -1
  79. package/esm/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
  80. package/umd/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
package/umd/index.umd.js CHANGED
@@ -58,7 +58,7 @@
58
58
  * @generated
59
59
  * @see https://github.com/webgptorg/promptbook
60
60
  */
61
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-12';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-13';
62
62
  /**
63
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
64
64
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -4098,10 +4098,6 @@
4098
4098
  * Environment variable containing the internal worker token for local agent runners.
4099
4099
  */
4100
4100
  const PTBK_AGENTS_SERVER_USER_CHAT_WORKER_TOKEN_ENV = 'PTBK_AGENTS_SERVER_USER_CHAT_WORKER_TOKEN';
4101
- /**
4102
- * Header used by internal local-runner routes.
4103
- */
4104
- const USER_CHAT_WORKER_TOKEN_HEADER = 'x-user-chat-worker-token';
4105
4101
 
4106
4102
  /**
4107
4103
  * Format either small or big number
@@ -21892,7 +21888,7 @@
21892
21888
  return agentBookStats.isFile();
21893
21889
  }
21894
21890
  catch (error) {
21895
- if (isFileNotFoundError$6(error)) {
21891
+ if (isFileNotFoundError$7(error)) {
21896
21892
  return false;
21897
21893
  }
21898
21894
  throw error;
@@ -21901,7 +21897,7 @@
21901
21897
  /**
21902
21898
  * Returns true when one filesystem error indicates a missing path.
21903
21899
  */
21904
- function isFileNotFoundError$6(error) {
21900
+ function isFileNotFoundError$7(error) {
21905
21901
  return Boolean(error &&
21906
21902
  typeof error === 'object' &&
21907
21903
  'code' in error &&
@@ -21931,7 +21927,7 @@
21931
21927
  return await promises.readdir(queuedMessagesDirectoryPath, { withFileTypes: true });
21932
21928
  }
21933
21929
  catch (error) {
21934
- if (isFileNotFoundError$5(error)) {
21930
+ if (isFileNotFoundError$6(error)) {
21935
21931
  return [];
21936
21932
  }
21937
21933
  throw error;
@@ -21952,7 +21948,7 @@
21952
21948
  /**
21953
21949
  * Returns true when an error is a missing-path filesystem error.
21954
21950
  */
21955
- function isFileNotFoundError$5(error) {
21951
+ function isFileNotFoundError$6(error) {
21956
21952
  return Boolean(error &&
21957
21953
  typeof error === 'object' &&
21958
21954
  'code' in error &&
@@ -22430,7 +22426,7 @@
22430
22426
  lastIndexLockState = await readGitIndexLockState(cwd, options.env);
22431
22427
  if ((lastIndexLockState === null || lastIndexLockState === void 0 ? void 0 : lastIndexLockState.isStale) && !isStaleIndexLockRemoved) {
22432
22428
  await promises.unlink(lastIndexLockState.path).catch((unlinkError) => {
22433
- if (isFileNotFoundError$4(unlinkError)) {
22429
+ if (isFileNotFoundError$5(unlinkError)) {
22434
22430
  return;
22435
22431
  }
22436
22432
  throw unlinkError;
@@ -22491,7 +22487,7 @@
22491
22487
  };
22492
22488
  }
22493
22489
  catch (error) {
22494
- if (isFileNotFoundError$4(error)) {
22490
+ if (isFileNotFoundError$5(error)) {
22495
22491
  return undefined;
22496
22492
  }
22497
22493
  throw error;
@@ -22548,7 +22544,7 @@
22548
22544
  /**
22549
22545
  * Detects missing-file errors while inspecting the lock file.
22550
22546
  */
22551
- function isFileNotFoundError$4(error) {
22547
+ function isFileNotFoundError$5(error) {
22552
22548
  return Boolean(error &&
22553
22549
  typeof error === 'object' &&
22554
22550
  'code' in error &&
@@ -29624,7 +29620,7 @@
29624
29620
  return crypto.createHash('sha1').update(content).digest('hex');
29625
29621
  }
29626
29622
  catch (error) {
29627
- if (isFileNotFoundError$3(error)) {
29623
+ if (isFileNotFoundError$4(error)) {
29628
29624
  return null;
29629
29625
  }
29630
29626
  throw error;
@@ -29639,7 +29635,7 @@
29639
29635
  /**
29640
29636
  * Returns true when an error is a missing-file filesystem error.
29641
29637
  */
29642
- function isFileNotFoundError$3(error) {
29638
+ function isFileNotFoundError$4(error) {
29643
29639
  return Boolean(error &&
29644
29640
  typeof error === 'object' &&
29645
29641
  'code' in error &&
@@ -30632,7 +30628,7 @@
30632
30628
  - You have 100% control over every project folder: create, read, modify, and delete files there, run scripts and commands inside it, and keep any persistent data you need between conversations.
30633
30629
  - When the user asks you to build something (for example a website) or to do longer-term work, create a new project folder \`${AGENT_PROJECTS_DIRECTORY_PATH}/<project-name>/\` (use a short kebab-case name) and do the work inside it. Modify an existing project when the user refers to work you already did.
30634
30630
  - A project can be a git repository — you can run \`git init\` inside a project folder and commit your work there.
30635
- - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory except the queued message file you are answering.
30631
+ - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory beyond the file changes allowed at the top of this prompt.
30636
30632
 
30637
30633
  ${block(buildProjectReferenceInstructions(projectsUrlPath))}
30638
30634
  ${block(buildProjectRuntimeInstructions(options.projectRuntimeApi))}
@@ -30688,55 +30684,166 @@
30688
30684
  }
30689
30685
 
30690
30686
  /**
30691
- * Title stored on every agent goal chat.
30687
+ * Milliseconds in one second.
30688
+ *
30689
+ * @private internal timeout-chat constant
30690
+ */
30691
+ const SECOND_IN_MILLISECONDS = 1000;
30692
+ /**
30693
+ * Seconds in one minute.
30694
+ *
30695
+ * @private internal timeout-chat constant
30696
+ */
30697
+ const MINUTE_IN_SECONDS = 60;
30698
+ /**
30699
+ * Seconds in one hour.
30700
+ *
30701
+ * @private internal timeout-chat constant
30702
+ */
30703
+ const HOUR_IN_SECONDS = 60 * MINUTE_IN_SECONDS;
30704
+ /**
30705
+ * Seconds in one day.
30706
+ *
30707
+ * @private internal timeout-chat constant
30708
+ */
30709
+ const HOURS_IN_DAY = 24;
30710
+ /**
30711
+ * Seconds in one day.
30712
+ *
30713
+ * @private internal timeout-chat constant
30714
+ */
30715
+ const DAY_IN_SECONDS = HOURS_IN_DAY * HOUR_IN_SECONDS;
30716
+ /**
30717
+ * Converts timeout duration into natural language sentence fragments.
30718
+ *
30719
+ * Shared by every timeout surface: the chat chips, the goal-chat planned messages of the Agents
30720
+ * Server, and the planned-message prompt section of the agent-folder runner.
30721
+ *
30722
+ * @param milliseconds - Duration or repeat interval in milliseconds.
30723
+ * @returns Label such as `5 minutes` or `1 hour 30 minutes`.
30724
+ *
30725
+ * @private internal utility of `<Chat/>` and the Agents Server planned messages
30692
30726
  */
30727
+ function formatTimeoutDurationHuman(milliseconds) {
30728
+ const normalizedMilliseconds = Math.max(0, Math.floor(milliseconds));
30729
+ const totalSeconds = Math.max(1, Math.floor(normalizedMilliseconds / SECOND_IN_MILLISECONDS));
30730
+ if (totalSeconds < MINUTE_IN_SECONDS) {
30731
+ return formatTimeoutUnit(totalSeconds, 'second');
30732
+ }
30733
+ if (totalSeconds < HOUR_IN_SECONDS) {
30734
+ const roundedMinutes = Math.max(1, Math.round(totalSeconds / MINUTE_IN_SECONDS));
30735
+ return formatTimeoutUnit(roundedMinutes, 'minute');
30736
+ }
30737
+ if (totalSeconds < DAY_IN_SECONDS) {
30738
+ const hours = Math.floor(totalSeconds / HOUR_IN_SECONDS);
30739
+ const minutes = Math.floor((totalSeconds % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS);
30740
+ if (minutes === 0) {
30741
+ return formatTimeoutUnit(hours, 'hour');
30742
+ }
30743
+ return `${formatTimeoutUnit(hours, 'hour')} ${formatTimeoutUnit(minutes, 'minute')}`;
30744
+ }
30745
+ const days = Math.floor(totalSeconds / DAY_IN_SECONDS);
30746
+ const hours = Math.floor((totalSeconds % DAY_IN_SECONDS) / HOUR_IN_SECONDS);
30747
+ if (hours === 0) {
30748
+ return formatTimeoutUnit(days, 'day');
30749
+ }
30750
+ return `${formatTimeoutUnit(days, 'day')} ${formatTimeoutUnit(hours, 'hour')}`;
30751
+ }
30693
30752
  /**
30694
- * Internal endpoint used by managed coding agents to plan goal-chat messages.
30753
+ * Formats one pluralized timeout unit.
30754
+ *
30755
+ * @private internal timeout-chat helper
30695
30756
  */
30696
- const AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH = '/api/internal/agent-goal-chat-planned-messages';
30757
+ function formatTimeoutUnit(value, unit) {
30758
+ return `${value} ${unit}${value === 1 ? '' : 's'}`;
30759
+ }
30760
+ // Note: [💞] Ignore a discrepancy between file name and entity name
30697
30761
 
30698
30762
  /**
30699
30763
  * Builds the planned-message instructions available to an Agents Server-managed coding agent.
30700
30764
  *
30701
- * Planned messages always target the agent's singleton goal chat, regardless of the chat in which
30702
- * the command is invoked.
30765
+ * Planned messages always target the agent's singleton goal chat, regardless of the chat in which the
30766
+ * wake-up is planned, and they repeat like `setInterval` until they are cancelled. The sidecar file is
30767
+ * the only channel that changes them, so an answer that merely claims a follow-up was planned changes
30768
+ * nothing — and an answer that changes nothing keeps the current plan running.
30769
+ *
30770
+ * @param sidecar - Sidecar prepared by the Agents Server for the answered message.
30771
+ * @returns Prompt section, or an empty string outside Agents Server-managed runs.
30703
30772
  */
30704
- function buildAgentGoalChatPromptSection(runtimeApi) {
30705
- if (!runtimeApi) {
30773
+ function buildAgentGoalChatPromptSection(sidecar) {
30774
+ if (!sidecar) {
30706
30775
  return '';
30707
30776
  }
30708
- return _spaceTrim.spaceTrim(`
30709
- ## Planned goal-chat messages
30777
+ return _spaceTrim.spaceTrim((block) => `
30778
+ ## Planned goal-chat messages
30710
30779
 
30711
- You can plan a future message that wakes you in your singleton goal chat. These commands are available during every Agents Server-managed chat invocation, not only while answering inside the goal chat.
30780
+ You can plan a message that repeatedly wakes you in your singleton goal chat. A planned message works like \`setInterval\`, not like \`setTimeout\`: once planned, it keeps waking you at its interval until you cancel it. This works during every Agents Server-managed chat invocation, not only while answering inside the goal chat.
30712
30781
 
30713
- - Use \`list_timeouts\` before planning follow-up work so you do not create duplicates.
30714
- - Use \`set_timeout\` when useful work should resume later. The delay is a positive number of milliseconds and the message should say exactly what your future invocation needs to do.
30715
- - Use \`cancel_timeout\` with the returned \`timeoutId\` when a planned message is no longer useful.
30716
- - A fired message appears in the goal chat and invokes you there. If more future work remains, plan the next useful message during that invocation.
30782
+ ${block(buildCurrentPlannedMessagesLines(sidecar.currentPlannedMessages))}
30717
30783
 
30718
- Run the corresponding authenticated command:
30784
+ Editing \`${sidecar.relativeSidecarPath}\` is the **only** way to change what wakes you. Writing in your answer that you scheduled something does not schedule anything.
30719
30785
 
30720
- \`\`\`bash
30721
- # list_timeouts
30722
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30723
- -H "Content-Type: application/json" \\
30724
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30725
- -d '{"action":"list","agentPermanentId":"${runtimeApi.agentPermanentId}"}'
30786
+ - **Keeping your planned messages as they are is the default.** When the list above already matches your goal, leave \`commands\` empty — every listed message keeps repeating on its own, and re-planning it would only duplicate it.
30787
+ - When your goal needs a repeating wake-up that is not listed above, append \`{"action":"set","milliseconds":<repeat interval>,"message":"<what each future invocation must do>"}\` to the \`commands\` array of that file.
30788
+ - When a listed message no longer matches your goal (for example your goal now says every 10 minutes while the message repeats every 5 minutes), append \`{"action":"cancel","timeoutId":"<timeout id>"}\` and one \`set\` command with the interval your goal really requires.
30789
+ - When your goal no longer needs a listed message at all, cancel it without planning a replacement.
30790
+ - Never edit \`version\`, \`agentPermanentId\`, or \`currentPlannedMessages\`.
30791
+ - The Agents Server applies every command once your answer is finished, and each repetition appears in the goal chat and invokes you there.
30792
+ - Only mention a planned follow-up in your answer when it is really planned: either you appended the matching \`set\` command, or you kept one of the messages listed above.
30793
+ `);
30794
+ }
30795
+ /**
30796
+ * Renders the already planned messages so the agent can compare them with its goal.
30797
+ *
30798
+ * @param currentPlannedMessages - Planned messages prepared by the Agents Server.
30799
+ * @returns Markdown list, or a sentence stating that nothing is planned yet.
30800
+ *
30801
+ * @private function of `buildAgentGoalChatPromptSection`
30802
+ */
30803
+ function buildCurrentPlannedMessagesLines(currentPlannedMessages) {
30804
+ if (currentPlannedMessages.length === 0) {
30805
+ return 'You currently have no planned messages waiting.';
30806
+ }
30807
+ const plannedMessageLines = currentPlannedMessages.map(createCurrentPlannedMessageLine);
30808
+ return _spaceTrim.spaceTrim((block) => `
30809
+ These planned messages are already waiting for you, so keep them unless they stopped matching your goal:
30726
30810
 
30727
- # set_timeout
30728
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30729
- -H "Content-Type: application/json" \\
30730
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30731
- -d '{"action":"set","agentPermanentId":"${runtimeApi.agentPermanentId}","milliseconds":3600000,"message":"Continue the current goal and decide the next concrete action."}'
30811
+ ${block(plannedMessageLines.join('\n'))}
30812
+ `);
30813
+ }
30814
+ /**
30815
+ * Renders one already planned message with the schedule the agent has to compare with its goal.
30816
+ *
30817
+ * @param plannedMessage - One planned message prepared by the Agents Server.
30818
+ * @returns Markdown list item.
30819
+ *
30820
+ * @private function of `buildAgentGoalChatPromptSection`
30821
+ */
30822
+ function createCurrentPlannedMessageLine(plannedMessage) {
30823
+ var _a;
30824
+ const message = ((_a = plannedMessage.message) === null || _a === void 0 ? void 0 : _a.trim()) || 'Continue working towards the current goal.';
30825
+ const schedule = plannedMessage.intervalMs
30826
+ ? `repeats every ${formatTimeoutDurationHuman(plannedMessage.intervalMs)}`
30827
+ : `wakes you once at ${plannedMessage.dueAt}`;
30828
+ return `- \`${plannedMessage.timeoutId}\` ${schedule}: ${message}`;
30829
+ }
30732
30830
 
30733
- # cancel_timeout
30734
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30735
- -H "Content-Type: application/json" \\
30736
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30737
- -d '{"action":"cancel","agentPermanentId":"${runtimeApi.agentPermanentId}","timeoutId":"<timeout-id>"}'
30738
- \`\`\`
30739
- `);
30831
+ /**
30832
+ * Creates the stable file-system safe base name that belongs to one queued `.book` message.
30833
+ *
30834
+ * Every sidecar the Agents Server prepares next to a queued message (TEAM workspaces, planned-message
30835
+ * files, ...) derives its own name from this one normalization, so a sidecar can always be found again
30836
+ * from the message file name alone.
30837
+ *
30838
+ * @param messageFileName - File name of the queued message, with or without directories.
30839
+ * @returns Normalized base name usable as a directory or file name.
30840
+ *
30841
+ * @private internal convention shared by the Agents Server and agent-folder runner
30842
+ */
30843
+ function createAgentMessageSidecarBaseName(messageFileName) {
30844
+ const rawBaseName = path.basename(messageFileName).replace(/\.book$/iu, '');
30845
+ const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
30846
+ return normalizedBaseName || 'message';
30740
30847
  }
30741
30848
 
30742
30849
  // Note: [💞] This file defines the shared TEAM workspace convention rather than one standalone entity.
@@ -30770,9 +30877,7 @@
30770
30877
  * @private internal convention shared by the Agents Server and agent-folder runner
30771
30878
  */
30772
30879
  function createAgentTeamConversationWorkspaceDirectoryName(messageFileName) {
30773
- const rawBaseName = path.basename(messageFileName).replace(/\.book$/iu, '');
30774
- const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
30775
- return normalizedBaseName || 'message';
30880
+ return createAgentMessageSidecarBaseName(messageFileName);
30776
30881
  }
30777
30882
  /**
30778
30883
  * Creates the relative active workspace path for one queued message.
@@ -30842,8 +30947,8 @@
30842
30947
  return '';
30843
30948
  }
30844
30949
  const teammateSections = workspace.manifest.teammates.map((teammate) => {
30845
- const sourcePath = toPromptPath(path.join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
30846
- const transcriptPath = toPromptPath(path.join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
30950
+ const sourcePath = toPromptPath$1(path.join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
30951
+ const transcriptPath = toPromptPath$1(path.join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
30847
30952
  const instructions = teammate.instructions.trim() || 'No additional TEAM instructions were provided.';
30848
30953
  return _spaceTrim.spaceTrim(`
30849
30954
  - ${teammate.agentName}
@@ -30861,7 +30966,7 @@
30861
30966
 
30862
30967
  - The underlying coding harness is already executing this turn **exactly once**. Never start, queue, or invoke another coding harness, agent runner, or \`ptbk\` command to consult a teammate.
30863
30968
  - The primary agent remains responsible for the final user-facing \`MESSAGE @Agent\` answer.
30864
- - Teammate source files are read-only context. When a teammate consultation occurs, record the actual exchange in a new top-level \`.book\` file in \`${toPromptPath(workspace.relativeWorkspacePath)}\`.
30969
+ - Teammate source files are read-only context. When a teammate consultation occurs, record the actual exchange in a new top-level \`.book\` file in \`${toPromptPath$1(workspace.relativeWorkspacePath)}\`.
30865
30970
  - Name each transcript \`<teammate permanent id>--<sequence>.book\`, for example \`${workspace.manifest.teammates[0].permanentId}--01.book\`.
30866
30971
  - Every transcript must contain alternating \`MESSAGE @${workspace.manifest.primaryAgent.agentName}\` and \`MESSAGE @<teammate name>\` blocks. Do not create a transcript for a teammate that was not consulted.
30867
30972
  `);
@@ -30869,7 +30974,7 @@
30869
30974
  /**
30870
30975
  * Converts a filesystem-relative path into the portable path notation used in prompts.
30871
30976
  */
30872
- function toPromptPath(path) {
30977
+ function toPromptPath$1(path) {
30873
30978
  return path.replace(/\\/gu, '/');
30874
30979
  }
30875
30980
 
@@ -30882,7 +30987,7 @@
30882
30987
 
30883
30988
  - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE\` block. In a normal chat it is from \`@User\`; in your internal goal chat it can be from \`@Agent\`.
30884
30989
  - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
30885
- ${block(buildAllowedFileChangesPromptLine(options.teamWorkspace))}
30990
+ ${block(buildAllowedFileChangesPromptLine(options))}
30886
30991
 
30887
30992
  ## Rules for the answering
30888
30993
 
@@ -30918,7 +31023,7 @@
30918
31023
 
30919
31024
  - You can use \`message\` for message that will be sent immediately after clicking the button, or \`messageDraft\` for message that will be prefilled in the input field for editing before sending.
30920
31025
 
30921
- ${block(buildAgentGoalChatPromptSection(options.goalChatRuntimeApi))}
31026
+ ${block(buildAgentGoalChatPromptSection(options.plannedMessagesSidecar))}
30922
31027
 
30923
31028
  ${block(buildAgentProjectsPromptSection(options))}
30924
31029
 
@@ -30932,11 +31037,30 @@
30932
31037
  /**
30933
31038
  * Explains the exact files that one coding harness may change for a user turn.
30934
31039
  */
30935
- function buildAllowedFileChangesPromptLine(teamWorkspace) {
30936
- if (!teamWorkspace) {
30937
- return `- Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`;
31040
+ function buildAllowedFileChangesPromptLine(options) {
31041
+ const allowedFileChanges = [`files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`];
31042
+ if (options.teamWorkspace) {
31043
+ allowedFileChanges.push(`new consultation transcripts inside \`${toPromptPath(options.teamWorkspace.relativeWorkspacePath)}\``);
31044
+ }
31045
+ if (options.plannedMessagesSidecar) {
31046
+ allowedFileChanges.push(`the \`commands\` array of \`${toPromptPath(options.plannedMessagesSidecar.relativeSidecarPath)}\``);
31047
+ }
31048
+ return `- Do not modify any other file in the repository, except ${formatAllowedFileChanges(allowedFileChanges)}`;
31049
+ }
31050
+ /**
31051
+ * Joins the allowed file-change phrases into one readable enumeration.
31052
+ */
31053
+ function formatAllowedFileChanges(allowedFileChanges) {
31054
+ if (allowedFileChanges.length === 1) {
31055
+ return allowedFileChanges[0];
30938
31056
  }
30939
- return `- Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory and new consultation transcripts inside \`${teamWorkspace.relativeWorkspacePath.replace(/\\/gu, '/')}\``;
31057
+ return `${allowedFileChanges.slice(0, -1).join(', ')} and ${allowedFileChanges[allowedFileChanges.length - 1]}`;
31058
+ }
31059
+ /**
31060
+ * Converts a filesystem-relative path into the portable path notation used in prompts.
31061
+ */
31062
+ function toPromptPath(path) {
31063
+ return path.replace(/\\/gu, '/');
30940
31064
  }
30941
31065
 
30942
31066
  var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModels",description:"List of available model names together with their descriptions as JSON",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelsRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n```json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n```\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n```json\n{availableModels}\n```\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelsRequirements",format:"JSON",dependentParameterNames:["availableModels","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModels}` List of available model names together with their descriptions as JSON\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelsRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n\\`\\`\\`json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n\\`\\`\\`json\n{availableModels}\n\\`\\`\\`\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelsRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
@@ -37690,12 +37814,12 @@
37690
37814
  await promises.rm(finishedWorkspacePath, { recursive: true, force: true });
37691
37815
  await promises.mkdir(finishedWorkspacePath, { recursive: true });
37692
37816
  const relativePaths = [
37693
- toPortablePath$1(path.join(finishedRelativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME)),
37817
+ toPortablePath$2(path.join(finishedRelativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME)),
37694
37818
  ];
37695
37819
  await promises.rename(activeManifestPath, path.join(finishedWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME));
37696
37820
  for (const transcriptFileName of transcriptFileNames) {
37697
37821
  await promises.rename(path.join(activeWorkspacePath, transcriptFileName), path.join(finishedWorkspacePath, transcriptFileName));
37698
- relativePaths.push(toPortablePath$1(path.join(finishedRelativeWorkspacePath, transcriptFileName)));
37822
+ relativePaths.push(toPortablePath$2(path.join(finishedRelativeWorkspacePath, transcriptFileName)));
37699
37823
  }
37700
37824
  await promises.rm(activeWorkspacePath, { recursive: true, force: true });
37701
37825
  return { relativePaths };
@@ -37712,7 +37836,7 @@
37712
37836
  .sort((firstFileName, secondFileName) => firstFileName.localeCompare(secondFileName));
37713
37837
  }
37714
37838
  catch (error) {
37715
- if (isFileNotFoundError$2(error)) {
37839
+ if (isFileNotFoundError$3(error)) {
37716
37840
  return [];
37717
37841
  }
37718
37842
  throw error;
@@ -37727,7 +37851,7 @@
37727
37851
  return true;
37728
37852
  }
37729
37853
  catch (error) {
37730
- if (isFileNotFoundError$2(error)) {
37854
+ if (isFileNotFoundError$3(error)) {
37731
37855
  return false;
37732
37856
  }
37733
37857
  throw error;
@@ -37736,11 +37860,204 @@
37736
37860
  /**
37737
37861
  * Converts a filesystem-relative path into a portable Git path.
37738
37862
  */
37863
+ function toPortablePath$2(path) {
37864
+ return path.replace(/\\/gu, '/');
37865
+ }
37866
+ /**
37867
+ * Returns true when one filesystem error indicates a missing path.
37868
+ */
37869
+ function isFileNotFoundError$3(error) {
37870
+ return Boolean(error &&
37871
+ typeof error === 'object' &&
37872
+ 'code' in error &&
37873
+ (error.code === 'ENOENT' || error.code === 'ENOTDIR'));
37874
+ }
37875
+
37876
+ // Note: [💞] This file defines the shared planned-message sidecar convention rather than one standalone entity.
37877
+ /**
37878
+ * Relative directory holding the planned-message sidecar of each queued message.
37879
+ *
37880
+ * @private internal convention shared by the Agents Server and agent-folder runner
37881
+ */
37882
+ const AGENT_PLANNED_MESSAGES_DIRECTORY_PATH = path.join(AGENT_MESSAGES_DIRECTORY_PATH, 'planned');
37883
+ /**
37884
+ * Command actions an agent can request from its planned-message sidecar.
37885
+ *
37886
+ * @private internal convention shared by the Agents Server and agent-folder runner
37887
+ */
37888
+ const AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS = ['set', 'cancel'];
37889
+ /**
37890
+ * Creates the relative planned-message sidecar path of one queued message.
37891
+ *
37892
+ * @param messageFileName - File name of the queued `.book` message.
37893
+ * @returns Relative path of the sidecar inside the agent folder.
37894
+ *
37895
+ * @private internal convention shared by the Agents Server and agent-folder runner
37896
+ */
37897
+ function createAgentPlannedMessagesSidecarPath(messageFileName) {
37898
+ return path.join(AGENT_PLANNED_MESSAGES_DIRECTORY_PATH, `${createAgentMessageSidecarBaseName(messageFileName)}.json`);
37899
+ }
37900
+ /**
37901
+ * Parses one untrusted planned-message sidecar written back by a coding harness.
37902
+ *
37903
+ * A malformed sidecar never fails the already answered chat, so this returns `null` instead of throwing.
37904
+ *
37905
+ * @param sidecarContent - Raw file content read from the agent folder.
37906
+ * @returns Normalized sidecar, or `null` when it cannot be used.
37907
+ *
37908
+ * @private internal convention shared by the Agents Server and agent-folder runner
37909
+ */
37910
+ function parseAgentPlannedMessagesSidecar(sidecarContent) {
37911
+ let parsedSidecar;
37912
+ try {
37913
+ parsedSidecar = JSON.parse(sidecarContent);
37914
+ }
37915
+ catch (_a) {
37916
+ return null;
37917
+ }
37918
+ if (!parsedSidecar || typeof parsedSidecar !== 'object' || Array.isArray(parsedSidecar)) {
37919
+ return null;
37920
+ }
37921
+ const sidecar = parsedSidecar;
37922
+ if (sidecar.version !== 1 || typeof sidecar.agentPermanentId !== 'string') {
37923
+ return null;
37924
+ }
37925
+ return {
37926
+ version: 1,
37927
+ agentPermanentId: sidecar.agentPermanentId,
37928
+ currentPlannedMessages: normalizeAgentPlannedMessageSnapshots(sidecar.currentPlannedMessages),
37929
+ commands: normalizeAgentPlannedMessageCommands(sidecar.commands),
37930
+ };
37931
+ }
37932
+ /**
37933
+ * Keeps only the commands whose action is understood by the planned-message actions.
37934
+ *
37935
+ * @param rawCommands - Untrusted `commands` value read from one sidecar.
37936
+ * @returns Commands that can be dispatched, in their original order.
37937
+ *
37938
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37939
+ */
37940
+ function normalizeAgentPlannedMessageCommands(rawCommands) {
37941
+ if (!Array.isArray(rawCommands)) {
37942
+ return [];
37943
+ }
37944
+ return rawCommands.filter(isAgentPlannedMessageCommand);
37945
+ }
37946
+ /**
37947
+ * Checks whether one untrusted sidecar entry names a supported planned-message action.
37948
+ *
37949
+ * @param value - Untrusted command entry.
37950
+ * @returns `true` when the entry can be dispatched.
37951
+ *
37952
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37953
+ */
37954
+ function isAgentPlannedMessageCommand(value) {
37955
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
37956
+ return false;
37957
+ }
37958
+ const action = value.action;
37959
+ return (typeof action === 'string' &&
37960
+ AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS.includes(action));
37961
+ }
37962
+ /**
37963
+ * Keeps only the already planned messages that can be shown to the agent.
37964
+ *
37965
+ * @param rawSnapshots - Untrusted `currentPlannedMessages` value read from one sidecar.
37966
+ * @returns Planned-message snapshots with the fields required for display and cancellation.
37967
+ *
37968
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37969
+ */
37970
+ function normalizeAgentPlannedMessageSnapshots(rawSnapshots) {
37971
+ if (!Array.isArray(rawSnapshots)) {
37972
+ return [];
37973
+ }
37974
+ return rawSnapshots.filter(isAgentPlannedMessageSnapshot).map(createNormalizedAgentPlannedMessageSnapshot);
37975
+ }
37976
+ /**
37977
+ * Normalizes the optional fields of one already planned message.
37978
+ *
37979
+ * @param snapshot - Planned-message entry with the required identity fields.
37980
+ * @returns Snapshot with a usable repeat interval and message.
37981
+ *
37982
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37983
+ */
37984
+ function createNormalizedAgentPlannedMessageSnapshot(snapshot) {
37985
+ const intervalMs = Number(snapshot.intervalMs);
37986
+ return {
37987
+ timeoutId: snapshot.timeoutId,
37988
+ dueAt: snapshot.dueAt,
37989
+ message: typeof snapshot.message === 'string' ? snapshot.message : null,
37990
+ intervalMs: Number.isFinite(intervalMs) && intervalMs > 0 ? Math.floor(intervalMs) : null,
37991
+ };
37992
+ }
37993
+ /**
37994
+ * Checks whether one untrusted sidecar entry describes an already planned message.
37995
+ *
37996
+ * @param value - Untrusted planned-message entry.
37997
+ * @returns `true` when the entry can be shown and cancelled.
37998
+ *
37999
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
38000
+ */
38001
+ function isAgentPlannedMessageSnapshot(value) {
38002
+ return Boolean(value &&
38003
+ typeof value === 'object' &&
38004
+ typeof value.timeoutId === 'string' &&
38005
+ typeof value.dueAt === 'string');
38006
+ }
38007
+
38008
+ /**
38009
+ * Loads the planned-message sidecar the Agents Server prepared for one queued message.
38010
+ *
38011
+ * @param projectPath - Absolute path of the current agent runner folder.
38012
+ * @param queuedMessage - Queued message currently being answered.
38013
+ * @returns Sidecar details for the prompt, or `null` outside Agents Server-managed runs.
38014
+ */
38015
+ async function loadAgentPlannedMessagesSidecar(projectPath, queuedMessage) {
38016
+ const relativeSidecarPath = createAgentPlannedMessagesSidecarPath(queuedMessage.fileName);
38017
+ const sidecarContent = await readOptionalTextFile$2(path.join(projectPath, relativeSidecarPath));
38018
+ if (sidecarContent === null) {
38019
+ return null;
38020
+ }
38021
+ const sidecar = parseAgentPlannedMessagesSidecar(sidecarContent);
38022
+ if (!sidecar) {
38023
+ return null;
38024
+ }
38025
+ return {
38026
+ relativeSidecarPath: toPortablePath$1(relativeSidecarPath),
38027
+ currentPlannedMessages: sidecar.currentPlannedMessages,
38028
+ };
38029
+ }
38030
+ /**
38031
+ * Reads one text file and treats a missing planned-message sidecar as absent.
38032
+ *
38033
+ * @param path - Absolute sidecar path.
38034
+ * @returns File content, or `null` when the sidecar does not exist.
38035
+ */
38036
+ async function readOptionalTextFile$2(path) {
38037
+ try {
38038
+ return await promises.readFile(path, 'utf-8');
38039
+ }
38040
+ catch (error) {
38041
+ if (isFileNotFoundError$2(error)) {
38042
+ return null;
38043
+ }
38044
+ throw error;
38045
+ }
38046
+ }
38047
+ /**
38048
+ * Converts a filesystem-relative path to the portable form used in prompts.
38049
+ *
38050
+ * @param path - Relative path inside the agent folder.
38051
+ * @returns Path with forward slashes.
38052
+ */
37739
38053
  function toPortablePath$1(path) {
37740
38054
  return path.replace(/\\/gu, '/');
37741
38055
  }
37742
38056
  /**
37743
38057
  * Returns true when one filesystem error indicates a missing path.
38058
+ *
38059
+ * @param error - Caught filesystem failure.
38060
+ * @returns `true` for missing-path errors.
37744
38061
  */
37745
38062
  function isFileNotFoundError$2(error) {
37746
38063
  return Boolean(error &&
@@ -37755,7 +38072,7 @@
37755
38072
  async function loadAgentTeamConversationWorkspace(projectPath, queuedMessage) {
37756
38073
  const relativeWorkspacePath = createAgentTeamConversationWorkspacePath(queuedMessage.fileName);
37757
38074
  const manifestPath = path.join(projectPath, relativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME);
37758
- const manifestContent = await readOptionalTextFile(manifestPath);
38075
+ const manifestContent = await readOptionalTextFile$1(manifestPath);
37759
38076
  if (manifestContent === null) {
37760
38077
  return null;
37761
38078
  }
@@ -37783,7 +38100,7 @@
37783
38100
  /**
37784
38101
  * Reads one text file and treats a missing team workspace as absent.
37785
38102
  */
37786
- async function readOptionalTextFile(path) {
38103
+ async function readOptionalTextFile$1(path) {
37787
38104
  try {
37788
38105
  return await promises.readFile(path, 'utf-8');
37789
38106
  }
@@ -37850,6 +38167,233 @@
37850
38167
  return `/agents/${encodeURIComponent(agentId)}/projects`;
37851
38168
  }
37852
38169
 
38170
+ /**
38171
+ * Parses every structured event out of one coding-harness runtime log.
38172
+ *
38173
+ * The local agent runner streams the answering harness output into a live runtime log file, one
38174
+ * JSON event per line, optionally prefixed by shell noise. Lines without a usable JSON object are
38175
+ * skipped so a partially written or interleaved log never breaks its consumers.
38176
+ *
38177
+ * @param logText - Raw runtime log content.
38178
+ * @returns Structured events in the order they were streamed.
38179
+ * @private internal utility of the agent-message runtime
38180
+ */
38181
+ function parseAgentMessageRuntimeLogEvents(logText) {
38182
+ if (!logText) {
38183
+ return [];
38184
+ }
38185
+ const events = [];
38186
+ for (const line of logText.split(/\r?\n/u)) {
38187
+ const event = parseAgentMessageRuntimeLogEvent(line);
38188
+ if (event) {
38189
+ events.push(event);
38190
+ }
38191
+ }
38192
+ return events;
38193
+ }
38194
+ /**
38195
+ * Parses one runtime log line into a structured event when it embeds JSON.
38196
+ *
38197
+ * @param line - One raw runtime log line.
38198
+ * @returns Structured event, or `null` when the line carries no JSON object.
38199
+ * @private internal helper of `parseAgentMessageRuntimeLogEvents`
38200
+ */
38201
+ function parseAgentMessageRuntimeLogEvent(line) {
38202
+ const jsonStartIndex = line.indexOf('{');
38203
+ if (jsonStartIndex === -1) {
38204
+ return null;
38205
+ }
38206
+ try {
38207
+ const parsedEvent = JSON.parse(line.slice(jsonStartIndex).trim());
38208
+ if (typeof parsedEvent !== 'object' || parsedEvent === null || Array.isArray(parsedEvent)) {
38209
+ return null;
38210
+ }
38211
+ return parsedEvent;
38212
+ }
38213
+ catch (_a) {
38214
+ return null;
38215
+ }
38216
+ }
38217
+
38218
+ /**
38219
+ * Matches one project directory name following the agent `projects/` folder in a file path or command.
38220
+ *
38221
+ * Both path separators are accepted because the local runner answers messages on Unix and Windows,
38222
+ * and JSON-encoded payloads escape backslashes.
38223
+ *
38224
+ * @private internal constant of agent-message touched projects
38225
+ */
38226
+ const TOUCHED_PROJECT_PATH_REGEX = new RegExp(`${AGENT_PROJECTS_DIRECTORY_PATH}[\\\\/]+([^\\\\/"'\\s,;:]+)`, 'giu');
38227
+ /**
38228
+ * Resolves which projects one coding harness touched while answering a single message.
38229
+ *
38230
+ * The runtime log streams what the harness really did, so a project counts as touched when the
38231
+ * harness named it in a tool invocation — reading, searching, running, or editing files inside
38232
+ * `projects/<project-name>/`. Only tool payloads are inspected: tool results and assistant
38233
+ * narration are ignored so a project merely mentioned in the conversation is never reported.
38234
+ *
38235
+ * Every candidate is matched against the projects that really exist, which both filters prompt
38236
+ * placeholders such as `projects/<project-name>/` and keeps the reported names canonical.
38237
+ *
38238
+ * @param options - Raw runtime log content and the project directory names of the agent.
38239
+ * @returns Touched project names in the canonical spelling, ordered by first appearance.
38240
+ * @private internal utility of the agent-message runtime
38241
+ */
38242
+ function resolveAgentMessageTouchedProjectNames(options) {
38243
+ const canonicalProjectNames = createCanonicalProjectNameLookup(options.knownProjectNames);
38244
+ if (canonicalProjectNames.size === 0) {
38245
+ return [];
38246
+ }
38247
+ const touchedProjectNames = new Set();
38248
+ for (const event of parseAgentMessageRuntimeLogEvents(options.logText)) {
38249
+ for (const toolInvocationText of resolveRuntimeLogEventToolInvocationTexts(event)) {
38250
+ for (const candidateProjectName of resolveCandidateProjectNames(toolInvocationText)) {
38251
+ const canonicalProjectName = canonicalProjectNames.get(candidateProjectName.toLowerCase());
38252
+ if (canonicalProjectName !== undefined) {
38253
+ touchedProjectNames.add(canonicalProjectName);
38254
+ }
38255
+ }
38256
+ }
38257
+ }
38258
+ return Array.from(touchedProjectNames);
38259
+ }
38260
+ /**
38261
+ * Indexes the existing project directory names by their lower-cased form.
38262
+ *
38263
+ * @param knownProjectNames - Project directory names of the agent.
38264
+ * @returns Lookup from the lower-cased name to its canonical spelling.
38265
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38266
+ */
38267
+ function createCanonicalProjectNameLookup(knownProjectNames) {
38268
+ const canonicalProjectNames = new Map();
38269
+ for (const knownProjectName of knownProjectNames) {
38270
+ const normalizedProjectName = knownProjectName.trim();
38271
+ if (normalizedProjectName.length > 0) {
38272
+ canonicalProjectNames.set(normalizedProjectName.toLowerCase(), normalizedProjectName);
38273
+ }
38274
+ }
38275
+ return canonicalProjectNames;
38276
+ }
38277
+ /**
38278
+ * Collects the texts of every tool invocation described by one runtime log event.
38279
+ *
38280
+ * Claude Code reports tool invocations as `tool_use` content blocks, while Codex reports executed
38281
+ * commands and file changes as items — both carry the paths the harness worked with.
38282
+ *
38283
+ * @param event - One structured runtime log event.
38284
+ * @returns Texts that may contain project paths.
38285
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38286
+ */
38287
+ function resolveRuntimeLogEventToolInvocationTexts(event) {
38288
+ var _a, _b, _c;
38289
+ const toolInvocationTexts = [];
38290
+ for (const contentBlock of ((_a = event.message) === null || _a === void 0 ? void 0 : _a.content) || []) {
38291
+ if (contentBlock.type === 'tool_use' && contentBlock.input) {
38292
+ toolInvocationTexts.push(serializeToolInput(contentBlock.input));
38293
+ }
38294
+ }
38295
+ if (typeof ((_b = event.item) === null || _b === void 0 ? void 0 : _b.command) === 'string') {
38296
+ toolInvocationTexts.push(event.item.command);
38297
+ }
38298
+ for (const fileChange of ((_c = event.item) === null || _c === void 0 ? void 0 : _c.changes) || []) {
38299
+ if (typeof fileChange.path === 'string') {
38300
+ toolInvocationTexts.push(fileChange.path);
38301
+ }
38302
+ }
38303
+ return toolInvocationTexts;
38304
+ }
38305
+ /**
38306
+ * Serializes one tool input payload into scannable text.
38307
+ *
38308
+ * @param toolInput - Tool arguments of one harness tool invocation.
38309
+ * @returns Serialized payload, or an empty string when it cannot be serialized.
38310
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38311
+ */
38312
+ function serializeToolInput(toolInput) {
38313
+ try {
38314
+ return JSON.stringify(toolInput);
38315
+ }
38316
+ catch (_a) {
38317
+ return '';
38318
+ }
38319
+ }
38320
+ /**
38321
+ * Extracts every project directory name mentioned right after the agent `projects/` folder.
38322
+ *
38323
+ * @param toolInvocationText - One tool payload, command, or changed path.
38324
+ * @returns Candidate project directory names.
38325
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38326
+ */
38327
+ function resolveCandidateProjectNames(toolInvocationText) {
38328
+ const candidateProjectNames = [];
38329
+ const projectPathRegex = new RegExp(TOUCHED_PROJECT_PATH_REGEX.source, TOUCHED_PROJECT_PATH_REGEX.flags);
38330
+ let projectPathMatch = projectPathRegex.exec(toolInvocationText);
38331
+ while (projectPathMatch !== null) {
38332
+ const candidateProjectName = projectPathMatch[1];
38333
+ if (candidateProjectName) {
38334
+ candidateProjectNames.push(candidateProjectName);
38335
+ }
38336
+ projectPathMatch = projectPathRegex.exec(toolInvocationText);
38337
+ }
38338
+ return candidateProjectNames;
38339
+ }
38340
+
38341
+ /**
38342
+ * Resolves which agent projects one answered message viewed or edited.
38343
+ *
38344
+ * The runtime log is deleted as soon as the harness run finishes, so this must be called while
38345
+ * the log still exists — the Agents Server later shows the reported projects as chips below the
38346
+ * answer. Reporting touched projects is best-effort telemetry: an unreadable log or projects
38347
+ * folder simply yields no projects instead of failing the already answered message.
38348
+ *
38349
+ * @param options - Agent folder path and the live runtime log path of the answered message.
38350
+ * @returns Touched project directory names, ordered by first appearance in the run.
38351
+ */
38352
+ async function resolveTouchedAgentProjects(options) {
38353
+ const [logText, knownProjectNames] = await Promise.all([
38354
+ readOptionalTextFile(options.runtimeLogPath),
38355
+ listAgentProjectDirectoryNames(options.projectPath),
38356
+ ]);
38357
+ return resolveAgentMessageTouchedProjectNames({ logText, knownProjectNames });
38358
+ }
38359
+ /**
38360
+ * Lists the direct project directories of one agent folder.
38361
+ *
38362
+ * @param projectPath - Absolute path of the local agent folder.
38363
+ * @returns Project directory names, or an empty list when the agent has no projects folder.
38364
+ *
38365
+ * @private helper of `resolveTouchedAgentProjects`
38366
+ */
38367
+ async function listAgentProjectDirectoryNames(projectPath) {
38368
+ try {
38369
+ const projectsRootEntries = await promises.readdir(path.join(projectPath, AGENT_PROJECTS_DIRECTORY_PATH), {
38370
+ withFileTypes: true,
38371
+ });
38372
+ return projectsRootEntries
38373
+ .filter((projectsRootEntry) => projectsRootEntry.isDirectory())
38374
+ .map((projectDirectoryEntry) => projectDirectoryEntry.name);
38375
+ }
38376
+ catch (_a) {
38377
+ return [];
38378
+ }
38379
+ }
38380
+ /**
38381
+ * Reads one text file and treats a missing runtime log as "nothing was recorded".
38382
+ *
38383
+ * @param filePath - Absolute path of the file to read.
38384
+ * @returns File content, or `null` when it cannot be read.
38385
+ *
38386
+ * @private helper of `resolveTouchedAgentProjects`
38387
+ */
38388
+ async function readOptionalTextFile(filePath) {
38389
+ try {
38390
+ return await promises.readFile(filePath, 'utf-8');
38391
+ }
38392
+ catch (_a) {
38393
+ return null;
38394
+ }
38395
+ }
38396
+
37853
38397
  // Note: [💞] Ignore a discrepancy between file name and entity name
37854
38398
  /**
37855
38399
  * File suffix appended to one answered message book to store its run report sidecar.
@@ -38129,10 +38673,11 @@
38129
38673
  const { projectPath, options: runOptions, runner, actualRunnerModel, queuedMessage, uiHandle, isSharedDashboard, } = options;
38130
38674
  const agentSystemMessage = await loadLocalAgentSystemMessage(projectPath);
38131
38675
  const teamWorkspace = await loadAgentTeamConversationWorkspace(projectPath, queuedMessage);
38676
+ const plannedMessagesSidecar = await loadAgentPlannedMessagesSidecar(projectPath, queuedMessage);
38132
38677
  const agentServerRuntimeApi = resolveAgentProjectRuntimePromptApi(projectPath);
38133
38678
  const prompt = buildAgentMessagePrompt(queuedMessage.relativePath, agentSystemMessage, {
38134
38679
  projectRuntimeApi: agentServerRuntimeApi,
38135
- goalChatRuntimeApi: agentServerRuntimeApi,
38680
+ plannedMessagesSidecar: plannedMessagesSidecar || undefined,
38136
38681
  projectsUrlPath: resolveAgentProjectsUrlPath(projectPath),
38137
38682
  teamWorkspace: teamWorkspace || undefined,
38138
38683
  });
@@ -38153,21 +38698,29 @@
38153
38698
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentScriptPath(scriptPath);
38154
38699
  }
38155
38700
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
38701
+ const executionStartedAt = new Date().toISOString();
38156
38702
  let promptRunResult;
38703
+ let touchedProjectNames = [];
38157
38704
  try {
38158
38705
  try {
38159
- promptRunResult = await withPromptRuntimeLog(scriptPath, async (logPath) => await runPromptWithTestFeedback({
38160
- runner,
38161
- prompt,
38162
- scriptPath,
38163
- projectPath,
38164
- promptLabel: queuedMessage.relativePath,
38165
- logPath,
38166
- preserveArtifactsOnSuccess: false,
38167
- onAttemptStarted: (attemptCount) => {
38168
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(attemptCount);
38169
- },
38170
- }), { preserveArtifactsOnSuccess: false });
38706
+ promptRunResult = await withPromptRuntimeLog(scriptPath, async (logPath) => {
38707
+ const runResult = await runPromptWithTestFeedback({
38708
+ runner,
38709
+ prompt,
38710
+ scriptPath,
38711
+ projectPath,
38712
+ promptLabel: queuedMessage.relativePath,
38713
+ logPath,
38714
+ preserveArtifactsOnSuccess: false,
38715
+ onAttemptStarted: (attemptCount) => {
38716
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(attemptCount);
38717
+ },
38718
+ });
38719
+ // Note: The runtime log is deleted right after this handler, so the projects this
38720
+ // answer worked with must be resolved while the log still exists.
38721
+ touchedProjectNames = await resolveTouchedAgentProjects({ projectPath, runtimeLogPath: logPath });
38722
+ return runResult;
38723
+ }, { preserveArtifactsOnSuccess: false });
38171
38724
  }
38172
38725
  catch (error) {
38173
38726
  throw withAgentWatchErrorContext(error, {
@@ -38181,6 +38734,7 @@
38181
38734
  finally {
38182
38735
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
38183
38736
  }
38737
+ const executionFinishedAt = new Date().toISOString();
38184
38738
  await normalizeLineEndingsForAgentRound(projectPath, runOptions, roundChangedFilesSnapshot);
38185
38739
  const finishedTeamWorkspace = await finalizeAgentTeamConversationWorkspace({
38186
38740
  projectPath,
@@ -38199,6 +38753,11 @@
38199
38753
  modelName: actualRunnerModel,
38200
38754
  loginMethod: promptRunResult.loginMethod,
38201
38755
  usage: promptRunResult.usage,
38756
+ executionTiming: {
38757
+ startedAt: executionStartedAt,
38758
+ finishedAt: executionFinishedAt,
38759
+ },
38760
+ ...(touchedProjectNames.length === 0 ? {} : { touchedProjectNames }),
38202
38761
  },
38203
38762
  });
38204
38763
  await commitAnsweredMessageIfEnabled({