@promptbook/cli 0.114.0-11 → 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 (145) hide show
  1. package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +8 -1
  2. package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +193 -63
  3. package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +25 -28
  4. package/apps/agents-server/src/app/admin/messages/send-email/emailTesting.ts +153 -0
  5. package/apps/agents-server/src/app/admin/messages/send-email/emailTestingAccess.ts +30 -0
  6. package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +18 -5
  7. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +7 -4
  8. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentGoalChatPlannedMessages.tsx +23 -8
  9. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +14 -3
  10. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +2 -20
  11. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +2 -1
  12. package/apps/agents-server/src/app/agents/[agentName]/projectReferenceActions.ts +61 -0
  13. package/apps/agents-server/src/app/api/internal/agent-goal-chat-planned-messages/route.ts +34 -7
  14. package/apps/agents-server/src/components/AgentProjects/resolveLatestCompletedAgentMessageKey.ts +32 -0
  15. package/apps/agents-server/src/components/AgentProjects/useAgentProjectMarkdownReferences.ts +124 -2
  16. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +3 -0
  17. package/apps/agents-server/src/languages/translations/czech.yaml +3 -0
  18. package/apps/agents-server/src/languages/translations/english.yaml +3 -0
  19. package/apps/agents-server/src/message-providers/email/stalwart/StalwartMessageProvider.ts +2 -1
  20. package/apps/agents-server/src/tools/agentGoalChatTimeoutToolFunctions.ts +24 -2
  21. package/apps/agents-server/src/tools/agentGoalChatTimeoutTools.ts +5 -4
  22. package/apps/agents-server/src/utils/agentGoalChat/agentGoalChatPlannedMessageActions.ts +38 -9
  23. package/apps/agents-server/src/utils/agentGoalChat/createAgentGoalChatNoteContent.ts +15 -6
  24. package/apps/agents-server/src/utils/agentProjects/listAgentProjectNames.ts +35 -0
  25. package/apps/agents-server/src/utils/agentProjects/listAgentProjects.ts +6 -23
  26. package/apps/agents-server/src/utils/agentProjects/resolveAgentChatProjectReferences.ts +25 -0
  27. package/apps/agents-server/src/utils/chatMessageChips/createAnsweredMessageChipToolCalls.ts +37 -0
  28. package/apps/agents-server/src/utils/chatMessageChips/createPlannedMessageChipToolCalls.ts +39 -0
  29. package/apps/agents-server/src/utils/chatMessageChips/createTouchedProjectChipToolCalls.ts +75 -0
  30. package/apps/agents-server/src/utils/email/agentEmailAddress.ts +14 -0
  31. package/apps/agents-server/src/utils/email/sendEmailThroughStalwart.ts +2 -1
  32. package/apps/agents-server/src/utils/localChatRunner/applyLocalAgentPlannedMessageCommands.ts +212 -0
  33. package/apps/agents-server/src/utils/localChatRunner/prepareLocalAgentPlannedMessagesSidecar.ts +66 -0
  34. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +110 -22
  35. package/apps/agents-server/src/utils/userChat/persistUserChatJobTerminalState.ts +5 -0
  36. package/apps/agents-server/src/utils/userChat/userChatJobRunReport.ts +30 -0
  37. package/apps/agents-server/src/utils/userChatTimeout/createTimeoutWakeUpMessage.ts +9 -1
  38. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore/repeatFiredUserChatTimeout.ts +74 -0
  39. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutStore.ts +1 -0
  40. package/apps/agents-server/src/utils/userChatTimeout/userChatTimeoutWorker.ts +63 -54
  41. package/esm/index.es.js +1042 -194
  42. package/esm/index.es.js.map +1 -1
  43. package/esm/scripts/run-agent-messages/messages/buildAgentGoalChatPromptSection.d.ts +22 -4
  44. package/esm/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +3 -3
  45. package/esm/scripts/run-agent-messages/messages/loadAgentPlannedMessagesSidecar.d.ts +10 -0
  46. package/esm/scripts/run-agent-messages/messages/resolveTouchedAgentProjects.d.ts +15 -0
  47. package/esm/scripts/run-codex-prompts/common/runGoScript/$spawnLoggedBashScript.d.ts +25 -0
  48. package/esm/scripts/run-codex-prompts/common/runGoScript/$terminateLoggedBashProcessTree.d.ts +11 -0
  49. package/esm/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +4 -0
  50. package/esm/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +5 -0
  51. package/esm/src/book-2.0/agent-source/ParsedUnknownCommitment.d.ts +26 -0
  52. package/esm/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.unknown.test.d.ts +1 -0
  53. package/esm/src/book-2.0/agent-source/getBookCommitmentLineType.d.ts +19 -0
  54. package/esm/src/book-2.0/agent-source/parseAgentSourceWithCommitments.unknown.test.d.ts +1 -0
  55. package/esm/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  56. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  57. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  58. package/esm/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  59. package/esm/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.d.ts +28 -0
  60. package/esm/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.test.d.ts +1 -0
  61. package/esm/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  62. package/esm/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  63. package/esm/src/cli/cli-commands/common/harness/$checkHarnessInstallation.d.ts +3 -3
  64. package/esm/src/cli/cli-commands/common/harness/$checkHarnessInstallation.test.d.ts +1 -0
  65. package/esm/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -3
  66. package/esm/src/cli/cli-commands/common/harness/HarnessInstallationStatus.d.ts +3 -2
  67. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +29 -0
  68. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts +1 -0
  69. package/esm/src/executables/platforms/locateAppOnWindows.d.ts +1 -1
  70. package/esm/src/executables/platforms/locateAppOnWindows.test.d.ts +1 -0
  71. package/esm/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  72. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  73. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  74. package/esm/src/version.d.ts +1 -1
  75. package/package.json +1 -1
  76. package/src/book-2.0/agent-source/AgentSourceParseResult.ts +6 -0
  77. package/src/book-2.0/agent-source/ParsedUnknownCommitment.ts +29 -0
  78. package/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments/augmentAgentModelRequirementsFromSource.ts +38 -1
  79. package/src/book-2.0/agent-source/getBookCommitmentLineType.ts +36 -0
  80. package/src/book-2.0/agent-source/parseAgentSourceWithCommitments.ts +128 -61
  81. package/src/book-3.0/AgentMessageRunReport.ts +76 -0
  82. package/src/book-3.0/AgentPlannedMessagesSidecar.ts +229 -0
  83. package/src/book-3.0/AgentTeamConversationWorkspace.ts +3 -5
  84. package/src/book-3.0/createAgentMessageSidecarBaseName.ts +20 -0
  85. package/src/book-components/BookEditor/BookEditorMonaco.tsx +6 -1
  86. package/src/book-components/BookEditor/BookEditorMonacoTokenization.ts +3 -1
  87. package/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.ts +50 -0
  88. package/src/book-components/Chat/Chat/Chat.module.css +6 -1
  89. package/src/book-components/Chat/Chat/renderToolCallDetails.tsx +1 -12
  90. package/src/book-components/Chat/utils/agentProjectToolCall.ts +74 -0
  91. package/src/book-components/Chat/utils/getToolCallChipletInfo.ts +28 -0
  92. package/src/book-components/Chat/utils/timeoutToolCallPresentation.ts +23 -3
  93. package/src/cli/cli-commands/coder/boilerplateTemplates.ts +3 -15
  94. package/src/cli/cli-commands/coder/init.ts +11 -2
  95. package/src/cli/cli-commands/coder/ping.ts +11 -1
  96. package/src/cli/cli-commands/coder/run.ts +11 -2
  97. package/src/cli/cli-commands/coder/server.ts +11 -2
  98. package/src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.ts +5 -0
  99. package/src/cli/cli-commands/common/harness/$checkHarnessInstallation.ts +17 -5
  100. package/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.ts +6 -3
  101. package/src/cli/cli-commands/common/harness/HarnessInstallationStatus.ts +3 -2
  102. package/src/cli/cli-commands/common/harnessUpdateCliOptions.ts +49 -0
  103. package/src/executables/platforms/locateAppOnWindows.ts +12 -9
  104. package/src/other/templates/getTemplatesPipelineCollection.ts +740 -860
  105. package/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.ts +105 -0
  106. package/src/utils/agent-message-runtime/resolveAgentMessageRuntimeActivity.ts +6 -54
  107. package/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.ts +147 -0
  108. package/src/version.ts +2 -2
  109. package/src/versions.txt +2 -1
  110. package/umd/index.umd.js +1041 -193
  111. package/umd/index.umd.js.map +1 -1
  112. package/umd/scripts/run-agent-messages/messages/buildAgentGoalChatPromptSection.d.ts +22 -4
  113. package/umd/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +3 -3
  114. package/umd/scripts/run-agent-messages/messages/loadAgentPlannedMessagesSidecar.d.ts +10 -0
  115. package/umd/scripts/run-agent-messages/messages/resolveTouchedAgentProjects.d.ts +15 -0
  116. package/umd/scripts/run-codex-prompts/common/runGoScript/$spawnLoggedBashScript.d.ts +25 -0
  117. package/umd/scripts/run-codex-prompts/common/runGoScript/$terminateLoggedBashProcessTree.d.ts +11 -0
  118. package/umd/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +4 -0
  119. package/umd/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +5 -0
  120. package/umd/src/book-2.0/agent-source/ParsedUnknownCommitment.d.ts +26 -0
  121. package/umd/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.unknown.test.d.ts +1 -0
  122. package/umd/src/book-2.0/agent-source/getBookCommitmentLineType.d.ts +19 -0
  123. package/umd/src/book-2.0/agent-source/parseAgentSourceWithCommitments.unknown.test.d.ts +1 -0
  124. package/umd/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  125. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  126. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  127. package/umd/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  128. package/umd/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.d.ts +28 -0
  129. package/umd/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.test.d.ts +1 -0
  130. package/umd/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  131. package/umd/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  132. package/umd/src/cli/cli-commands/common/harness/$checkHarnessInstallation.d.ts +3 -3
  133. package/umd/src/cli/cli-commands/common/harness/$checkHarnessInstallation.test.d.ts +1 -0
  134. package/umd/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -3
  135. package/umd/src/cli/cli-commands/common/harness/HarnessInstallationStatus.d.ts +3 -2
  136. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +29 -0
  137. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts +1 -0
  138. package/umd/src/executables/platforms/locateAppOnWindows.d.ts +1 -1
  139. package/umd/src/executables/platforms/locateAppOnWindows.test.d.ts +1 -0
  140. package/umd/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  141. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  142. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  143. package/umd/src/version.d.ts +1 -1
  144. package/esm/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
  145. package/umd/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
