@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
@@ -0,0 +1,229 @@
1
+ import { join } from 'path';
2
+ import { AGENT_MESSAGES_DIRECTORY_PATH } from './agentFolderPaths';
3
+ import { createAgentMessageSidecarBaseName } from './createAgentMessageSidecarBaseName';
4
+
5
+ // Note: [💞] This file defines the shared planned-message sidecar convention rather than one standalone entity.
6
+
7
+ /**
8
+ * Relative directory holding the planned-message sidecar of each queued message.
9
+ *
10
+ * @private internal convention shared by the Agents Server and agent-folder runner
11
+ */
12
+ export const AGENT_PLANNED_MESSAGES_DIRECTORY_PATH = join(AGENT_MESSAGES_DIRECTORY_PATH, 'planned');
13
+
14
+ /**
15
+ * Command actions an agent can request from its planned-message sidecar.
16
+ *
17
+ * @private internal convention shared by the Agents Server and agent-folder runner
18
+ */
19
+ export const AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS = ['set', 'cancel'] as const;
20
+
21
+ /**
22
+ * One planned-message command action.
23
+ *
24
+ * @private internal convention shared by the Agents Server and agent-folder runner
25
+ */
26
+ export type AgentPlannedMessageCommandAction = (typeof AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS)[number];
27
+
28
+ /**
29
+ * One planned message that is already waiting to wake the agent.
30
+ *
31
+ * A planned message repeats like `setInterval`, so `intervalMs` is what the agent compares with its
32
+ * goal, while `dueAt` only says when the nearest repetition happens.
33
+ *
34
+ * @private internal convention shared by the Agents Server and agent-folder runner
35
+ */
36
+ export type AgentPlannedMessageSnapshot = {
37
+ readonly timeoutId: string;
38
+ readonly dueAt: string;
39
+ readonly message: string | null;
40
+
41
+ /**
42
+ * Repeat interval in milliseconds, or `null` for a planned message that wakes the agent only once.
43
+ */
44
+ readonly intervalMs: number | null;
45
+ };
46
+
47
+ /**
48
+ * One command written by the coding harness into its planned-message sidecar.
49
+ *
50
+ * The payload stays untyped on purpose: it is untrusted harness output and is validated by the very
51
+ * same shared planned-message actions that back the model tools and the internal runtime API.
52
+ *
53
+ * For a `set` command, `milliseconds` is the repeat interval of the planned message, not a one-shot delay.
54
+ *
55
+ * @private internal convention shared by the Agents Server and agent-folder runner
56
+ */
57
+ export type AgentPlannedMessageCommand = {
58
+ readonly action: AgentPlannedMessageCommandAction;
59
+ readonly milliseconds?: unknown;
60
+ readonly message?: unknown;
61
+ readonly timeoutId?: unknown;
62
+ };
63
+
64
+ /**
65
+ * Planned-message sidecar prepared for one queued message.
66
+ *
67
+ * `currentPlannedMessages` is written by the Agents Server and is read-only for the agent, while
68
+ * `commands` is the only channel through which a coding harness can plan or cancel a wake-up.
69
+ *
70
+ * @private internal convention shared by the Agents Server and agent-folder runner
71
+ */
72
+ export type AgentPlannedMessagesSidecar = {
73
+ readonly version: 1;
74
+ readonly agentPermanentId: string;
75
+ readonly currentPlannedMessages: ReadonlyArray<AgentPlannedMessageSnapshot>;
76
+ readonly commands: ReadonlyArray<AgentPlannedMessageCommand>;
77
+ };
78
+
79
+ /**
80
+ * Creates the relative planned-message sidecar path of one queued message.
81
+ *
82
+ * @param messageFileName - File name of the queued `.book` message.
83
+ * @returns Relative path of the sidecar inside the agent folder.
84
+ *
85
+ * @private internal convention shared by the Agents Server and agent-folder runner
86
+ */
87
+ export function createAgentPlannedMessagesSidecarPath(messageFileName: string): string {
88
+ return join(AGENT_PLANNED_MESSAGES_DIRECTORY_PATH, `${createAgentMessageSidecarBaseName(messageFileName)}.json`);
89
+ }
90
+
91
+ /**
92
+ * Serializes one planned-message sidecar into its stored representation.
93
+ *
94
+ * @param sidecar - Sidecar prepared for one queued message.
95
+ * @returns Pretty-printed JSON the coding harness can edit by hand.
96
+ *
97
+ * @private internal convention shared by the Agents Server and agent-folder runner
98
+ */
99
+ export function createAgentPlannedMessagesSidecarContent(sidecar: AgentPlannedMessagesSidecar): string {
100
+ return `${JSON.stringify(sidecar, null, 2)}\n`;
101
+ }
102
+
103
+ /**
104
+ * Parses one untrusted planned-message sidecar written back by a coding harness.
105
+ *
106
+ * A malformed sidecar never fails the already answered chat, so this returns `null` instead of throwing.
107
+ *
108
+ * @param sidecarContent - Raw file content read from the agent folder.
109
+ * @returns Normalized sidecar, or `null` when it cannot be used.
110
+ *
111
+ * @private internal convention shared by the Agents Server and agent-folder runner
112
+ */
113
+ export function parseAgentPlannedMessagesSidecar(sidecarContent: string): AgentPlannedMessagesSidecar | null {
114
+ let parsedSidecar: unknown;
115
+
116
+ try {
117
+ parsedSidecar = JSON.parse(sidecarContent);
118
+ } catch {
119
+ return null;
120
+ }
121
+
122
+ if (!parsedSidecar || typeof parsedSidecar !== 'object' || Array.isArray(parsedSidecar)) {
123
+ return null;
124
+ }
125
+
126
+ const sidecar = parsedSidecar as Partial<AgentPlannedMessagesSidecar>;
127
+
128
+ if (sidecar.version !== 1 || typeof sidecar.agentPermanentId !== 'string') {
129
+ return null;
130
+ }
131
+
132
+ return {
133
+ version: 1,
134
+ agentPermanentId: sidecar.agentPermanentId,
135
+ currentPlannedMessages: normalizeAgentPlannedMessageSnapshots(sidecar.currentPlannedMessages),
136
+ commands: normalizeAgentPlannedMessageCommands(sidecar.commands),
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Keeps only the commands whose action is understood by the planned-message actions.
142
+ *
143
+ * @param rawCommands - Untrusted `commands` value read from one sidecar.
144
+ * @returns Commands that can be dispatched, in their original order.
145
+ *
146
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
147
+ */
148
+ function normalizeAgentPlannedMessageCommands(rawCommands: unknown): Array<AgentPlannedMessageCommand> {
149
+ if (!Array.isArray(rawCommands)) {
150
+ return [];
151
+ }
152
+
153
+ return rawCommands.filter(isAgentPlannedMessageCommand);
154
+ }
155
+
156
+ /**
157
+ * Checks whether one untrusted sidecar entry names a supported planned-message action.
158
+ *
159
+ * @param value - Untrusted command entry.
160
+ * @returns `true` when the entry can be dispatched.
161
+ *
162
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
163
+ */
164
+ function isAgentPlannedMessageCommand(value: unknown): value is AgentPlannedMessageCommand {
165
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
166
+ return false;
167
+ }
168
+
169
+ const action = (value as AgentPlannedMessageCommand).action;
170
+
171
+ return (
172
+ typeof action === 'string' &&
173
+ AGENT_PLANNED_MESSAGE_COMMAND_ACTIONS.includes(action as AgentPlannedMessageCommandAction)
174
+ );
175
+ }
176
+
177
+ /**
178
+ * Keeps only the already planned messages that can be shown to the agent.
179
+ *
180
+ * @param rawSnapshots - Untrusted `currentPlannedMessages` value read from one sidecar.
181
+ * @returns Planned-message snapshots with the fields required for display and cancellation.
182
+ *
183
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
184
+ */
185
+ function normalizeAgentPlannedMessageSnapshots(rawSnapshots: unknown): Array<AgentPlannedMessageSnapshot> {
186
+ if (!Array.isArray(rawSnapshots)) {
187
+ return [];
188
+ }
189
+
190
+ return rawSnapshots.filter(isAgentPlannedMessageSnapshot).map(createNormalizedAgentPlannedMessageSnapshot);
191
+ }
192
+
193
+ /**
194
+ * Normalizes the optional fields of one already planned message.
195
+ *
196
+ * @param snapshot - Planned-message entry with the required identity fields.
197
+ * @returns Snapshot with a usable repeat interval and message.
198
+ *
199
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
200
+ */
201
+ function createNormalizedAgentPlannedMessageSnapshot(
202
+ snapshot: AgentPlannedMessageSnapshot,
203
+ ): AgentPlannedMessageSnapshot {
204
+ const intervalMs = Number(snapshot.intervalMs);
205
+
206
+ return {
207
+ timeoutId: snapshot.timeoutId,
208
+ dueAt: snapshot.dueAt,
209
+ message: typeof snapshot.message === 'string' ? snapshot.message : null,
210
+ intervalMs: Number.isFinite(intervalMs) && intervalMs > 0 ? Math.floor(intervalMs) : null,
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Checks whether one untrusted sidecar entry describes an already planned message.
216
+ *
217
+ * @param value - Untrusted planned-message entry.
218
+ * @returns `true` when the entry can be shown and cancelled.
219
+ *
220
+ * @private internal utility of `parseAgentPlannedMessagesSidecar`
221
+ */
222
+ function isAgentPlannedMessageSnapshot(value: unknown): value is AgentPlannedMessageSnapshot {
223
+ return Boolean(
224
+ value &&
225
+ typeof value === 'object' &&
226
+ typeof (value as AgentPlannedMessageSnapshot).timeoutId === 'string' &&
227
+ typeof (value as AgentPlannedMessageSnapshot).dueAt === 'string',
228
+ );
229
+ }
@@ -1,5 +1,6 @@
1
- import { basename, join } from 'path';
1
+ import { join } from 'path';
2
2
  import { AGENT_FINISHED_MESSAGES_DIRECTORY_PATH, AGENT_MESSAGES_DIRECTORY_PATH } from './agentFolderPaths';
3
+ import { createAgentMessageSidecarBaseName } from './createAgentMessageSidecarBaseName';
3
4
 
4
5
  // Note: [💞] This file defines the shared TEAM workspace convention rather than one standalone entity.
5
6
 
@@ -71,10 +72,7 @@ export type AgentTeamConversationWorkspaceManifest = {
71
72
  * @private internal convention shared by the Agents Server and agent-folder runner
72
73
  */
73
74
  export function createAgentTeamConversationWorkspaceDirectoryName(messageFileName: string): string {
74
- const rawBaseName = basename(messageFileName).replace(/\.book$/iu, '');
75
- const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
76
-
77
- return normalizedBaseName || 'message';
75
+ return createAgentMessageSidecarBaseName(messageFileName);
78
76
  }
79
77
 
80
78
  /**
@@ -0,0 +1,20 @@
1
+ import { basename } from 'path';
2
+
3
+ /**
4
+ * Creates the stable file-system safe base name that belongs to one queued `.book` message.
5
+ *
6
+ * Every sidecar the Agents Server prepares next to a queued message (TEAM workspaces, planned-message
7
+ * files, ...) derives its own name from this one normalization, so a sidecar can always be found again
8
+ * from the message file name alone.
9
+ *
10
+ * @param messageFileName - File name of the queued message, with or without directories.
11
+ * @returns Normalized base name usable as a directory or file name.
12
+ *
13
+ * @private internal convention shared by the Agents Server and agent-folder runner
14
+ */
15
+ export function createAgentMessageSidecarBaseName(messageFileName: string): string {
16
+ const rawBaseName = basename(messageFileName).replace(/\.book$/iu, '');
17
+ const normalizedBaseName = rawBaseName.replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/^[._-]+|[._-]+$/gu, '');
18
+
19
+ return normalizedBaseName || 'message';
20
+ }
@@ -735,7 +735,12 @@
735
735
  border-radius: 999px;
736
736
  box-shadow: 0 2px 8px rgba(15, 23, 42, 0.12);
737
737
  color: inherit;
738
- font-family: 'OpenMojiColor', 'OpenMojiBlack', Arial, Helvetica, sans-serif;
738
+ /*
739
+ * OpenMoji includes ASCII digits for keycap emoji sequences. Keeping it first in this stack
740
+ * makes time labels such as `10:28 AM` use its non-text digit glyphs, so chips must inherit
741
+ * the chat typography and use OpenMoji only as its existing emoji fallback.
742
+ */
743
+ font-family: inherit;
739
744
  cursor: pointer;
740
745
  transition: all 0.2s ease;
741
746
  backdrop-filter: blur(8px);
@@ -7,6 +7,7 @@ import type { ChatMessage } from '../types/ChatMessage';
7
7
  import type { ChatParticipant } from '../types/ChatParticipant';
8
8
  import { getToolCallChipletInfo, TOOL_TITLES } from '../utils/getToolCallChipletInfo';
9
9
  import { resolveToolCallState } from '../utils/resolveToolCallState';
10
+ import { isTimeoutToolCallName } from '../utils/timeoutToolCallPresentation';
10
11
  import { getToolCallTimestamp } from '../utils/toolCallParsing/getToolCallTimestamp';
11
12
  import { parseToolCallArguments } from '../utils/toolCallParsing/parseToolCallArguments';
12
13
  import { parseToolCallResult } from '../utils/toolCallParsing/parseToolCallResult';
@@ -375,18 +376,6 @@ function isTimeToolCallName(toolName: string): boolean {
375
376
  return toolName === 'get_current_time' || toolName === 'useTime';
376
377
  }
377
378
 
378
- /**
379
- * Checks whether a tool name should use the timeout renderer.
380
- *
381
- * @param toolName - Raw tool name.
382
- * @returns `true` when the tool is a timeout-like tool.
383
- *
384
- * @private function of ChatToolCallModal
385
- */
386
- function isTimeoutToolCallName(toolName: string): boolean {
387
- return toolName === 'set_timeout' || toolName === 'cancel_timeout';
388
- }
389
-
390
379
  /**
391
380
  * Checks whether a tool name should use the email renderer.
392
381
  *
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Synthetic tool name used for agent-project chips in chat UI.
3
+ *
4
+ * The Agents Server emits one such tool call per project an answer viewed or edited, so the chat
5
+ * shows which projects the agent worked with below the message that touched them.
6
+ *
7
+ * @private internal chat-ui marker for touched agent projects
8
+ */
9
+ export const AGENT_PROJECT_TOOL_CALL_NAME = 'agent_project_touched';
10
+
11
+ /**
12
+ * Safe, user-facing metadata shown in agent-project chips.
13
+ *
14
+ * @private internal chat-ui type for touched agent projects
15
+ */
16
+ export type AgentProjectToolCallResult = {
17
+ /**
18
+ * Directory name of the project.
19
+ */
20
+ readonly projectName: string;
21
+
22
+ /**
23
+ * Human-readable project name shown in the chip.
24
+ */
25
+ readonly displayName?: string;
26
+
27
+ /**
28
+ * Link to the project page, when the chat knows where the project lives.
29
+ */
30
+ readonly projectHref?: string;
31
+ };
32
+
33
+ /**
34
+ * Parses the project-chip payload from one tool result.
35
+ *
36
+ * @param result - Raw tool result payload.
37
+ * @returns Parsed project metadata, or `null` when the result describes no project.
38
+ *
39
+ * @private internal helper reused by chip and modal rendering
40
+ */
41
+ export function parseAgentProjectToolCallResult(result: unknown): AgentProjectToolCallResult | null {
42
+ if (!result || typeof result !== 'object' || Array.isArray(result)) {
43
+ return null;
44
+ }
45
+
46
+ const candidate = result as Partial<AgentProjectToolCallResult>;
47
+ if (typeof candidate.projectName !== 'string' || candidate.projectName.trim().length === 0) {
48
+ return null;
49
+ }
50
+
51
+ return {
52
+ projectName: candidate.projectName.trim(),
53
+ ...(typeof candidate.displayName === 'string' && candidate.displayName.trim().length > 0
54
+ ? { displayName: candidate.displayName.trim() }
55
+ : {}),
56
+ ...(typeof candidate.projectHref === 'string' && candidate.projectHref.trim().length > 0
57
+ ? { projectHref: candidate.projectHref.trim() }
58
+ : {}),
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Resolves the chip label of one touched agent project.
64
+ *
65
+ * @param projectResult - Parsed project metadata.
66
+ * @returns Human-readable project name.
67
+ *
68
+ * @private internal helper reused by chip and modal rendering
69
+ */
70
+ export function resolveAgentProjectToolCallLabel(projectResult: AgentProjectToolCallResult): string {
71
+ return projectResult.displayName || projectResult.projectName;
72
+ }
73
+
74
+ // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1,6 +1,11 @@
1
1
  import type { ToolCall } from '../../../types/ToolCall';
2
2
  import { ASSISTANT_PREPARATION_TOOL_CALL_NAME } from '../../../types/ToolCall';
3
3
  import type { AgentChipData } from '../AgentChip/AgentChip';
4
+ import {
5
+ AGENT_PROJECT_TOOL_CALL_NAME,
6
+ parseAgentProjectToolCallResult,
7
+ resolveAgentProjectToolCallLabel,
8
+ } from './agentProjectToolCall';
4
9
  import { formatToolCallLocalTime } from './formatToolCallLocalTime';
5
10
  import { formatToolCallTranslationTemplate } from './formatToolCallTranslationTemplate';
6
11
  import {
@@ -112,6 +117,7 @@ type ToolCallChipletResolver = (context: ToolCallChipletContext) => ToolCallChip
112
117
  */
113
118
  const SPECIALIZED_TOOL_CALL_CHIPLET_RESOLVERS: ReadonlyArray<ToolCallChipletResolver> = [
114
119
  resolveWalletCredentialChipletInfo,
120
+ resolveAgentProjectToolCallChipletInfo,
115
121
  resolveTeamToolCallChipletInfo,
116
122
  resolveTimeToolCallChipletInfo,
117
123
  resolveEmailToolCallChipletInfo,
@@ -140,6 +146,7 @@ export function buildToolCallChipText(chipletInfo: ToolCallChipletInfo): string
140
146
  export const TOOL_TITLES: Record<string, { title: string; emoji: string }> = {
141
147
  [ASSISTANT_PREPARATION_TOOL_CALL_NAME]: { title: 'Preparing agent', emoji: '✨' },
142
148
  [WALLET_CREDENTIAL_TOOL_CALL_NAME]: { title: 'Credential used', emoji: '🔐' },
149
+ [AGENT_PROJECT_TOOL_CALL_NAME]: { title: 'Project', emoji: '📁' },
143
150
  'self-learning': { title: 'self-learning', emoji: '🧠' },
144
151
  retrieve_user_memory: { title: 'Reading memory', emoji: '🧠' },
145
152
  store_user_memory: { title: 'Storing memory', emoji: '🧠' },
@@ -320,6 +327,27 @@ function resolveWalletCredentialChipletInfo(context: ToolCallChipletContext): To
320
327
  return createEmojiToolCallChipletInfo(context, walletCredentialResult.credentialName);
321
328
  }
322
329
 
330
+ /**
331
+ * Resolves the project-name chip label for touched agent projects.
332
+ *
333
+ * @param context - Shared chiplet resolution context.
334
+ * @returns Chiplet info when the tool call marks a touched project, otherwise `null`.
335
+ *
336
+ * @private utility of `<Chat/>`
337
+ */
338
+ function resolveAgentProjectToolCallChipletInfo(context: ToolCallChipletContext): ToolCallChipletInfo | null {
339
+ if (context.toolCall.name !== AGENT_PROJECT_TOOL_CALL_NAME) {
340
+ return null;
341
+ }
342
+
343
+ const projectResult = parseAgentProjectToolCallResult(context.resultRaw);
344
+ if (!projectResult) {
345
+ return null;
346
+ }
347
+
348
+ return createEmojiToolCallChipletInfo(context, resolveAgentProjectToolCallLabel(projectResult));
349
+ }
350
+
323
351
  /**
324
352
  * Resolves a chip label and agent metadata for TEAM tool calls.
325
353
  *
@@ -2,12 +2,26 @@ import type { TODO_any } from '../../../utils/organization/TODO_any';
2
2
  import { formatToolCallDateTime } from './formatToolCallDateTime';
3
3
  import { formatToolCallTranslationTemplate } from './formatToolCallTranslationTemplate';
4
4
 
5
+ /**
6
+ * Tool name of one scheduled wake-up, used by every producer of timeout chips.
7
+ *
8
+ * @private internal timeout-chat constant
9
+ */
10
+ export const SET_TIMEOUT_TOOL_CALL_NAME = 'set_timeout';
11
+
12
+ /**
13
+ * Tool name of one cancelled wake-up, used by every producer of timeout chips.
14
+ *
15
+ * @private internal timeout-chat constant
16
+ */
17
+ export const CANCEL_TIMEOUT_TOOL_CALL_NAME = 'cancel_timeout';
18
+
5
19
  /**
6
20
  * Tool names handled by timeout-specific chat presentation.
7
21
  *
8
22
  * @private internal timeout-chat constant
9
23
  */
10
- const TIMEOUT_TOOL_NAMES = ['set_timeout', 'cancel_timeout'] as const;
24
+ const TIMEOUT_TOOL_NAMES = [SET_TIMEOUT_TOOL_CALL_NAME, CANCEL_TIMEOUT_TOOL_CALL_NAME] as const;
11
25
 
12
26
  /**
13
27
  * Milliseconds in one second.
@@ -412,9 +426,15 @@ function formatTimeoutDurationCompact(milliseconds: number): string {
412
426
  /**
413
427
  * Converts timeout duration into natural language sentence fragments.
414
428
  *
415
- * @private internal timeout-chat helper
429
+ * Shared by every timeout surface: the chat chips, the goal-chat planned messages of the Agents
430
+ * Server, and the planned-message prompt section of the agent-folder runner.
431
+ *
432
+ * @param milliseconds - Duration or repeat interval in milliseconds.
433
+ * @returns Label such as `5 minutes` or `1 hour 30 minutes`.
434
+ *
435
+ * @private internal utility of `<Chat/>` and the Agents Server planned messages
416
436
  */
417
- function formatTimeoutDurationHuman(milliseconds: number): string {
437
+ export function formatTimeoutDurationHuman(milliseconds: number): string {
418
438
  const normalizedMilliseconds = Math.max(0, Math.floor(milliseconds));
419
439
  const totalSeconds = Math.max(1, Math.floor(normalizedMilliseconds / SECOND_IN_MILLISECONDS));
420
440