@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/esm/index.es.js CHANGED
@@ -48,7 +48,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
48
48
  * @generated
49
49
  * @see https://github.com/webgptorg/promptbook
50
50
  */
51
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-12';
51
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-13';
52
52
  /**
53
53
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
54
54
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -4088,10 +4088,6 @@ const PTBK_AGENTS_SERVER_URL_ENV = 'PTBK_AGENTS_SERVER_URL';
4088
4088
  * Environment variable containing the internal worker token for local agent runners.
4089
4089
  */
4090
4090
  const PTBK_AGENTS_SERVER_USER_CHAT_WORKER_TOKEN_ENV = 'PTBK_AGENTS_SERVER_USER_CHAT_WORKER_TOKEN';
4091
- /**
4092
- * Header used by internal local-runner routes.
4093
- */
4094
- const USER_CHAT_WORKER_TOKEN_HEADER = 'x-user-chat-worker-token';
4095
4091
 
4096
4092
  /**
4097
4093
  * Format either small or big number
@@ -21882,7 +21878,7 @@ async function isAgentRunnerProject(projectPath) {
21882
21878
  return agentBookStats.isFile();
21883
21879
  }
21884
21880
  catch (error) {
21885
- if (isFileNotFoundError$6(error)) {
21881
+ if (isFileNotFoundError$7(error)) {
21886
21882
  return false;
21887
21883
  }
21888
21884
  throw error;
@@ -21891,7 +21887,7 @@ async function isAgentRunnerProject(projectPath) {
21891
21887
  /**
21892
21888
  * Returns true when one filesystem error indicates a missing path.
21893
21889
  */