@@ -0,0 +1,212 @@
1
+ import { readFile, rm } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { serializeError } from '@promptbook-local/utils';
4
+ import {
5
+ createAgentPlannedMessagesSidecarPath,
6
+ parseAgentPlannedMessagesSidecar,
7
+ type AgentPlannedMessageCommand,
8
+ } from '../../../../../src/book-3.0/AgentPlannedMessagesSidecar';
9
+ import {
10
+ AGENT_GOAL_CHAT_PLANNED_MESSAGE_ACTIONS,
11
+ type CancelAgentGoalChatPlannedMessageResult,
12
+ type SetAgentGoalChatPlannedMessageResult,
13
+ } from '../agentGoalChat/agentGoalChatPlannedMessageActions';
14
+ import type { UserChatJobRecord } from '../userChat/UserChatJobRecord';
15
+ import type { LocalUserChatJobMetadata } from './LocalUserChatJobMetadata';
16
+
17
+ /**
18
+ * Planned-message operations used while applying one sidecar.
19
+ */
20
+ export type LocalAgentPlannedMessageActions = Pick<typeof AGENT_GOAL_CHAT_PLANNED_MESSAGE_ACTIONS, 'set' | 'cancel'>;
21
+
22
+ /**
23
+ * One planned-message command that was applied together with its result.
24
+ *
25
+ * The command carries what the agent asked for (for example the requested delay) and the result
26
+ * carries what really happened, so the chat can show the wake-up below the message that planned it.
27
+ */
28
+ export type AppliedAgentPlannedMessageCommand = {
29
+ readonly command: AgentPlannedMessageCommand;
30
+ readonly result: SetAgentGoalChatPlannedMessageResult | CancelAgentGoalChatPlannedMessageResult;
31
+ };
32
+
33
+ /**
34
+ * Applies every planned-message command a coding harness wrote while answering one message.
35
+ *
36
+ * The sidecar is consumed exactly once: it is removed afterwards so a replayed worker tick can never
37
+ * schedule the very same wake-up twice.
38
+ *
39
+ * @param options - Answered job, its agent folder path, and the local runner metadata of the message.
40
+ * @returns Planned-message commands that were applied successfully, in sidecar order.
41
+ */
42
+ export async function applyLocalAgentPlannedMessageCommands(options: {
43
+ readonly job: Pick<UserChatJobRecord, 'id' | 'chatId' | 'agentPermanentId'>;
44
+ readonly agentDirectoryPath: string;
45
+ readonly metadata: LocalUserChatJobMetadata;
46
+ readonly actions?: LocalAgentPlannedMessageActions;
47
+ }): Promise<ReadonlyArray<AppliedAgentPlannedMessageCommand>> {
48
+ const sidecarPath = resolveLocalAgentPlannedMessagesSidecarPath(options.agentDirectoryPath, options.metadata);
49
+ const sidecarContent = await readOptionalTextFile(sidecarPath);
50
+
51
+ await removeLocalAgentPlannedMessagesSidecar(options.agentDirectoryPath, options.metadata);
52
+
53
+ if (sidecarContent === null) {
54
+ return [];
55
+ }
56
+
57
+ const sidecar = parseAgentPlannedMessagesSidecar(sidecarContent);
58
+
59
+ if (!sidecar) {
60
+ console.warn('[local-chat-runner] planned_messages_sidecar_unreadable', {
61
+ chatId: options.job.chatId,
62
+ jobId: options.job.id,
63
+ });
64
+ return [];
65
+ }
66
+
67
+ const actions = options.actions || AGENT_GOAL_CHAT_PLANNED_MESSAGE_ACTIONS;
68
+ const appliedCommands: Array<AppliedAgentPlannedMessageCommand> = [];
69
+
70
+ for (const command of sidecar.commands) {
71
+ const result = await applyLocalAgentPlannedMessageCommand({
72
+ command,
73
+ actions,
74
+ agentPermanentId: options.job.agentPermanentId,
75
+ job: options.job,
76
+ });
77
+
78
+ if (result) {
79
+ appliedCommands.push({ command, result });
80
+ }
81
+ }
82
+
83
+ return appliedCommands;
84
+ }
85
+
86
+ /**
87
+ * Removes the planned-message sidecar of one queued message.
88
+ *
89
+ * @param agentDirectoryPath - Absolute path of the local agent folder.
90
+ * @param metadata - Local runner metadata of the queued message.
91
+ */
92
+ export async function removeLocalAgentPlannedMessagesSidecar(
93
+ agentDirectoryPath: string,
94
+ metadata: LocalUserChatJobMetadata,
95
+ ): Promise<void> {
96
+ await rm(resolveLocalAgentPlannedMessagesSidecarPath(agentDirectoryPath, metadata), { force: true }).catch(
97
+ () => undefined,
98
+ );
99
+ }
100
+
101
+ /**
102
+ * Dispatches one planned-message command through the shared planned-message actions.
103
+ *
104
+ * One rejected command never blocks the remaining ones, because the agent already answered and the
105
+ * harness owns this sidecar.
106
+ *
107
+ * @param options - Command, actions, and the agent whose goal chat owns the planned message.
108
+ * @returns Result of the applied command, or `null` when it was rejected.
109
+ *
110
+ * @private function of `applyLocalAgentPlannedMessageCommands`
111
+ */
112
+ async function applyLocalAgentPlannedMessageCommand(options: {
113
+ readonly command: AgentPlannedMessageCommand;
114
+ readonly actions: LocalAgentPlannedMessageActions;
115
+ readonly agentPermanentId: string;
116
+ readonly job: Pick<UserChatJobRecord, 'id' | 'chatId'>;
117
+ }): Promise<AppliedAgentPlannedMessageCommand['result'] | null> {
118
+ try {
119
+ if (options.command.action === 'set') {
120
+ const plannedMessage = await options.actions.set({
121
+ agentPermanentId: options.agentPermanentId,
122
+ milliseconds: options.command.milliseconds,
123
+ message: options.command.message,
124
+ });
125
+
126
+ console.info('[local-chat-runner]', 'planned_message_set', {
127
+ chatId: options.job.chatId,
128
+ jobId: options.job.id,
129
+ timeoutId: plannedMessage.timeoutId,
130
+ dueAt: plannedMessage.dueAt,
131
+ });
132
+
133
+ return plannedMessage;
134
+ }
135
+
136
+ const cancelledPlannedMessage = await options.actions.cancel({
137
+ agentPermanentId: options.agentPermanentId,
138
+ timeoutId: options.command.timeoutId,
139
+ });
140
+
141
+ console.info('[local-chat-runner]', 'planned_message_cancel', {
142
+ chatId: options.job.chatId,
143
+ jobId: options.job.id,
144
+ timeoutId: cancelledPlannedMessage.timeoutId,
145
+ status: cancelledPlannedMessage.status,
146
+ });
147
+
148
+ return cancelledPlannedMessage.status === 'cancelled' ? cancelledPlannedMessage : null;
149
+ } catch (error) {
150
+ console.error('[local-chat-runner]', 'planned_message_command_failed', {
151
+ chatId: options.job.chatId,
152
+ jobId: options.job.id,
153
+ action: options.command.action,
154
+ error: serializeError(error as Error),
155
+ });
156
+
157
+ return null;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Resolves the absolute planned-message sidecar path of one queued message.
163
+ *
164
+ * @param agentDirectoryPath - Absolute path of the local agent folder.
165
+ * @param metadata - Local runner metadata of the queued message.
166
+ * @returns Absolute sidecar path.
167
+ *
168
+ * @private function of `applyLocalAgentPlannedMessageCommands`
169
+ */
170
+ function resolveLocalAgentPlannedMessagesSidecarPath(
171
+ agentDirectoryPath: string,
172
+ metadata: LocalUserChatJobMetadata,
173
+ ): string {
174
+ return join(agentDirectoryPath, createAgentPlannedMessagesSidecarPath(metadata.fileName));
175
+ }
176
+
177
+ /**
178
+ * Reads one text file and treats a missing planned-message sidecar as absent.
179
+ *
180
+ * @param path - Absolute sidecar path.
181
+ * @returns File content, or `null` when the sidecar does not exist.
182
+ *
183
+ * @private function of `applyLocalAgentPlannedMessageCommands`
184
+ */
185
+ async function readOptionalTextFile(path: string): Promise<string | null> {
186
+ try {
187
+ return await readFile(path, 'utf-8');
188
+ } catch (error) {
189
+ if (isFileNotFoundError(error)) {
190
+ return null;
191
+ }
192
+
193
+ throw error;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Returns true when one filesystem error indicates a missing path.
199
+ *
200
+ * @param error - Caught filesystem failure.
201
+ * @returns `true` for missing-path errors.
202
+ *
203
+ * @private function of `applyLocalAgentPlannedMessageCommands`
204
+ */
205
+ function isFileNotFoundError(error: unknown): boolean {
206
+ return Boolean(
207
+ error &&
208
+ typeof error === 'object' &&
209
+ 'code' in error &&
210
+ ((error as { code?: string }).code === 'ENOENT' || (error as { code?: string }).code === 'ENOTDIR'),
211
+ );
212
+ }
@@ -0,0 +1,66 @@
1
+ import { mkdir, writeFile } from 'fs/promises';
2
+ import { dirname, join } from 'path';
3
+ import {
4
+ createAgentPlannedMessagesSidecarContent,
5
+ createAgentPlannedMessagesSidecarPath,
6
+ type AgentPlannedMessageSnapshot,
7
+ type AgentPlannedMessagesSidecar,
8
+ } from '../../../../../src/book-3.0/AgentPlannedMessagesSidecar';
9
+ import { AGENT_GOAL_CHAT_PLANNED_MESSAGE_ACTIONS } from '../agentGoalChat/agentGoalChatPlannedMessageActions';
10
+ import type { UserChatJobRecord } from '../userChat/UserChatJobRecord';
11
+ import type { LocalAgentFolder } from './ensureLocalAgentFolder';
12
+ import type { LocalUserChatJobMetadata } from './LocalUserChatJobMetadata';
13
+
14
+ /**
15
+ * Writes the planned-message sidecar a coding harness uses to see and change its future wake-ups.
16
+ *
17
+ * The sidecar is the only planning channel of a locally executed turn: the harness never has to reach
18
+ * the Agents Server over the network, and a wake-up exists only when it was actually requested here.
19
+ * Planned messages repeat on their own, so an empty `commands` array keeps the current plan intact.
20
+ *
21
+ * @param options - Answered job, its agent folder, and the local runner metadata of the queued message.
22
+ */
23
+ export async function prepareLocalAgentPlannedMessagesSidecar(options: {
24
+ readonly job: Pick<UserChatJobRecord, 'agentPermanentId'>;
25
+ readonly agentFolder: LocalAgentFolder;
26
+ readonly metadata: LocalUserChatJobMetadata;
27
+ }): Promise<void> {
28
+ const sidecarPath = join(
29
+ options.agentFolder.directoryPath,
30
+ createAgentPlannedMessagesSidecarPath(options.metadata.fileName),
31
+ );
32
+ const plannedMessages = await AGENT_GOAL_CHAT_PLANNED_MESSAGE_ACTIONS.list({
33
+ agentPermanentId: options.job.agentPermanentId,
34
+ });
35
+ const sidecar: AgentPlannedMessagesSidecar = {
36
+ version: 1,
37
+ agentPermanentId: options.job.agentPermanentId,
38
+ currentPlannedMessages: plannedMessages.items.map(createLocalAgentPlannedMessageSnapshot),
39
+ commands: [],
40
+ };
41
+
42
+ await mkdir(dirname(sidecarPath), { recursive: true });
43
+ await writeFile(sidecarPath, createAgentPlannedMessagesSidecarContent(sidecar), 'utf-8');
44
+ }
45
+
46
+ /**
47
+ * Reduces one stored planned message to the fields a coding harness needs.
48
+ *
49
+ * @param plannedMessage - Planned message returned by the shared planned-message actions.
50
+ * @returns Snapshot written into the sidecar.
51
+ *
52
+ * @private function of `prepareLocalAgentPlannedMessagesSidecar`
53
+ */
54
+ function createLocalAgentPlannedMessageSnapshot(plannedMessage: {
55
+ readonly timeoutId: string;
56
+ readonly dueAt: string;
57
+ readonly message: string | null;
58
+ readonly intervalMs: number | null;
59
+ }): AgentPlannedMessageSnapshot {
60
+ return {
61
+ timeoutId: plannedMessage.timeoutId,
62
+ dueAt: plannedMessage.dueAt,
63
+ message: plannedMessage.message,
64
+ intervalMs: plannedMessage.intervalMs,
65
+ };
66
+ }
@@ -1,11 +1,14 @@
1
1
  import type { Json } from '@/src/database/schema';
2
+ import type { ToolCall } from '@promptbook-local/types';
2
3
  import { mkdir, readFile, rm, writeFile } from 'fs/promises';
3
4
  import { join } from 'path';
4
5
  import { parseAgentSource } from '../../../../../src/book-2.0/agent-source/parseAgentSource';
5
6
  import {
6
7
  buildAgentMessageRunReportPath,
7
8
  parseAgentMessageRunReport,
9
+ type AgentMessageRunReport,
8
10
  } from '../../../../../src/book-3.0/AgentMessageRunReport';
11
+ import { createAnsweredMessageChipToolCalls } from '../chatMessageChips/createAnsweredMessageChipToolCalls';
9
12
  import { createUserChatJobFailureDetails } from '../userChat/createUserChatJobFailureDetails';
10
13
  import { claimNextQueuedUserChatJob } from '../userChat/claimNextQueuedUserChatJob';
11
14
  import { finalizeUserChatJob } from '../userChat/finalizeUserChatJob';
@@ -14,6 +17,7 @@ import { getUserChatJobById } from '../userChat/getUserChatJobById';
14
17
  import { persistUserChatJobTerminalState } from '../userChat/persistUserChatJobTerminalState';
15
18
  import { provideUserChatJobTable } from '../userChat/provideUserChatJobTable';
16
19
  import { updateUserChatAssistantMessage } from '../userChat/updateUserChatAssistantMessage';
20
+ import { resolveUserChatJobExecutionTiming } from '../userChat/userChatJobRunReport';
17
21
  import type { UserChatJobRecord } from '../userChat/UserChatJobRecord';
18
22
  import type { UserChatJobRow } from '../userChat/UserChatJobRow';
19
23
  import { createUserChatRunnerThreadMessages } from '../userChat/userChatMessageLifecycle';
@@ -32,6 +36,12 @@ import {
32
36
  parseLocalChatFinishedMessageBook,
33
37
  } from './localChatMessageBook';
34
38
  import { LOCAL_USER_CHAT_JOB_ANSWER_TIMEOUT_MS, LOCAL_USER_CHAT_JOB_PROVIDER } from './localChatRunnerConstants';
39
+ import {
40
+ applyLocalAgentPlannedMessageCommands,
41
+ removeLocalAgentPlannedMessagesSidecar,
42
+ type AppliedAgentPlannedMessageCommand,
43
+ } from './applyLocalAgentPlannedMessageCommands';
44
+ import { prepareLocalAgentPlannedMessagesSidecar } from './prepareLocalAgentPlannedMessagesSidecar';
35
45
  import {
36
46
  ensureLocalAgentFolder,
37
47
  loadLocalAgentSourceSnapshot,
@@ -191,6 +201,7 @@ async function enqueueLocalUserChatJobWithinTerminalCapture(
191
201
  metadata,
192
202
  primaryAgentName: agentSourceSnapshot.agentName,
193
203
  });
204
+ await prepareLocalAgentPlannedMessagesSidecarIfPossible({ job, agentFolder, metadata });
194
205
  await mkdir(join(agentFolder.directoryPath, 'messages', 'queued'), { recursive: true });
195
206
  await writeFile(queuedMessagePath, createLocalChatQueuedMessageBook({ messages: threadMessages }), 'utf-8');
196
207
 
@@ -228,6 +239,22 @@ async function prepareLocalTeamConversationWorkspaceIfPossible(
228
239
  }
229
240
  }
230
241
 
242
+ /**
243
+ * Prepares the planned-message sidecar without preventing an answer when planned messages cannot be read.
244
+ */
245
+ async function prepareLocalAgentPlannedMessagesSidecarIfPossible(
246
+ options: Parameters<typeof prepareLocalAgentPlannedMessagesSidecar>[0],
247
+ ): Promise<void> {
248
+ try {
249
+ await prepareLocalAgentPlannedMessagesSidecar(options);
250
+ } catch (error) {
251
+ console.warn('[local-chat-runner] planned_messages_sidecar_prepare_failed', {
252
+ agentPermanentId: options.job.agentPermanentId,
253
+ error,
254
+ });
255
+ }
256
+ }
257
+
231
258
  /**
232
259
  * Synchronizes one already queued local job with message-folder output.
233
260
  */
@@ -245,7 +272,15 @@ async function synchronizeLocalUserChatJob(
245
272
  });
246
273
 
247
274
  if (content) {
248
- await persistLocalUserChatJobRunReportIfPresent(job, agentDirectoryPath, metadata);
275
+ const runReport = await persistLocalUserChatJobRunReportIfPresent(job, agentDirectoryPath, metadata);
276
+ const executionTiming = resolveUserChatJobExecutionTiming(runReport);
277
+ // Note: Planned messages are applied before the answer becomes terminal, so a wake-up the
278
+ // agent announced in its answer is already stored when the answer becomes visible.
279
+ const appliedPlannedMessageCommands = await applyLocalAgentPlannedMessageCommandsIfPossible({
280
+ job,
281
+ agentDirectoryPath,
282
+ metadata,
283
+ });
249
284
  const teamConversations = await parseFinishedLocalTeamConversations({
250
285
  agentDirectoryPath,
251
286
  metadata,
@@ -255,13 +290,20 @@ async function synchronizeLocalUserChatJob(
255
290
  job,
256
291
  conversations: teamConversations,
257
292
  });
293
+ const chipToolCalls = await createAnsweredMessageChipToolCallsIfPossible({
294
+ job,
295
+ appliedPlannedMessageCommands,
296
+ touchedProjectNames: runReport?.touchedProjectNames || [],
297
+ });
298
+ const toolCalls = [...teamToolCalls, ...chipToolCalls];
258
299
  await persistUserChatJobTerminalState({
259
300
  job,
260
301
  status: 'COMPLETED',
261
302
  provider: LOCAL_USER_CHAT_JOB_PROVIDER,
262
303
  content,
263
- toolCalls: teamToolCalls.length > 0 ? teamToolCalls : undefined,
264
- generationDurationMs: resolveLocalUserChatJobDurationMs(metadata.queuedAt),
304
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
305
+ executedAt: executionTiming?.executedAt,
306
+ generationDurationMs: executionTiming?.generationDurationMs,
265
307
  });
266
308
  return { didMutate: true, outcome: 'completed' };
267
309
  }
@@ -278,26 +320,26 @@ async function synchronizeLocalUserChatJob(
278
320
  return { didMutate: false, outcome: 'waiting' };
279
321
  }
280
322
 
323
+ await removeLocalAgentPlannedMessagesSidecar(agentDirectoryPath, metadata);
281
324
  await persistUserChatJobTerminalState({
282
325
  job,
283
326
  status: 'FAILED',
284
327
  provider: LOCAL_USER_CHAT_JOB_PROVIDER,
285
328
  failureReason,
286
329
  failureDetails: createLocalUserChatJobFailureDetails(job, failureReason, 'localRunnerFailedFile'),
287
- generationDurationMs: resolveLocalUserChatJobDurationMs(metadata.queuedAt),
288
330
  });
289
331
  return { didMutate: true, outcome: 'failed' };
290
332
  }
291
333
 
292
334
  if (isLocalUserChatJobTimedOut(metadata) && job.status !== 'FAILED') {
293
335
  const failureReason = 'Local agent runner did not finish the queued message within 30 minutes.';
336
+ await removeLocalAgentPlannedMessagesSidecar(agentDirectoryPath, metadata);
294
337
  await persistUserChatJobTerminalState({
295
338
  job,
296
339
  status: 'FAILED',
297
340
  provider: LOCAL_USER_CHAT_JOB_PROVIDER,
298
341
  failureReason,
299
342
  failureDetails: createLocalUserChatJobFailureDetails(job, failureReason, 'localRunnerTimeout'),
300
- generationDurationMs: resolveLocalUserChatJobDurationMs(metadata.queuedAt),
301
343
  });
302
344
  return { didMutate: true, outcome: 'failed' };
303
345
  }
@@ -315,28 +357,34 @@ async function synchronizeLocalUserChatJob(
315
357
  *
316
358
  * The report is best-effort telemetry shown in the admin task details, so any read or
317
359
  * persistence failure only logs a warning instead of failing the finished chat answer.
360
+ *
361
+ * @returns The report of the answered message, or `null` when the runner wrote none.
318
362
  */
319
363
  async function persistLocalUserChatJobRunReportIfPresent(
320
364
  job: UserChatJobRecord,
321
365
  agentDirectoryPath: string,
322
366
  metadata: LocalUserChatJobMetadata,
323
- ): Promise<void> {
367
+ ): Promise<AgentMessageRunReport | null> {
324
368
  try {
325
369
  const reportFileContent = await readOptionalTextFile(
326
370
  join(agentDirectoryPath, buildAgentMessageRunReportPath(metadata.finishedPath)),
327
371
  );
328
372
  const report = reportFileContent === null ? null : parseAgentMessageRunReport(reportFileContent);
329
373
  if (!report) {
330
- return;
374
+ return null;
331
375
  }
332
376
 
333
377
  await persistUserChatJobRunReport(job, report);
378
+
379
+ return report;
334
380
  } catch (error) {
335
381
  console.warn('[local-chat-runner] run_report_sync_failed', {
336
382
  chatId: job.chatId,
337
383
  jobId: job.id,
338
384
  error,
339
385
  });
386
+
387
+ return null;
340
388
  }
341
389
  }
342
390
 
@@ -451,9 +499,61 @@ async function removeQueuedLocalMessageIfPresent(metadata: LocalUserChatJobMetad
451
499
  return;
452
500
  }
453
501
 
454
- await rm(join(resolveLocalAgentRootPath(), metadata.agentDirectoryName, metadata.queuedPath), {
455
- force: true,
456
- }).catch(() => undefined);
502
+ const agentDirectoryPath = join(resolveLocalAgentRootPath(), metadata.agentDirectoryName);
503
+
504
+ await rm(join(agentDirectoryPath, metadata.queuedPath), { force: true }).catch(() => undefined);
505
+ await removeLocalAgentPlannedMessagesSidecar(agentDirectoryPath, metadata);
506
+ }
507
+
508
+ /**
509
+ * Applies planned-message commands without preventing an already answered message from completing.
510
+ *
511
+ * @returns The applied commands, or an empty list when the sidecar could not be applied.
512
+ */
513
+ async function applyLocalAgentPlannedMessageCommandsIfPossible(
514
+ options: Parameters<typeof applyLocalAgentPlannedMessageCommands>[0],
515
+ ): Promise<ReadonlyArray<AppliedAgentPlannedMessageCommand>> {
516
+ try {
517
+ return await applyLocalAgentPlannedMessageCommands(options);
518
+ } catch (error) {
519
+ console.warn('[local-chat-runner] planned_messages_sidecar_apply_failed', {
520
+ chatId: options.job.chatId,
521
+ jobId: options.job.id,
522
+ error,
523
+ });
524
+
525
+ return [];
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Builds the chips of one answered message without preventing it from completing.
531
+ *
532
+ * The chips only enrich the answer, so a failure to resolve them never hides an answer the
533
+ * agent already produced.
534
+ *
535
+ * @returns Tool calls rendered as chips below the answer, or an empty list when they failed.
536
+ */
537
+ async function createAnsweredMessageChipToolCallsIfPossible(options: {
538
+ readonly job: UserChatJobRecord;
539
+ readonly appliedPlannedMessageCommands: ReadonlyArray<AppliedAgentPlannedMessageCommand>;
540
+ readonly touchedProjectNames: ReadonlyArray<string>;
541
+ }): Promise<ReadonlyArray<ToolCall>> {
542
+ try {
543
+ return await createAnsweredMessageChipToolCalls({
544
+ agentPermanentId: options.job.agentPermanentId,
545
+ appliedPlannedMessageCommands: options.appliedPlannedMessageCommands,
546
+ touchedProjectNames: options.touchedProjectNames,
547
+ });
548
+ } catch (error) {
549
+ console.warn('[local-chat-runner] message_chips_build_failed', {
550
+ chatId: options.job.chatId,
551
+ jobId: options.job.id,
552
+ error,
553
+ });
554
+
555
+ return [];
556
+ }
457
557
  }
458
558
 
459
559
  /**
@@ -479,18 +579,6 @@ function isLocalUserChatJobTimedOut(metadata: LocalUserChatJobMetadata): boolean
479
579
  return Number.isFinite(queuedAtMs) && Date.now() - queuedAtMs >= LOCAL_USER_CHAT_JOB_ANSWER_TIMEOUT_MS;
480
580
  }
481
581
 
482
- /**
483
- * Resolves elapsed time since the local message was queued.
484
- */
485
- function resolveLocalUserChatJobDurationMs(queuedAt: string): number | undefined {
486
- const queuedAtMs = Date.parse(queuedAt);
487
- if (!Number.isFinite(queuedAtMs)) {
488
- return undefined;
489
- }
490
-
491
- return Math.max(0, Date.now() - queuedAtMs);
492
- }
493
-
494
582
  /**
495
583
  * Returns true when one filesystem error indicates a missing path.
496
584
  */
@@ -28,6 +28,10 @@ export async function persistUserChatJobTerminalState(options: {
28
28
  provider?: string | null;
29
29
  failureReason?: string | null;
30
30
  failureDetails?: string | null;
31
+ /**
32
+ * Actual time at which an out-of-process runner finished this assistant message.
33
+ */
34
+ executedAt?: NonNullable<ChatMessage['createdAt']>;
31
35
  generationDurationMs?: number;
32
36
  }): Promise<void> {
33
37
  let updatedChat: Awaited<ReturnType<typeof updateUserChatAssistantMessage>> | null = null;
@@ -47,6 +51,7 @@ export async function persistUserChatJobTerminalState(options: {
47
51
  ongoingToolCalls: undefined,
48
52
  toolCalls: options.toolCalls ?? message.toolCalls,
49
53
  completedToolCalls: options.toolCalls ?? message.completedToolCalls,
54
+ createdAt: options.executedAt ?? message.createdAt,
50
55
  generationDurationMs: options.generationDurationMs ?? message.generationDurationMs,
51
56
  progressCard: undefined,
52
57
  availableTools: options.availableTools ?? message.availableTools,
@@ -1,4 +1,5 @@
1
1
  import type { Json } from '@/src/database/schema';
2
+ import type { ChatMessage } from '@promptbook-local/types';
2
3
  import {
3
4
  normalizeAgentMessageRunReport,
4
5
  type AgentMessageRunReport,
@@ -42,6 +43,35 @@ export function withUserChatJobRunReport(
42
43
  };
43
44
  }
44
45
 
46
+ /**
47
+ * Resolves display-ready execution timing from one runner-produced message report.
48
+ *
49
+ * The runner's timestamps describe only active harness work, unlike durable job timestamps which also include time
50
+ * waiting for the out-of-process runner to pick up a queued message.
51
+ *
52
+ * @returns The completed message timestamp and actual execution duration, or `null` for older reports without timing.
53
+ */
54
+ export function resolveUserChatJobExecutionTiming(report: AgentMessageRunReport | null): {
55
+ readonly executedAt: NonNullable<ChatMessage['createdAt']>;
56
+ readonly generationDurationMs: number;
57
+ } | null {
58
+ const executionTiming = report?.executionTiming;
59
+ if (!executionTiming) {
60
+ return null;
61
+ }
62
+
63
+ const startedAtMilliseconds = Date.parse(executionTiming.startedAt);
64
+ const finishedAtMilliseconds = Date.parse(executionTiming.finishedAt);
65
+ if (!Number.isFinite(startedAtMilliseconds) || !Number.isFinite(finishedAtMilliseconds)) {
66
+ return null;
67
+ }
68
+
69
+ return {
70
+ executedAt: executionTiming.finishedAt as NonNullable<ChatMessage['createdAt']>,
71
+ generationDurationMs: Math.max(0, finishedAtMilliseconds - startedAtMilliseconds),
72
+ };
73
+ }
74
+
45
75
  /**
46
76
  * Persists one runner-produced run report into the job parameters so task details can show it.
47
77
  */
@@ -1,20 +1,28 @@
1
1
  import { spaceTrim } from 'spacetrim';
2
+ import { formatTimeoutDurationHuman } from '../../../../../src/book-components/Chat/utils/timeoutToolCallPresentation';
2
3
 
3
4
  /**
4
5
  * Builds the synthetic user-like message injected when a timeout elapses.
5
6
  *
7
+ * A repeating planned message stays armed after every firing, so its wake-up says so: the agent
8
+ * only has to act on the message itself, not to plan the same wake-up again.
9
+ *
6
10
  * @private internal utility of userChatTimeout
7
11
  */
8
12
  export function createTimeoutWakeUpMessage(options: {
9
13
  readonly timeoutId: string;
10
14
  readonly durationMs: number;
15
+ readonly intervalMs?: number | null;
11
16
  readonly message?: string | null;
12
17
  }): string {
13
18
  const message = options.message?.trim() || '';
19
+ const headline = options.intervalMs
20
+ ? `⏱️ Planned message elapsed, repeating every ${formatTimeoutDurationHuman(options.intervalMs)}.`
21
+ : `⏱️ Timeout elapsed after ${options.durationMs}ms.`;
14
22
 
15
23
  return spaceTrim(
16
24
  (block) => `
17
- ⏱️ Timeout elapsed after ${options.durationMs}ms.
25
+ ${headline}
18
26
  timeoutId: ${options.timeoutId}
19
27
  ${message ? block(message) : ''}
20
28
  `,