21894
- function isFileNotFoundError$6(error) {
21890
+ function isFileNotFoundError$7(error) {
21895
21891
  return Boolean(error &&
21896
21892
  typeof error === 'object' &&
21897
21893
  'code' in error &&
@@ -21921,7 +21917,7 @@ async function readQueuedMessageDirectoryEntries(queuedMessagesDirectoryPath) {
21921
21917
  return await readdir(queuedMessagesDirectoryPath, { withFileTypes: true });
21922
21918
  }
21923
21919
  catch (error) {
21924
- if (isFileNotFoundError$5(error)) {
21920
+ if (isFileNotFoundError$6(error)) {
21925
21921
  return [];
21926
21922
  }
21927
21923
  throw error;
@@ -21942,7 +21938,7 @@ function normalizeRelativePath$2(relativePath) {
21942
21938
  /**
21943
21939
  * Returns true when an error is a missing-path filesystem error.
21944
21940
  */
21945
- function isFileNotFoundError$5(error) {
21941
+ function isFileNotFoundError$6(error) {
21946
21942
  return Boolean(error &&
21947
21943
  typeof error === 'object' &&
21948
21944
  'code' in error &&
@@ -22420,7 +22416,7 @@ async function runGitCommand(options) {
22420
22416
  lastIndexLockState = await readGitIndexLockState(cwd, options.env);
22421
22417
  if ((lastIndexLockState === null || lastIndexLockState === void 0 ? void 0 : lastIndexLockState.isStale) && !isStaleIndexLockRemoved) {
22422
22418
  await unlink(lastIndexLockState.path).catch((unlinkError) => {
22423
- if (isFileNotFoundError$4(unlinkError)) {
22419
+ if (isFileNotFoundError$5(unlinkError)) {
22424
22420
  return;
22425
22421
  }
22426
22422
  throw unlinkError;
@@ -22481,7 +22477,7 @@ async function readGitIndexLockState(cwd, env) {
22481
22477
  };
22482
22478
  }
22483
22479
  catch (error) {
22484
- if (isFileNotFoundError$4(error)) {
22480
+ if (isFileNotFoundError$5(error)) {
22485
22481
  return undefined;
22486
22482
  }
22487
22483
  throw error;
@@ -22538,7 +22534,7 @@ function formatDelay$1(delayMs) {
22538
22534
  /**
22539
22535
  * Detects missing-file errors while inspecting the lock file.
22540
22536
  */
22541
- function isFileNotFoundError$4(error) {
22537
+ function isFileNotFoundError$5(error) {
22542
22538
  return Boolean(error &&
22543
22539
  typeof error === 'object' &&
22544
22540
  'code' in error &&
@@ -29614,7 +29610,7 @@ async function readWorkingTreeFileHash(projectPath, relativePath) {
29614
29610
  return createHash('sha1').update(content).digest('hex');
29615
29611
  }
29616
29612
  catch (error) {
29617
- if (isFileNotFoundError$3(error)) {
29613
+ if (isFileNotFoundError$4(error)) {
29618
29614
  return null;
29619
29615
  }
29620
29616
  throw error;
@@ -29629,7 +29625,7 @@ function normalizeGitFilePath(filePath) {
29629
29625
  /**
29630
29626
  * Returns true when an error is a missing-file filesystem error.
29631
29627
  */
29632
- function isFileNotFoundError$3(error) {
29628
+ function isFileNotFoundError$4(error) {
29633
29629
  return Boolean(error &&
29634
29630
  typeof error === 'object' &&
29635
29631
  'code' in error &&
@@ -30622,7 +30618,7 @@ function buildAgentProjectsPromptSection(options = {}) {
30622
30618
  - 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.
30623
30619
  - 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.
30624
30620
  - A project can be a git repository — you can run \`git init\` inside a project folder and commit your work there.
30625
- - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory except the queued message file you are answering.
30621
+ - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory beyond the file changes allowed at the top of this prompt.
30626
30622
 
30627
30623
  ${block(buildProjectReferenceInstructions(projectsUrlPath))}
30628
30624
  ${block(buildProjectRuntimeInstructions(options.projectRuntimeApi))}
@@ -30678,55 +30674,166 @@ function buildProjectRuntimeInstructions(projectRuntimeApi) {
30678
30674
  }
30679
30675
 
30680
30676
  /**
30681
- * Title stored on every agent goal chat.
30677
+ * Milliseconds in one second.
30678
+ *
30679
+ * @private internal timeout-chat constant
30680
+ */
30681
+ const SECOND_IN_MILLISECONDS = 1000;
30682
+ /**
30683
+ * Seconds in one minute.
30684
+ *
30685
+ * @private internal timeout-chat constant
30686
+ */
30687
+ const MINUTE_IN_SECONDS = 60;
30688
+ /**
30689
+ * Seconds in one hour.
30690
+ *
30691
+ * @private internal timeout-chat constant
30692
+ */
30693
+ const HOUR_IN_SECONDS = 60 * MINUTE_IN_SECONDS;
30694
+ /**
30695
+ * Seconds in one day.
30696
+ *
30697
+ * @private internal timeout-chat constant
30698
+ */
30699
+ const HOURS_IN_DAY = 24;
30700
+ /**
30701
+ * Seconds in one day.
30702
+ *
30703
+ * @private internal timeout-chat constant
30704
+ */
30705
+ const DAY_IN_SECONDS = HOURS_IN_DAY * HOUR_IN_SECONDS;
30706
+ /**
30707
+ * Converts timeout duration into natural language sentence fragments.
30708
+ *
30709
+ * Shared by every timeout surface: the chat chips, the goal-chat planned messages of the Agents
30710
+ * Server, and the planned-message prompt section of the agent-folder runner.
30711
+ *
30712
+ * @param milliseconds - Duration or repeat interval in milliseconds.
30713
+ * @returns Label such as `5 minutes` or `1 hour 30 minutes`.
30714
+ *
30715
+ * @private internal utility of `<Chat/>` and the Agents Server planned messages
30682
30716
  */
30717
+ function formatTimeoutDurationHuman(milliseconds) {
30718
+ const normalizedMilliseconds = Math.max(0, Math.floor(milliseconds));
30719
+ const totalSeconds = Math.max(1, Math.floor(normalizedMilliseconds / SECOND_IN_MILLISECONDS));
30720
+ if (totalSeconds < MINUTE_IN_SECONDS) {
30721
+ return formatTimeoutUnit(totalSeconds, 'second');
30722
+ }
30723
+ if (totalSeconds < HOUR_IN_SECONDS) {
30724
+ const roundedMinutes = Math.max(1, Math.round(totalSeconds / MINUTE_IN_SECONDS));
30725
+ return formatTimeoutUnit(roundedMinutes, 'minute');
30726
+ }
30727
+ if (totalSeconds < DAY_IN_SECONDS) {
30728
+ const hours = Math.floor(totalSeconds / HOUR_IN_SECONDS);
30729
+ const minutes = Math.floor((totalSeconds % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS);
30730
+ if (minutes === 0) {
30731
+ return formatTimeoutUnit(hours, 'hour');
30732
+ }
30733
+ return `${formatTimeoutUnit(hours, 'hour')} ${formatTimeoutUnit(minutes, 'minute')}`;
30734
+ }
30735
+ const days = Math.floor(totalSeconds / DAY_IN_SECONDS);
30736
+ const hours = Math.floor((totalSeconds % DAY_IN_SECONDS) / HOUR_IN_SECONDS);
30737
+ if (hours === 0) {
30738
+ return formatTimeoutUnit(days, 'day');
30739
+ }
30740
+ return `${formatTimeoutUnit(days, 'day')} ${formatTimeoutUnit(hours, 'hour')}`;
30741
+ }
30683
30742
  /**
30684
- * Internal endpoint used by managed coding agents to plan goal-chat messages.
30743
+ * Formats one pluralized timeout unit.
30744
+ *
30745
+ * @private internal timeout-chat helper
30685
30746
  */
30686
- const AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH = '/api/internal/agent-goal-chat-planned-messages';
30747
+ function formatTimeoutUnit(value, unit) {
30748
+ return `${value} ${unit}${value === 1 ? '' : 's'}`;
30749
+ }
30750
+ // Note: [💞] Ignore a discrepancy between file name and entity name
30687
30751
 
30688
30752
  /**
30689
30753
  * Builds the planned-message instructions available to an Agents Server-managed coding agent.
30690
30754
  *
30691
- * Planned messages always target the agent's singleton goal chat, regardless of the chat in which
30692
- * the command is invoked.
30755
+ * Planned messages always target the agent's singleton goal chat, regardless of the chat in which the
30756
+ * wake-up is planned, and they repeat like `setInterval` until they are cancelled. The sidecar file is
30757
+ * the only channel that changes them, so an answer that merely claims a follow-up was planned changes
30758
+ * nothing — and an answer that changes nothing keeps the current plan running.
30759
+ *
30760
+ * @param sidecar - Sidecar prepared by the Agents Server for the answered message.
30761
+ * @returns Prompt section, or an empty string outside Agents Server-managed runs.
30693
30762
  */
30694
- function buildAgentGoalChatPromptSection(runtimeApi) {
30695
- if (!runtimeApi) {
30763
+ function buildAgentGoalChatPromptSection(sidecar) {
30764
+ if (!sidecar) {
30696
30765
  return '';
30697
30766
  }
30698
- return spaceTrim$1(`
30699
- ## Planned goal-chat messages
30767
+ return spaceTrim$1((block) => `
30768
+ ## Planned goal-chat messages
30700
30769
 
30701
- 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.
30770
+ 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.
30702
30771
 
30703
- - Use \`list_timeouts\` before planning follow-up work so you do not create duplicates.
30704
- - 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.
30705
- - Use \`cancel_timeout\` with the returned \`timeoutId\` when a planned message is no longer useful.
30706
- - 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.
30772
+ ${block(buildCurrentPlannedMessagesLines(sidecar.currentPlannedMessages))}
30707
30773
 
30708
- Run the corresponding authenticated command:
30774
+ Editing \`${sidecar.relativeSidecarPath}\` is the **only** way to change what wakes you. Writing in your answer that you scheduled something does not schedule anything.
30709
30775
 
30710
- \`\`\`bash
30711
- # list_timeouts
30712
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30713
- -H "Content-Type: application/json" \\
30714
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30715
- -d '{"action":"list","agentPermanentId":"${runtimeApi.agentPermanentId}"}'
30776
+ - **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.
30777
+ - 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.
30778
+ - 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.
30779
+ - When your goal no longer needs a listed message at all, cancel it without planning a replacement.
30780
+ - Never edit \`version\`, \`agentPermanentId\`, or \`currentPlannedMessages\`.
30781
+ - The Agents Server applies every command once your answer is finished, and each repetition appears in the goal chat and invokes you there.
30782
+ - 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.
30783
+ `);
30784
+ }
30785
+ /**
30786
+ * Renders the already planned messages so the agent can compare them with its goal.
30787
+ *
30788
+ * @param currentPlannedMessages - Planned messages prepared by the Agents Server.
30789
+ * @returns Markdown list, or a sentence stating that nothing is planned yet.
30790
+ *
30791
+ * @private function of `buildAgentGoalChatPromptSection`
30792
+ */
30793
+ function buildCurrentPlannedMessagesLines(currentPlannedMessages) {
30794
+ if (currentPlannedMessages.length === 0) {
30795
+ return 'You currently have no planned messages waiting.';
30796
+ }
30797
+ const plannedMessageLines = currentPlannedMessages.map(createCurrentPlannedMessageLine);
30798
+ return spaceTrim$1((block) => `
30799
+ These planned messages are already waiting for you, so keep them unless they stopped matching your goal:
30716
30800
 
30717
- # set_timeout
30718
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30719
- -H "Content-Type: application/json" \\
30720
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30721
- -d '{"action":"set","agentPermanentId":"${runtimeApi.agentPermanentId}","milliseconds":3600000,"message":"Continue the current goal and decide the next concrete action."}'
30801
+ ${block(plannedMessageLines.join('\n'))}
30802
+ `);
30803
+ }
30804
+ /**
30805
+ * Renders one already planned message with the schedule the agent has to compare with its goal.
30806
+ *
30807
+ * @param plannedMessage - One planned message prepared by the Agents Server.
30808
+ * @returns Markdown list item.
30809
+ *
30810
+ * @private function of `buildAgentGoalChatPromptSection`
30811
+ */
30812
+ function createCurrentPlannedMessageLine(plannedMessage) {
30813
+ var _a;
30814
+ const message = ((_a = plannedMessage.message) === null || _a === void 0 ? void 0 : _a.trim()) || 'Continue working towards the current goal.';
30815
+ const schedule = plannedMessage.intervalMs
30816
+ ? `repeats every ${formatTimeoutDurationHuman(plannedMessage.intervalMs)}`
30817
+ : `wakes you once at ${plannedMessage.dueAt}`;
30818
+ return `- \`${plannedMessage.timeoutId}\` ${schedule}: ${message}`;
30819
+ }
30722
30820
 
30723
- # cancel_timeout
30724
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
30725
- -H "Content-Type: application/json" \\
30726
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
30727
- -d '{"action":"cancel","agentPermanentId":"${runtimeApi.agentPermanentId}","timeoutId":"<timeout-id>"}'
30728
- \`\`\`
30729
- `);
30821
+ /**
30822
+ * Creates the stable file-system safe base name that belongs to one queued `.book` message.
30823
+ *
30824
+ * Every sidecar the Agents Server prepares next to a queued message (TEAM workspaces, planned-message
30825
+ * files, ...) derives its own name from this one normalization, so a sidecar can always be found again
30826
+ * from the message file name alone.
30827
+ *
30828
+ * @param messageFileName - File name of the queued message, with or without directories.
30829
+ * @returns Normalized base name usable as a directory or file name.
30830
+ *
30831
+ * @private internal convention shared by the Agents Server and agent-folder runner
30832
+ */
30833
+ function createAgentMessageSidecarBaseName(messageFileName) {
30834
+ const rawBaseName = basename(messageFileName).replace(/\.book$/iu, '');
30835
+ const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
30836
+ return normalizedBaseName || 'message';
30730
30837
  }
30731
30838
 
30732
30839
  // Note: [💞] This file defines the shared TEAM workspace convention rather than one standalone entity.
@@ -30760,9 +30867,7 @@ const AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME = 'teammates';
30760
30867
  * @private internal convention shared by the Agents Server and agent-folder runner
30761
30868
  */
30762
30869
  function createAgentTeamConversationWorkspaceDirectoryName(messageFileName) {
30763
- const rawBaseName = basename(messageFileName).replace(/\.book$/iu, '');
30764
- const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
30765
- return normalizedBaseName || 'message';
30870
+ return createAgentMessageSidecarBaseName(messageFileName);
30766
30871
  }
30767
30872
  /**
30768
30873
  * Creates the relative active workspace path for one queued message.
@@ -30832,8 +30937,8 @@ function buildAgentTeamPromptSection(workspace) {
30832
30937
  return '';
30833
30938
  }
30834
30939
  const teammateSections = workspace.manifest.teammates.map((teammate) => {
30835
- const sourcePath = toPromptPath(join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
30836
- const transcriptPath = toPromptPath(join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
30940
+ const sourcePath = toPromptPath$1(join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
30941
+ const transcriptPath = toPromptPath$1(join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
30837
30942
  const instructions = teammate.instructions.trim() || 'No additional TEAM instructions were provided.';
30838
30943
  return spaceTrim$1(`
30839
30944
  - ${teammate.agentName}
@@ -30851,7 +30956,7 @@ function buildAgentTeamPromptSection(workspace) {
30851
30956
 
30852
30957
  - 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.
30853
30958
  - The primary agent remains responsible for the final user-facing \`MESSAGE @Agent\` answer.
30854
- - 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)}\`.
30959
+ - 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)}\`.
30855
30960
  - Name each transcript \`<teammate permanent id>--<sequence>.book\`, for example \`${workspace.manifest.teammates[0].permanentId}--01.book\`.
30856
30961
  - 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.
30857
30962
  `);
@@ -30859,7 +30964,7 @@ function buildAgentTeamPromptSection(workspace) {
30859
30964
  /**
30860
30965
  * Converts a filesystem-relative path into the portable path notation used in prompts.
30861
30966
  */
30862
- function toPromptPath(path) {
30967
+ function toPromptPath$1(path) {
30863
30968
  return path.replace(/\\/gu, '/');
30864
30969
  }
30865
30970
 
@@ -30872,7 +30977,7 @@ function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage, option
30872
30977
 
30873
30978
  - 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\`.
30874
30979
  - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
30875
- ${block(buildAllowedFileChangesPromptLine(options.teamWorkspace))}
30980
+ ${block(buildAllowedFileChangesPromptLine(options))}
30876
30981
 
30877
30982
  ## Rules for the answering
30878
30983
 
@@ -30908,7 +31013,7 @@ function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage, option
30908
31013
 
30909
31014
  - 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.
30910
31015
 
30911
- ${block(buildAgentGoalChatPromptSection(options.goalChatRuntimeApi))}
31016
+ ${block(buildAgentGoalChatPromptSection(options.plannedMessagesSidecar))}
30912
31017
 
30913
31018
  ${block(buildAgentProjectsPromptSection(options))}
30914
31019
 
@@ -30922,11 +31027,30 @@ function buildAgentMessagePrompt(messageRelativePath, agentSystemMessage, option
30922
31027
  /**
30923
31028
  * Explains the exact files that one coding harness may change for a user turn.
30924
31029
  */
30925
- function buildAllowedFileChangesPromptLine(teamWorkspace) {
30926
- if (!teamWorkspace) {
30927
- return `- Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`;
31030
+ function buildAllowedFileChangesPromptLine(options) {
31031
+ const allowedFileChanges = [`files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`];
31032
+ if (options.teamWorkspace) {
31033
+ allowedFileChanges.push(`new consultation transcripts inside \`${toPromptPath(options.teamWorkspace.relativeWorkspacePath)}\``);
31034
+ }
31035
+ if (options.plannedMessagesSidecar) {
31036
+ allowedFileChanges.push(`the \`commands\` array of \`${toPromptPath(options.plannedMessagesSidecar.relativeSidecarPath)}\``);
31037
+ }
31038
+ return `- Do not modify any other file in the repository, except ${formatAllowedFileChanges(allowedFileChanges)}`;
31039
+ }
31040
+ /**
31041
+ * Joins the allowed file-change phrases into one readable enumeration.
31042
+ */
31043
+ function formatAllowedFileChanges(allowedFileChanges) {
31044
+ if (allowedFileChanges.length === 1) {
31045
+ return allowedFileChanges[0];
30928
31046
  }
30929
- 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, '/')}\``;
31047
+ return `${allowedFileChanges.slice(0, -1).join(', ')} and ${allowedFileChanges[allowedFileChanges.length - 1]}`;
31048
+ }
31049
+ /**
31050
+ * Converts a filesystem-relative path into the portable path notation used in prompts.
31051
+ */
31052
+ function toPromptPath(path) {
31053
+ return path.replace(/\\/gu, '/');
30930
31054
  }
30931
31055
 
30932
31056
  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"}];
@@ -37680,12 +37804,12 @@ async function finalizeAgentTeamConversationWorkspace(options) {
37680
37804
  await rm(finishedWorkspacePath, { recursive: true, force: true });
37681
37805
  await mkdir(finishedWorkspacePath, { recursive: true });
37682
37806
  const relativePaths = [
37683
- toPortablePath$1(join(finishedRelativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME)),
37807
+ toPortablePath$2(join(finishedRelativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME)),
37684
37808
  ];
37685
37809
  await rename(activeManifestPath, join(finishedWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME));
37686
37810
  for (const transcriptFileName of transcriptFileNames) {
37687
37811
  await rename(join(activeWorkspacePath, transcriptFileName), join(finishedWorkspacePath, transcriptFileName));
37688
- relativePaths.push(toPortablePath$1(join(finishedRelativeWorkspacePath, transcriptFileName)));
37812
+ relativePaths.push(toPortablePath$2(join(finishedRelativeWorkspacePath, transcriptFileName)));
37689
37813
  }
37690
37814
  await rm(activeWorkspacePath, { recursive: true, force: true });
37691
37815
  return { relativePaths };
@@ -37702,7 +37826,7 @@ async function listAgentTeamTranscriptFileNames(activeWorkspacePath) {
37702
37826
  .sort((firstFileName, secondFileName) => firstFileName.localeCompare(secondFileName));
37703
37827
  }
37704
37828
  catch (error) {
37705
- if (isFileNotFoundError$2(error)) {
37829
+ if (isFileNotFoundError$3(error)) {
37706
37830
  return [];
37707
37831
  }
37708
37832
  throw error;
@@ -37717,7 +37841,7 @@ async function isExistingPath$1(path) {
37717
37841
  return true;
37718
37842
  }
37719
37843
  catch (error) {
37720
- if (isFileNotFoundError$2(error)) {
37844
+ if (isFileNotFoundError$3(error)) {
37721
37845
  return false;
37722
37846
  }
37723
37847
  throw error;
@@ -37726,11 +37850,204 @@ async function isExistingPath$1(path) {
37726
37850
  /**
37727
37851
  * Converts a filesystem-relative path into a portable Git path.
37728
37852
  */
37853
+ function toPortablePath$2(path) {
37854
+ return path.replace(/\\/gu, '/');
37855
+ }
37856
+ /**
37857
+ * Returns true when one filesystem error indicates a missing path.
37858
+ */
37859
+ function isFileNotFoundError$3(error) {
37860
+ return Boolean(error &&
37861
+ typeof error === 'object' &&
37862
+ 'code' in error &&
37863
+ (error.code === 'ENOENT' || error.code === 'ENOTDIR'));
37864
+ }
37865
+
37866
+ // Note: [💞] This file defines the shared planned-message sidecar convention rather than one standalone entity.
37867
+ /**
37868
+ * Relative directory holding the planned-message sidecar of each queued message.
37869
+ *
37870
+ * @private internal convention shared by the Agents Server and agent-folder runner
37871
+ */
37872
+ const AGENT_PLANNED_MESSAGES_DIRECTORY_PATH = join(AGENT_MESSAGES_DIRECTORY_PATH, 'planned');
37873
+ /**
37874
+ * Command actions an agent can request from its planned-message sidecar.
37875
+ *
37876
+ * @private internal convention shared by the Agents Server and agent-folder runner
37877
+ */
37878
+ const AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS = ['set', 'cancel'];
37879
+ /**
37880
+ * Creates the relative planned-message sidecar path of one queued message.
37881
+ *
37882
+ * @param messageFileName - File name of the queued `.book` message.
37883
+ * @returns Relative path of the sidecar inside the agent folder.
37884
+ *
37885
+ * @private internal convention shared by the Agents Server and agent-folder runner
37886
+ */
37887
+ function createAgentPlannedMessagesSidecarPath(messageFileName) {
37888
+ return join(AGENT_PLANNED_MESSAGES_DIRECTORY_PATH, `${createAgentMessageSidecarBaseName(messageFileName)}.json`);
37889
+ }
37890
+ /**
37891
+ * Parses one untrusted planned-message sidecar written back by a coding harness.
37892
+ *
37893
+ * A malformed sidecar never fails the already answered chat, so this returns `null` instead of throwing.
37894
+ *
37895
+ * @param sidecarContent - Raw file content read from the agent folder.
37896
+ * @returns Normalized sidecar, or `null` when it cannot be used.
37897
+ *
37898
+ * @private internal convention shared by the Agents Server and agent-folder runner
37899
+ */
37900
+ function parseAgentPlannedMessagesSidecar(sidecarContent) {
37901
+ let parsedSidecar;
37902
+ try {
37903
+ parsedSidecar = JSON.parse(sidecarContent);
37904
+ }
37905
+ catch (_a) {
37906
+ return null;
37907
+ }
37908
+ if (!parsedSidecar || typeof parsedSidecar !== 'object' || Array.isArray(parsedSidecar)) {
37909
+ return null;
37910
+ }
37911
+ const sidecar = parsedSidecar;
37912
+ if (sidecar.version !== 1 || typeof sidecar.agentPermanentId !== 'string') {
37913
+ return null;
37914
+ }
37915
+ return {
37916
+ version: 1,
37917
+ agentPermanentId: sidecar.agentPermanentId,
37918
+ currentPlannedMessages: normalizeAgentPlannedMessageSnapshots(sidecar.currentPlannedMessages),
37919
+ commands: normalizeAgentPlannedMessageCommands(sidecar.commands),
37920
+ };
37921
+ }
37922
+ /**
37923
+ * Keeps only the commands whose action is understood by the planned-message actions.
37924
+ *
37925
+ * @param rawCommands - Untrusted `commands` value read from one sidecar.
37926
+ * @returns Commands that can be dispatched, in their original order.
37927
+ *
37928
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37929
+ */
37930
+ function normalizeAgentPlannedMessageCommands(rawCommands) {
37931
+ if (!Array.isArray(rawCommands)) {
37932
+ return [];
37933
+ }
37934
+ return rawCommands.filter(isAgentPlannedMessageCommand);
37935
+ }
37936
+ /**
37937
+ * Checks whether one untrusted sidecar entry names a supported planned-message action.
37938
+ *
37939
+ * @param value - Untrusted command entry.
37940
+ * @returns `true` when the entry can be dispatched.
37941
+ *
37942
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37943
+ */
37944
+ function isAgentPlannedMessageCommand(value) {
37945
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
37946
+ return false;
37947
+ }
37948
+ const action = value.action;
37949
+ return (typeof action === 'string' &&
37950
+ AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS.includes(action));
37951
+ }
37952
+ /**
37953
+ * Keeps only the already planned messages that can be shown to the agent.
37954
+ *
37955
+ * @param rawSnapshots - Untrusted `currentPlannedMessages` value read from one sidecar.
37956
+ * @returns Planned-message snapshots with the fields required for display and cancellation.
37957
+ *
37958
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37959
+ */
37960
+ function normalizeAgentPlannedMessageSnapshots(rawSnapshots) {
37961
+ if (!Array.isArray(rawSnapshots)) {
37962
+ return [];
37963
+ }
37964
+ return rawSnapshots.filter(isAgentPlannedMessageSnapshot).map(createNormalizedAgentPlannedMessageSnapshot);
37965
+ }
37966
+ /**
37967
+ * Normalizes the optional fields of one already planned message.
37968
+ *
37969
+ * @param snapshot - Planned-message entry with the required identity fields.
37970
+ * @returns Snapshot with a usable repeat interval and message.
37971
+ *
37972
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37973
+ */
37974
+ function createNormalizedAgentPlannedMessageSnapshot(snapshot) {
37975
+ const intervalMs = Number(snapshot.intervalMs);
37976
+ return {
37977
+ timeoutId: snapshot.timeoutId,
37978
+ dueAt: snapshot.dueAt,
37979
+ message: typeof snapshot.message === 'string' ? snapshot.message : null,
37980
+ intervalMs: Number.isFinite(intervalMs) && intervalMs > 0 ? Math.floor(intervalMs) : null,
37981
+ };
37982
+ }
37983
+ /**
37984
+ * Checks whether one untrusted sidecar entry describes an already planned message.
37985
+ *
37986
+ * @param value - Untrusted planned-message entry.
37987
+ * @returns `true` when the entry can be shown and cancelled.
37988
+ *
37989
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
37990
+ */
37991
+ function isAgentPlannedMessageSnapshot(value) {
37992
+ return Boolean(value &&
37993
+ typeof value === 'object' &&
37994
+ typeof value.timeoutId === 'string' &&
37995
+ typeof value.dueAt === 'string');
37996
+ }
37997
+
37998
+ /**
37999
+ * Loads the planned-message sidecar the Agents Server prepared for one queued message.
38000
+ *
38001
+ * @param projectPath - Absolute path of the current agent runner folder.
38002
+ * @param queuedMessage - Queued message currently being answered.
38003
+ * @returns Sidecar details for the prompt, or `null` outside Agents Server-managed runs.
38004
+ */
38005
+ async function loadAgentPlannedMessagesSidecar(projectPath, queuedMessage) {
38006
+ const relativeSidecarPath = createAgentPlannedMessagesSidecarPath(queuedMessage.fileName);
38007
+ const sidecarContent = await readOptionalTextFile$2(join(projectPath, relativeSidecarPath));
38008
+ if (sidecarContent === null) {
38009
+ return null;
38010
+ }
38011
+ const sidecar = parseAgentPlannedMessagesSidecar(sidecarContent);
38012
+ if (!sidecar) {
38013
+ return null;
38014
+ }
38015
+ return {
38016
+ relativeSidecarPath: toPortablePath$1(relativeSidecarPath),
38017
+ currentPlannedMessages: sidecar.currentPlannedMessages,
38018
+ };
38019
+ }
38020
+ /**
38021
+ * Reads one text file and treats a missing planned-message sidecar as absent.
38022
+ *
38023
+ * @param path - Absolute sidecar path.
38024
+ * @returns File content, or `null` when the sidecar does not exist.
38025
+ */
38026
+ async function readOptionalTextFile$2(path) {
38027
+ try {
38028
+ return await readFile(path, 'utf-8');
38029
+ }
38030
+ catch (error) {
38031
+ if (isFileNotFoundError$2(error)) {
38032
+ return null;
38033
+ }
38034
+ throw error;
38035
+ }
38036
+ }
38037
+ /**
38038
+ * Converts a filesystem-relative path to the portable form used in prompts.
38039
+ *
38040
+ * @param path - Relative path inside the agent folder.
38041
+ * @returns Path with forward slashes.
38042
+ */
37729
38043
  function toPortablePath$1(path) {
37730
38044
  return path.replace(/\\/gu, '/');
37731
38045
  }
37732
38046
  /**
37733
38047
  * Returns true when one filesystem error indicates a missing path.
38048
+ *
38049
+ * @param error - Caught filesystem failure.
38050
+ * @returns `true` for missing-path errors.
37734
38051
  */
37735
38052
  function isFileNotFoundError$2(error) {
37736
38053
  return Boolean(error &&
@@ -37745,7 +38062,7 @@ function isFileNotFoundError$2(error) {
37745
38062
  async function loadAgentTeamConversationWorkspace(projectPath, queuedMessage) {
37746
38063
  const relativeWorkspacePath = createAgentTeamConversationWorkspacePath(queuedMessage.fileName);
37747
38064
  const manifestPath = join(projectPath, relativeWorkspacePath, AGENT_TEAM_CONVERSATION_MANIFEST_FILE_NAME);
37748
- const manifestContent = await readOptionalTextFile(manifestPath);
38065
+ const manifestContent = await readOptionalTextFile$1(manifestPath);
37749
38066
  if (manifestContent === null) {
37750
38067
  return null;
37751
38068
  }
@@ -37773,7 +38090,7 @@ function parseAgentTeamConversationWorkspaceManifest(manifestContent) {
37773
38090
  /**
37774
38091
  * Reads one text file and treats a missing team workspace as absent.
37775
38092
  */
37776
- async function readOptionalTextFile(path) {
38093
+ async function readOptionalTextFile$1(path) {
37777
38094
  try {
37778
38095
  return await readFile(path, 'utf-8');
37779
38096
  }
@@ -37840,6 +38157,233 @@ function resolveAgentProjectsUrlPath(projectPath) {
37840
38157
  return `/agents/${encodeURIComponent(agentId)}/projects`;
37841
38158
  }
37842
38159
 
38160
+ /**
38161
+ * Parses every structured event out of one coding-harness runtime log.
38162
+ *
38163
+ * The local agent runner streams the answering harness output into a live runtime log file, one
38164
+ * JSON event per line, optionally prefixed by shell noise. Lines without a usable JSON object are
38165
+ * skipped so a partially written or interleaved log never breaks its consumers.
38166
+ *
38167
+ * @param logText - Raw runtime log content.
38168
+ * @returns Structured events in the order they were streamed.
38169
+ * @private internal utility of the agent-message runtime
38170
+ */
38171
+ function parseAgentMessageRuntimeLogEvents(logText) {
38172
+ if (!logText) {
38173
+ return [];
38174
+ }
38175
+ const events = [];
38176
+ for (const line of logText.split(/\r?\n/u)) {
38177
+ const event = parseAgentMessageRuntimeLogEvent(line);
38178
+ if (event) {
38179
+ events.push(event);
38180
+ }
38181
+ }
38182
+ return events;
38183
+ }
38184
+ /**
38185
+ * Parses one runtime log line into a structured event when it embeds JSON.
38186
+ *
38187
+ * @param line - One raw runtime log line.
38188
+ * @returns Structured event, or `null` when the line carries no JSON object.
38189
+ * @private internal helper of `parseAgentMessageRuntimeLogEvents`
38190
+ */
38191
+ function parseAgentMessageRuntimeLogEvent(line) {
38192
+ const jsonStartIndex = line.indexOf('{');
38193
+ if (jsonStartIndex === -1) {
38194
+ return null;
38195
+ }
38196
+ try {
38197
+ const parsedEvent = JSON.parse(line.slice(jsonStartIndex).trim());
38198
+ if (typeof parsedEvent !== 'object' || parsedEvent === null || Array.isArray(parsedEvent)) {
38199
+ return null;
38200
+ }
38201
+ return parsedEvent;
38202
+ }
38203
+ catch (_a) {
38204
+ return null;
38205
+ }
38206
+ }
38207
+
38208
+ /**
38209
+ * Matches one project directory name following the agent `projects/` folder in a file path or command.
38210
+ *
38211
+ * Both path separators are accepted because the local runner answers messages on Unix and Windows,
38212
+ * and JSON-encoded payloads escape backslashes.
38213
+ *
38214
+ * @private internal constant of agent-message touched projects
38215
+ */
38216
+ const TOUCHED_PROJECT_PATH_REGEX = new RegExp(`${AGENT_PROJECTS_DIRECTORY_PATH}[\\\\/]+([^\\\\/"'\\s,;:]+)`, 'giu');
38217
+ /**
38218
+ * Resolves which projects one coding harness touched while answering a single message.
38219
+ *
38220
+ * The runtime log streams what the harness really did, so a project counts as touched when the
38221
+ * harness named it in a tool invocation — reading, searching, running, or editing files inside
38222
+ * `projects/<project-name>/`. Only tool payloads are inspected: tool results and assistant
38223
+ * narration are ignored so a project merely mentioned in the conversation is never reported.
38224
+ *
38225
+ * Every candidate is matched against the projects that really exist, which both filters prompt
38226
+ * placeholders such as `projects/<project-name>/` and keeps the reported names canonical.
38227
+ *
38228
+ * @param options - Raw runtime log content and the project directory names of the agent.
38229
+ * @returns Touched project names in the canonical spelling, ordered by first appearance.
38230
+ * @private internal utility of the agent-message runtime
38231
+ */
38232
+ function resolveAgentMessageTouchedProjectNames(options) {
38233
+ const canonicalProjectNames = createCanonicalProjectNameLookup(options.knownProjectNames);
38234
+ if (canonicalProjectNames.size === 0) {
38235
+ return [];
38236
+ }
38237
+ const touchedProjectNames = new Set();
38238
+ for (const event of parseAgentMessageRuntimeLogEvents(options.logText)) {
38239
+ for (const toolInvocationText of resolveRuntimeLogEventToolInvocationTexts(event)) {
38240
+ for (const candidateProjectName of resolveCandidateProjectNames(toolInvocationText)) {
38241
+ const canonicalProjectName = canonicalProjectNames.get(candidateProjectName.toLowerCase());
38242
+ if (canonicalProjectName !== undefined) {
38243
+ touchedProjectNames.add(canonicalProjectName);
38244
+ }
38245
+ }
38246
+ }
38247
+ }
38248
+ return Array.from(touchedProjectNames);
38249
+ }
38250
+ /**
38251
+ * Indexes the existing project directory names by their lower-cased form.
38252
+ *
38253
+ * @param knownProjectNames - Project directory names of the agent.
38254
+ * @returns Lookup from the lower-cased name to its canonical spelling.
38255
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38256
+ */
38257
+ function createCanonicalProjectNameLookup(knownProjectNames) {
38258
+ const canonicalProjectNames = new Map();
38259
+ for (const knownProjectName of knownProjectNames) {
38260
+ const normalizedProjectName = knownProjectName.trim();
38261
+ if (normalizedProjectName.length > 0) {
38262
+ canonicalProjectNames.set(normalizedProjectName.toLowerCase(), normalizedProjectName);
38263
+ }
38264
+ }
38265
+ return canonicalProjectNames;
38266
+ }
38267
+ /**
38268
+ * Collects the texts of every tool invocation described by one runtime log event.
38269
+ *
38270
+ * Claude Code reports tool invocations as `tool_use` content blocks, while Codex reports executed
38271
+ * commands and file changes as items — both carry the paths the harness worked with.
38272
+ *
38273
+ * @param event - One structured runtime log event.
38274
+ * @returns Texts that may contain project paths.
38275
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38276
+ */
38277
+ function resolveRuntimeLogEventToolInvocationTexts(event) {
38278
+ var _a, _b, _c;
38279
+ const toolInvocationTexts = [];
38280
+ for (const contentBlock of ((_a = event.message) === null || _a === void 0 ? void 0 : _a.content) || []) {
38281
+ if (contentBlock.type === 'tool_use' && contentBlock.input) {
38282
+ toolInvocationTexts.push(serializeToolInput(contentBlock.input));
38283
+ }
38284
+ }
38285
+ if (typeof ((_b = event.item) === null || _b === void 0 ? void 0 : _b.command) === 'string') {
38286
+ toolInvocationTexts.push(event.item.command);
38287
+ }
38288
+ for (const fileChange of ((_c = event.item) === null || _c === void 0 ? void 0 : _c.changes) || []) {
38289
+ if (typeof fileChange.path === 'string') {
38290
+ toolInvocationTexts.push(fileChange.path);
38291
+ }
38292
+ }
38293
+ return toolInvocationTexts;
38294
+ }
38295
+ /**
38296
+ * Serializes one tool input payload into scannable text.
38297
+ *
38298
+ * @param toolInput - Tool arguments of one harness tool invocation.
38299
+ * @returns Serialized payload, or an empty string when it cannot be serialized.
38300
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38301
+ */
38302
+ function serializeToolInput(toolInput) {
38303
+ try {
38304
+ return JSON.stringify(toolInput);
38305
+ }
38306
+ catch (_a) {
38307
+ return '';
38308
+ }
38309
+ }
38310
+ /**
38311
+ * Extracts every project directory name mentioned right after the agent `projects/` folder.
38312
+ *
38313
+ * @param toolInvocationText - One tool payload, command, or changed path.
38314
+ * @returns Candidate project directory names.
38315
+ * @private internal helper of `resolveAgentMessageTouchedProjectNames`
38316
+ */
38317
+ function resolveCandidateProjectNames(toolInvocationText) {
38318
+ const candidateProjectNames = [];
38319
+ const projectPathRegex = new RegExp(TOUCHED_PROJECT_PATH_REGEX.source, TOUCHED_PROJECT_PATH_REGEX.flags);
38320
+ let projectPathMatch = projectPathRegex.exec(toolInvocationText);
38321
+ while (projectPathMatch !== null) {
38322
+ const candidateProjectName = projectPathMatch[1];
38323
+ if (candidateProjectName) {
38324
+ candidateProjectNames.push(candidateProjectName);
38325
+ }
38326
+ projectPathMatch = projectPathRegex.exec(toolInvocationText);
38327
+ }
38328
+ return candidateProjectNames;
38329
+ }
38330
+
38331
+ /**
38332
+ * Resolves which agent projects one answered message viewed or edited.
38333
+ *
38334
+ * The runtime log is deleted as soon as the harness run finishes, so this must be called while
38335
+ * the log still exists — the Agents Server later shows the reported projects as chips below the
38336
+ * answer. Reporting touched projects is best-effort telemetry: an unreadable log or projects
38337
+ * folder simply yields no projects instead of failing the already answered message.
38338
+ *
38339
+ * @param options - Agent folder path and the live runtime log path of the answered message.
38340
+ * @returns Touched project directory names, ordered by first appearance in the run.
38341
+ */
38342
+ async function resolveTouchedAgentProjects(options) {
38343
+ const [logText, knownProjectNames] = await Promise.all([
38344
+ readOptionalTextFile(options.runtimeLogPath),
38345
+ listAgentProjectDirectoryNames(options.projectPath),
38346
+ ]);
38347
+ return resolveAgentMessageTouchedProjectNames({ logText, knownProjectNames });
38348
+ }
38349
+ /**
38350
+ * Lists the direct project directories of one agent folder.
38351
+ *
38352
+ * @param projectPath - Absolute path of the local agent folder.
38353
+ * @returns Project directory names, or an empty list when the agent has no projects folder.
38354
+ *
38355
+ * @private helper of `resolveTouchedAgentProjects`
38356
+ */
38357
+ async function listAgentProjectDirectoryNames(projectPath) {
38358
+ try {
38359
+ const projectsRootEntries = await readdir(join(projectPath, AGENT_PROJECTS_DIRECTORY_PATH), {
38360
+ withFileTypes: true,
38361
+ });
38362
+ return projectsRootEntries
38363
+ .filter((projectsRootEntry) => projectsRootEntry.isDirectory())
38364
+ .map((projectDirectoryEntry) => projectDirectoryEntry.name);
38365
+ }
38366
+ catch (_a) {
38367
+ return [];
38368
+ }
38369
+ }
38370
+ /**
38371
+ * Reads one text file and treats a missing runtime log as "nothing was recorded".
38372
+ *
38373
+ * @param filePath - Absolute path of the file to read.
38374
+ * @returns File content, or `null` when it cannot be read.
38375
+ *
38376
+ * @private helper of `resolveTouchedAgentProjects`
38377
+ */
38378
+ async function readOptionalTextFile(filePath) {
38379
+ try {
38380
+ return await readFile(filePath, 'utf-8');
38381
+ }
38382
+ catch (_a) {
38383
+ return null;
38384
+ }
38385
+ }
38386
+
37843
38387
  // Note: [💞] Ignore a discrepancy between file name and entity name
37844
38388
  /**
37845
38389
  * File suffix appended to one answered message book to store its run report sidecar.
@@ -38119,10 +38663,11 @@ async function runQueuedAgentMessage(options) {
38119
38663
  const { projectPath, options: runOptions, runner, actualRunnerModel, queuedMessage, uiHandle, isSharedDashboard, } = options;
38120
38664
  const agentSystemMessage = await loadLocalAgentSystemMessage(projectPath);
38121
38665
  const teamWorkspace = await loadAgentTeamConversationWorkspace(projectPath, queuedMessage);
38666
+ const plannedMessagesSidecar = await loadAgentPlannedMessagesSidecar(projectPath, queuedMessage);
38122
38667
  const agentServerRuntimeApi = resolveAgentProjectRuntimePromptApi(projectPath);
38123
38668
  const prompt = buildAgentMessagePrompt(queuedMessage.relativePath, agentSystemMessage, {
38124
38669
  projectRuntimeApi: agentServerRuntimeApi,
38125
- goalChatRuntimeApi: agentServerRuntimeApi,
38670
+ plannedMessagesSidecar: plannedMessagesSidecar || undefined,
38126
38671
  projectsUrlPath: resolveAgentProjectsUrlPath(projectPath),
38127
38672
  teamWorkspace: teamWorkspace || undefined,
38128
38673
  });
@@ -38143,21 +38688,29 @@ async function runQueuedAgentMessage(options) {
38143
38688
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setCurrentScriptPath(scriptPath);
38144
38689
  }
38145
38690
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.startCapturingAgentOutput();
38691
+ const executionStartedAt = new Date().toISOString();
38146
38692
  let promptRunResult;
38693
+ let touchedProjectNames = [];
38147
38694
  try {
38148
38695
  try {
38149
- promptRunResult = await withPromptRuntimeLog(scriptPath, async (logPath) => await runPromptWithTestFeedback({
38150
- runner,
38151
- prompt,
38152
- scriptPath,
38153
- projectPath,
38154
- promptLabel: queuedMessage.relativePath,
38155
- logPath,
38156
- preserveArtifactsOnSuccess: false,
38157
- onAttemptStarted: (attemptCount) => {
38158
- uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(attemptCount);
38159
- },
38160
- }), { preserveArtifactsOnSuccess: false });
38696
+ promptRunResult = await withPromptRuntimeLog(scriptPath, async (logPath) => {
38697
+ const runResult = await runPromptWithTestFeedback({
38698
+ runner,
38699
+ prompt,
38700
+ scriptPath,
38701
+ projectPath,
38702
+ promptLabel: queuedMessage.relativePath,
38703
+ logPath,
38704
+ preserveArtifactsOnSuccess: false,
38705
+ onAttemptStarted: (attemptCount) => {
38706
+ uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setAttempt(attemptCount);
38707
+ },
38708
+ });
38709
+ // Note: The runtime log is deleted right after this handler, so the projects this
38710
+ // answer worked with must be resolved while the log still exists.
38711
+ touchedProjectNames = await resolveTouchedAgentProjects({ projectPath, runtimeLogPath: logPath });
38712
+ return runResult;
38713
+ }, { preserveArtifactsOnSuccess: false });
38161
38714
  }
38162
38715
  catch (error) {
38163
38716
  throw withAgentWatchErrorContext(error, {
@@ -38171,6 +38724,7 @@ async function runQueuedAgentMessage(options) {
38171
38724
  finally {
38172
38725
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
38173
38726
  }
38727
+ const executionFinishedAt = new Date().toISOString();
38174
38728
  await normalizeLineEndingsForAgentRound(projectPath, runOptions, roundChangedFilesSnapshot);
38175
38729
  const finishedTeamWorkspace = await finalizeAgentTeamConversationWorkspace({
38176
38730
  projectPath,
@@ -38189,6 +38743,11 @@ async function runQueuedAgentMessage(options) {
38189
38743
  modelName: actualRunnerModel,
38190
38744
  loginMethod: promptRunResult.loginMethod,
38191
38745
  usage: promptRunResult.usage,
38746
+ executionTiming: {
38747
+ startedAt: executionStartedAt,
38748
+ finishedAt: executionFinishedAt,
38749
+ },
38750
+ ...(touchedProjectNames.length === 0 ? {} : { touchedProjectNames }),
38192
38751
  },
38193
38752
  });
38194
38753
  await commitAnsweredMessageIfEnabled({