@promptbook/cli 0.114.0-12 → 0.114.0-14

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 (86) 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 +776 -124
  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/scripts/run-codex-prompts/ping/pingCoderHarnessPeriodically.d.ts +22 -0
  38. package/esm/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  39. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  40. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  41. package/esm/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  42. package/esm/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  43. package/esm/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  44. package/esm/src/cli/cli-commands/coder/waitOptions.d.ts +12 -0
  45. package/esm/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  46. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  47. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  48. package/esm/src/version.d.ts +1 -1
  49. package/package.json +1 -1
  50. package/src/book-3.0/AgentMessageRunReport.ts +76 -0
  51. package/src/book-3.0/AgentPlannedMessagesSidecar.ts +229 -0
  52. package/src/book-3.0/AgentTeamConversationWorkspace.ts +3 -5
  53. package/src/book-3.0/createAgentMessageSidecarBaseName.ts +20 -0
  54. package/src/book-components/Chat/Chat/Chat.module.css +6 -1
  55. package/src/book-components/Chat/Chat/renderToolCallDetails.tsx +1 -12
  56. package/src/book-components/Chat/utils/agentProjectToolCall.ts +74 -0
  57. package/src/book-components/Chat/utils/getToolCallChipletInfo.ts +28 -0
  58. package/src/book-components/Chat/utils/timeoutToolCallPresentation.ts +23 -3
  59. package/src/cli/cli-commands/coder/ping.ts +35 -9
  60. package/src/cli/cli-commands/coder/waitOptions.ts +33 -0
  61. package/src/other/templates/getTemplatesPipelineCollection.ts +859 -725
  62. package/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.ts +105 -0
  63. package/src/utils/agent-message-runtime/resolveAgentMessageRuntimeActivity.ts +6 -54
  64. package/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.ts +147 -0
  65. package/src/version.ts +2 -2
  66. package/src/versions.txt +2 -0
  67. package/umd/index.umd.js +776 -124
  68. package/umd/index.umd.js.map +1 -1
  69. package/umd/scripts/run-agent-messages/messages/buildAgentGoalChatPromptSection.d.ts +22 -4
  70. package/umd/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +3 -3
  71. package/umd/scripts/run-agent-messages/messages/loadAgentPlannedMessagesSidecar.d.ts +10 -0
  72. package/umd/scripts/run-agent-messages/messages/resolveTouchedAgentProjects.d.ts +15 -0
  73. package/umd/scripts/run-codex-prompts/ping/pingCoderHarnessPeriodically.d.ts +22 -0
  74. package/umd/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  75. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  76. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  77. package/umd/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  78. package/umd/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  79. package/umd/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  80. package/umd/src/cli/cli-commands/coder/waitOptions.d.ts +12 -0
  81. package/umd/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  82. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  83. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  84. package/umd/src/version.d.ts +1 -1
  85. package/esm/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
  86. package/umd/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
@@ -6,6 +6,7 @@ import { AgentChat, ChatMessage, useSendMessageToLlmChat } from '@promptbook-loc
6
6
  import { RemoteAgent } from '@promptbook-local/core';
7
7
  import { ClientVersionMismatchError } from '@promptbook-local/utils';
8
8
  import { useCallback, useEffect, useMemo, useState, type CSSProperties } from 'react';
9
+ import { AGENT_PROJECT_TOOL_CALL_NAME } from '../../../../../../src/book-components/Chat/utils/agentProjectToolCall';
9
10
  import { string_agent_url } from '../../../../../../src/types/typeAliases';
10
11
  import type { AgentProjectItemInfo } from '../../../components/AgentProjects/AgentProjectReferencesList';
11
12
  import { useAgentProjectMarkdownReferences } from '../../../components/AgentProjects/useAgentProjectMarkdownReferences';
@@ -144,10 +145,6 @@ export function AgentChatWrapper(props: AgentChatWrapperProps) {
144
145
 
145
146
  const { value: agent } = usePromise(agentPromise, [agentPromise]);
146
147
  const teamAgentProfiles = useTeamAgentProfiles(agent?.capabilities);
147
- const markdownInlineReferences = useAgentProjectMarkdownReferences({
148
- agentPermanentId: agentName,
149
- projects: projectReferences,
150
- });
151
148
 
152
149
  // Error state management
153
150
  const [currentError, setCurrentError] = useState<FriendlyErrorMessage | null>(null);
@@ -157,6 +154,11 @@ export function AgentChatWrapper(props: AgentChatWrapperProps) {
157
154
  const [githubAppStatus, setGithubAppStatus] = useState<GithubAppStatusResponse | null>(null);
158
155
  const [calendarOAuthStatus, setCalendarOAuthStatus] = useState<CalendarOAuthStatusResponse | null>(null);
159
156
  const [renderedMessages, setRenderedMessages] = useState<ReadonlyArray<ChatMessage>>([]);
157
+ const markdownInlineReferences = useAgentProjectMarkdownReferences({
158
+ agentPermanentId: agentName,
159
+ projects: projectReferences,
160
+ messages: renderedMessages,
161
+ });
160
162
  const currentAgentPermanentId = useMemo(() => {
161
163
  return typeof (agent as { permanentId?: unknown } | undefined)?.permanentId === 'string'
162
164
  ? ((agent as { permanentId?: string }).permanentId as string)
@@ -454,6 +456,7 @@ export function AgentChatWrapper(props: AgentChatWrapperProps) {
454
456
  project_delete_file: t('chat.toolTitle.projectFileDeleter'),
455
457
  project_create_branch: t('chat.toolTitle.projectBranchCreator'),
456
458
  project_create_pull_request: t('chat.toolTitle.projectPullRequestCreator'),
459
+ [AGENT_PROJECT_TOOL_CALL_NAME]: t('chat.toolTitle.agentProjectTouched'),
457
460
  }}
458
461
  feedbackMode={toChatComponentFeedbackMode(feedbackMode)}
459
462
  onFeedback={shouldEnableFeedback ? handleFeedback : undefined}
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { Clock3Icon } from 'lucide-react';
3
+ import { Clock3Icon, RepeatIcon } from 'lucide-react';
4
4
  import { useCallback, useEffect, useState } from 'react';
5
+ import { formatTimeoutDurationHuman } from '../../../../../../../src/book-components/Chat/utils/timeoutToolCallPresentation';
5
6
  import { useServerLanguage } from '../../../../components/ServerLanguage/ServerLanguageProvider';
6
7
  import { cancelAgentUserTimeout, fetchAgentUserTimeouts, type UserChatTimeout } from '../../../../utils/userChatClient';
7
8
  import { formatChatTimeoutRemainingTime } from './formatChatTimeoutRemainingTime';
@@ -136,14 +137,28 @@ export function AgentGoalChatPlannedMessages({ agentName, currentTimestamp }: Ag
136
137
  {plannedMessage.message ||
137
138
  translateText('goalChat.plannedMessageDefaultLabel')}
138
139
  </div>
139
- <div className="mt-1 inline-flex items-center gap-1.5 text-xs font-medium text-amber-700 dark:text-amber-300">
140
- <Clock3Icon className="h-3.5 w-3.5" />
141
- <span>
142
- {formatChatTimeoutRemainingTime(plannedMessage.dueAt, currentTimestamp)}
143
- </span>
144
- <span className="text-slate-400 dark:text-slate-500">
145
- {new Date(plannedMessage.dueAt).toLocaleString()}
140
+ <div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs font-medium text-amber-700 dark:text-amber-300">
141
+ <span className="inline-flex items-center gap-1.5">
142
+ <Clock3Icon className="h-3.5 w-3.5" />
143
+ <span>
144
+ {formatChatTimeoutRemainingTime(plannedMessage.dueAt, currentTimestamp)}
145
+ </span>
146
+ <span className="text-slate-400 dark:text-slate-500">
147
+ {new Date(plannedMessage.dueAt).toLocaleString()}
148
+ </span>
146
149
  </span>
150
+ {plannedMessage.recurrenceIntervalMs !== null && (
151
+ <span className="inline-flex items-center gap-1.5 text-slate-500 dark:text-slate-400">
152
+ <RepeatIcon className="h-3.5 w-3.5" />
153
+ <span>
154
+ {translateText('goalChat.plannedMessageIntervalLabel', {
155
+ interval: formatTimeoutDurationHuman(
156
+ plannedMessage.recurrenceIntervalMs,
157
+ ),
158
+ })}
159
+ </span>
160
+ </span>
161
+ )}
147
162
  </div>
148
163
  </div>
149
164
  <button
@@ -4,6 +4,7 @@ import { Chat } from '@promptbook-local/components';
4
4
  import { useCallback, useMemo, type CSSProperties, type ReactNode } from 'react';
5
5
  import type { ChatParticipant } from '../../../../../../../src/book-components/Chat/types/ChatParticipant';
6
6
  import type { ChatSaveFormatHandlerOptions } from '../../../../../../../src/book-components/Chat/save/_common/ChatSaveFormatHandler';
7
+ import { AGENT_PROJECT_TOOL_CALL_NAME } from '../../../../../../../src/book-components/Chat/utils/agentProjectToolCall';
7
8
  import type { AgentProjectItemInfo } from '../../../../components/AgentProjects/AgentProjectReferencesList';
8
9
  import { useAgentProjectMarkdownReferences } from '../../../../components/AgentProjects/useAgentProjectMarkdownReferences';
9
10
  import { useAgentBackground } from '../../../../components/AgentProfile/useAgentBackground';
@@ -143,6 +144,7 @@ export function CanonicalAgentChatSurface({
143
144
  const markdownInlineReferences = useAgentProjectMarkdownReferences({
144
145
  agentPermanentId: agentName,
145
146
  projects: projectReferences,
147
+ messages: state.renderedMessages,
146
148
  });
147
149
  const chatBackgroundStyle = useMemo(
148
150
  () =>
@@ -174,7 +176,10 @@ export function CanonicalAgentChatSurface({
174
176
  ],
175
177
  );
176
178
  const { language, t: translateText } = useServerLanguage();
177
- const translations = useMemo(() => createCanonicalAgentChatTranslations(translateText), [translateText]);
179
+ const translations = useMemo(
180
+ () => createCanonicalAgentChatTranslations(translateText, isAgentGoalChat),
181
+ [isAgentGoalChat, translateText],
182
+ );
178
183
  const frozenChatBannerLabel = useMemo(() => {
179
184
  const sourceBannerLabel = readOnlySource ? getUserChatSourceBannerLabel(readOnlySource) : null;
180
185
  if (sourceBannerLabel) {
@@ -431,7 +436,10 @@ function renderCanonicalAgentChatLoadingSkeleton(): ReactNode {
431
436
  *
432
437
  * @private function of CanonicalAgentChatPanel
433
438
  */
434
- function createCanonicalAgentChatTranslations(translateText: TranslateText): CanonicalAgentChatTranslations {
439
+ function createCanonicalAgentChatTranslations(
440
+ translateText: TranslateText,
441
+ isAgentGoalChat: boolean,
442
+ ): CanonicalAgentChatTranslations {
435
443
  return {
436
444
  chatUiTranslations: {
437
445
  inputPlaceholder: translateText('chat.inputPlaceholder'),
@@ -520,9 +528,12 @@ function createCanonicalAgentChatTranslations(translateText: TranslateText): Can
520
528
  project_delete_file: translateText('chat.toolTitle.projectFileDeleter'),
521
529
  project_create_branch: translateText('chat.toolTitle.projectBranchCreator'),
522
530
  project_create_pull_request: translateText('chat.toolTitle.projectPullRequestCreator'),
531
+ [AGENT_PROJECT_TOOL_CALL_NAME]: translateText('chat.toolTitle.agentProjectTouched'),
523
532
  },
524
533
  timingTranslations: {
525
- answerDurationLabel: translateText('chat.answerDurationLabel'),
534
+ answerDurationLabel: translateText(
535
+ isAgentGoalChat ? 'goalChat.executionDurationLabel' : 'chat.answerDurationLabel',
536
+ ),
526
537
  },
527
538
  feedbackTranslations: {
528
539
  reportIssueButtonTitle: translateText('chat.feedback.reportIssueButtonTitle'),
@@ -4,8 +4,7 @@ import { ForbiddenPage } from '@/src/components/ForbiddenPage/ForbiddenPage';
4
4
  import type { AgentProjectItemInfo } from '@/src/components/AgentProjects/AgentProjectReferencesList';
5
5
  import { $provideServer } from '@/src/tools/$provideServer';
6
6
  import { resolveAgentAccess } from '@/src/utils/agentAccess';
7
- import { resolveAgentProjectsAccess } from '@/src/utils/agentProjects/agentProjectAccess';
8
- import { listAgentProjectChatReferences } from '@/src/utils/agentProjects/listAgentProjectChatReferences';
7
+ import { resolveAgentChatProjectReferences } from '@/src/utils/agentProjects/resolveAgentChatProjectReferences';
9
8
  import { loadChatConfiguration } from '@/src/utils/chatConfiguration';
10
9
  import { ensureChatHistoryIdentity } from '@/src/utils/currentUserIdentity';
11
10
  import { getCurrentUser } from '@/src/utils/getCurrentUser';
@@ -71,23 +70,6 @@ function buildCanonicalAgentChatPath(
71
70
  return query ? `${pathname}?${query}` : pathname;
72
71
  }
73
72
 
74
- /**
75
- * Resolves compact project references available to the agent chat surface.
76
- *
77
- * @param agentPermanentId - Permanent id of the agent owning the projects.
78
- * @returns Display-only project references safe to send to the client.
79
- */
80
- async function resolveAgentChatProjectReferences(
81
- agentPermanentId: string,
82
- ): Promise<ReadonlyArray<AgentProjectItemInfo>> {
83
- const projectAccess = await resolveAgentProjectsAccess(agentPermanentId);
84
- if (!projectAccess.isProjectOverviewVisible) {
85
- return [];
86
- }
87
-
88
- return await listAgentProjectChatReferences(agentPermanentId);
89
- }
90
-
91
73
  /**
92
74
  * Handles agent chat page.
93
75
  */
@@ -149,7 +131,7 @@ export default async function AgentChatPage({
149
131
  const providedServerPromise = $provideServer();
150
132
  const agentProjectReferencesPromise = isHeadless
151
133
  ? Promise.resolve<ReadonlyArray<AgentProjectItemInfo>>([])
152
- : resolveAgentChatProjectReferences(canonicalAgentId);
134
+ : resolveAgentChatProjectReferences({ agentPermanentId: canonicalAgentId });
153
135
  const shareTargetPayloadPromise = shareTarget
154
136
  ? peekShareTargetPayload({
155
137
  shareTargetId: shareTarget,
@@ -6,6 +6,7 @@ import { $provideServer } from '@/src/tools/$provideServer';
6
6
  import { resolveAgentProjectsAccess } from '@/src/utils/agentProjects/agentProjectAccess';
7
7
  import { buildAgentProjectsDashboardHref } from '@/src/utils/agentProjects/agentProjectHrefs';
8
8
  import { listAgentProjectChatReferences } from '@/src/utils/agentProjects/listAgentProjectChatReferences';
9
+ import { resolveAgentChatProjectReferences } from '@/src/utils/agentProjects/resolveAgentChatProjectReferences';
9
10
  import { isPublicAgentVisibility } from '@/src/utils/agentVisibility';
10
11
  import { ensureChatHistoryIdentity } from '@/src/utils/currentUserIdentity';
11
12
  import { createAgentEmailAddress } from '@/src/utils/email/agentEmailAddress';
@@ -689,7 +690,7 @@ export default async function AgentPage(props: AgentPageProps) {
689
690
 
690
691
  const [pageData, projects] = await Promise.all([
691
692
  loadAgentPageData(route.canonicalAgentId, Boolean(access.currentUser)),
692
- access.isProjectOverviewVisible ? listAgentProjectChatReferences(route.canonicalAgentId) : Promise.resolve([]),
693
+ resolveAgentChatProjectReferences({ agentPermanentId: route.canonicalAgentId, projectsAccess: access }),
693
694
  ]);
694
695
  return renderAgentProfilePage(createAgentPageViewModel(route, pageData, projects));
695
696
  }
@@ -0,0 +1,61 @@
1
+ 'use server';
2
+
3
+ import { resolveAgentProjectsAccess } from '@/src/utils/agentProjects/agentProjectAccess';
4
+ import type { AgentProjectReferenceInfo } from '@/src/utils/agentProjects/AgentProjectReferenceInfo';
5
+ import { listAgentProjectNames } from '@/src/utils/agentProjects/listAgentProjectNames';
6
+ import { resolveAgentChatProjectReferences } from '@/src/utils/agentProjects/resolveAgentChatProjectReferences';
7
+
8
+ /**
9
+ * Reloads the project references of one open chat when the agent's set of projects changed.
10
+ *
11
+ * A chat renders every mention of a project as a project chip built from these references, so a project
12
+ * created while the chat stays open must reach the browser without a page reload. Building the references
13
+ * walks every project folder, therefore the project names the chat already knows decide whether that work
14
+ * is done at all.
15
+ *
16
+ * @param agentPermanentId - Permanent id of the agent owning the projects.
17
+ * @param knownProjectNames - Project directory names the open chat already renders.
18
+ * @returns Fresh project references, or `null` when the agent owns exactly the known projects.
19
+ */
20
+ export async function $refreshAgentProjectChatReferencesAction(
21
+ agentPermanentId: string,
22
+ knownProjectNames: ReadonlyArray<string>,
23
+ ): Promise<ReadonlyArray<AgentProjectReferenceInfo> | null> {
24
+ const projectsAccess = await resolveAgentProjectsAccess(agentPermanentId);
25
+
26
+ if (!projectsAccess.isProjectOverviewVisible) {
27
+ return [];
28
+ }
29
+
30
+ const projectNames = await listAgentProjectNames(agentPermanentId);
31
+
32
+ if (areAgentProjectNamesEqual(projectNames, knownProjectNames)) {
33
+ return null;
34
+ }
35
+
36
+ return await resolveAgentChatProjectReferences({ agentPermanentId, projectsAccess });
37
+ }
38
+
39
+ /**
40
+ * Compares two collections of project directory names ignoring their order and letter case.
41
+ *
42
+ * @param firstProjectNames - Project names listed on the server.
43
+ * @param secondProjectNames - Project names reported by the open chat.
44
+ * @returns Whether both collections describe the same projects.
45
+ */
46
+ function areAgentProjectNamesEqual(
47
+ firstProjectNames: ReadonlyArray<string>,
48
+ secondProjectNames: ReadonlyArray<string>,
49
+ ): boolean {
50
+ if (firstProjectNames.length !== secondProjectNames.length) {
51
+ return false;
52
+ }
53
+
54
+ const normalizedSecondProjectNames = new Set(
55
+ secondProjectNames.map((secondProjectName) => secondProjectName.toLowerCase()),
56
+ );
57
+
58
+ return firstProjectNames.every((firstProjectName) =>
59
+ normalizedSecondProjectNames.has(firstProjectName.toLowerCase()),
60
+ );
61
+ }
@@ -0,0 +1,32 @@
1
+ import type { ChatMessage } from '@promptbook-local/types';
2
+
3
+ /**
4
+ * Resolves a key identifying the newest finished agent answer in one chat.
5
+ *
6
+ * A finished agent turn is the only moment when the agent can have created a project, so this key is the
7
+ * signal telling the chat to reload its project references. Messages which are still being generated are
8
+ * ignored on purpose — their content keeps changing while the progress card updates, which says nothing
9
+ * about the projects on the server.
10
+ *
11
+ * @param messages - Messages currently rendered in the chat.
12
+ * @returns Key of the newest finished agent message, or `null` when the agent has answered nothing yet.
13
+ */
14
+ export function resolveLatestCompletedAgentMessageKey(messages: ReadonlyArray<ChatMessage> | undefined): string | null {
15
+ if (!messages) {
16
+ return null;
17
+ }
18
+
19
+ for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex--) {
20
+ const message = messages[messageIndex];
21
+
22
+ if (message === undefined || message.sender === 'USER' || message.isComplete === false) {
23
+ continue;
24
+ }
25
+
26
+ // Note: Messages of chats which do not persist ids are identified by their position, which still
27
+ // moves forward with every new answer
28
+ return message.id === undefined ? `#${messageIndex}` : String(message.id);
29
+ }
30
+
31
+ return null;
32
+ }
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
- import type { MarkdownInlineReference } from '@promptbook-local/types';
3
+ import type { ChatMessage, MarkdownInlineReference } from '@promptbook-local/types';
4
4
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
+ import { $refreshAgentProjectChatReferencesAction } from '../../app/agents/[agentName]/projectReferenceActions';
5
6
  import {
6
7
  $startAgentProjectRuntimeAction,
7
8
  $stopAgentProjectRuntimeAction,
@@ -13,6 +14,7 @@ import {
13
14
  type AgentProjectMarkdownReferenceInfo,
14
15
  } from '../../utils/agentProjects/createAgentProjectMarkdownReferences';
15
16
  import { notifyError } from '../Notifications/notifications';
17
+ import { resolveLatestCompletedAgentMessageKey } from './resolveLatestCompletedAgentMessageKey';
16
18
 
17
19
  /**
18
20
  * Options used to build interactive project references for an Agents Server chat.
@@ -27,6 +29,14 @@ type UseAgentProjectMarkdownReferencesOptions = {
27
29
  * Initial browser-safe project references loaded by the server page.
28
30
  */
29
31
  readonly projects: ReadonlyArray<AgentProjectReferenceInfo>;
32
+
33
+ /**
34
+ * Messages currently rendered in the chat.
35
+ *
36
+ * They are watched only to notice that the agent finished an answer, which is when a project it just
37
+ * created must become a chip without waiting for a page reload.
38
+ */
39
+ readonly messages?: ReadonlyArray<ChatMessage>;
30
40
  };
31
41
 
32
42
  /**
@@ -38,7 +48,7 @@ type UseAgentProjectMarkdownReferencesOptions = {
38
48
  export function useAgentProjectMarkdownReferences(
39
49
  options: UseAgentProjectMarkdownReferencesOptions,
40
50
  ): ReadonlyArray<MarkdownInlineReference> {
41
- const { agentPermanentId, projects } = options;
51
+ const { agentPermanentId, projects, messages } = options;
42
52
  const [currentProjects, setCurrentProjects] = useState<ReadonlyArray<AgentProjectReferenceInfo>>(projects);
43
53
  const previousProjectsRef = useRef(projects);
44
54
 
@@ -51,6 +61,13 @@ export function useAgentProjectMarkdownReferences(
51
61
  setCurrentProjects(projects);
52
62
  }, [projects]);
53
63
 
64
+ useRefreshedAgentProjectReferences({
65
+ agentPermanentId,
66
+ currentProjects,
67
+ messages,
68
+ setCurrentProjects,
69
+ });
70
+
54
71
  /**
55
72
  * Merges one action result into the local project reference state.
56
73
  */
@@ -143,6 +160,111 @@ export function useAgentProjectMarkdownReferences(
143
160
  );
144
161
  }
145
162
 
163
+ /**
164
+ * Options of the refresh keeping chat project references current while the chat stays open.
165
+ *
166
+ * @private type of `useAgentProjectMarkdownReferences`
167
+ */
168
+ type UseRefreshedAgentProjectReferencesOptions = {
169
+ /**
170
+ * Permanent id of the agent owning the projects.
171
+ */
172
+ readonly agentPermanentId: string;
173
+
174
+ /**
175
+ * Project references the chat currently renders.
176
+ */
177
+ readonly currentProjects: ReadonlyArray<AgentProjectReferenceInfo>;
178
+
179
+ /**
180
+ * Messages currently rendered in the chat.
181
+ */
182
+ readonly messages: ReadonlyArray<ChatMessage> | undefined;
183
+
184
+ /**
185
+ * Replaces the project references the chat renders.
186
+ */
187
+ readonly setCurrentProjects: (projects: ReadonlyArray<AgentProjectReferenceInfo>) => void;
188
+ };
189
+
190
+ /**
191
+ * Reloads the project references after each finished agent answer which could have created a project.
192
+ *
193
+ * The server renders a chat with the projects existing at that moment, so an agent creating a project
194
+ * during the conversation would otherwise reference it as a plain link until the page is reloaded. The
195
+ * messages already present when the chat thread opens arrive together with their projects and never
196
+ * trigger a reload, and the server skips rebuilding the references while the agent owns the very same
197
+ * projects.
198
+ *
199
+ * @param options - Owning agent, current references, and the messages the chat renders.
200
+ *
201
+ * @private function of `useAgentProjectMarkdownReferences`
202
+ */
203
+ function useRefreshedAgentProjectReferences({
204
+ agentPermanentId,
205
+ currentProjects,
206
+ messages,
207
+ setCurrentProjects,
208
+ }: UseRefreshedAgentProjectReferencesOptions): void {
209
+ const currentProjectsRef = useRef(currentProjects);
210
+ const isLatestCompletedAgentMessageKeyKnownRef = useRef(false);
211
+ const refreshedAgentMessageKeyRef = useRef<string | null>(null);
212
+ const hasRenderedChatMessages = messages !== undefined && messages.length > 0;
213
+ const latestCompletedAgentMessageKey = resolveLatestCompletedAgentMessageKey(messages);
214
+
215
+ useEffect(() => {
216
+ currentProjectsRef.current = currentProjects;
217
+ }, [currentProjects]);
218
+
219
+ useEffect(() => {
220
+ if (!hasRenderedChatMessages) {
221
+ // Note: An empty thread is also the state of a chat whose history is still being loaded
222
+ return undefined;
223
+ }
224
+
225
+ if (!isLatestCompletedAgentMessageKeyKnownRef.current) {
226
+ // Note: The messages rendered when the chat opens were listed together with the server projects
227
+ isLatestCompletedAgentMessageKeyKnownRef.current = true;
228
+ refreshedAgentMessageKeyRef.current = latestCompletedAgentMessageKey;
229
+ return undefined;
230
+ }
231
+
232
+ if (refreshedAgentMessageKeyRef.current === latestCompletedAgentMessageKey) {
233
+ return undefined;
234
+ }
235
+
236
+ refreshedAgentMessageKeyRef.current = latestCompletedAgentMessageKey;
237
+ let isDisposed = false;
238
+
239
+ void (async () => {
240
+ const knownProjectNames = currentProjectsRef.current.map((project) => project.projectName);
241
+
242
+ try {
243
+ const refreshedProjects = await $refreshAgentProjectChatReferencesAction(
244
+ agentPermanentId,
245
+ knownProjectNames,
246
+ );
247
+
248
+ if (isDisposed || refreshedProjects === null) {
249
+ return;
250
+ }
251
+
252
+ setCurrentProjects(refreshedProjects);
253
+ } catch (error) {
254
+ // Note: A chat stays usable without fresh projects, the chip then appears after the next answer
255
+ console.error('[agents-server:agent-projects] Failed to refresh chat project references', {
256
+ agentPermanentId,
257
+ error,
258
+ });
259
+ }
260
+ })();
261
+
262
+ return () => {
263
+ isDisposed = true;
264
+ };
265
+ }, [agentPermanentId, hasRenderedChatMessages, latestCompletedAgentMessageKey, setCurrentProjects]);
266
+ }
267
+
146
268
  /**
147
269
  * Selects a useful server-action error message without assuming a particular serialized error type.
148
270
  *
@@ -17,10 +17,12 @@ export const SERVER_TRANSLATION_KEYS = [
17
17
  'common.goalChat',
18
18
  'goalChat.noticeTitle',
19
19
  'goalChat.noticeDescription',
20
+ 'goalChat.executionDurationLabel',
20
21
  'goalChat.plannedMessagesTitle',
21
22
  'goalChat.plannedMessagesEmptyLabel',
22
23
  'goalChat.plannedMessagesLoadErrorLabel',
23
24
  'goalChat.plannedMessageDefaultLabel',
25
+ 'goalChat.plannedMessageIntervalLabel',
24
26
  'goalChat.plannedMessageCancelLabel',
25
27
  'goalChat.plannedMessageCancellingLabel',
26
28
  'common.projects',
@@ -586,6 +588,7 @@ export const SERVER_TRANSLATION_KEYS = [
586
588
  'chat.toolTitle.projectFileDeleter',
587
589
  'chat.toolTitle.projectBranchCreator',
588
590
  'chat.toolTitle.projectPullRequestCreator',
591
+ 'chat.toolTitle.agentProjectTouched',
589
592
  'chatTimeout.defaultLabel',
590
593
  'chatTimeout.singleActiveLabel',
591
594
  'chatTimeout.multipleActiveLabel',
@@ -11,10 +11,12 @@ common.book: Book
11
11
  common.goalChat: Cíl
12
12
  goalChat.noticeTitle: Toto je cílový chat agenta
13
13
  goalChat.noticeDescription: Agent si sem píše sám a plánuje práci ke svému cíli, proto je toto vlákno pro lidi jen ke čtení.
14
+ goalChat.executionDurationLabel: 'běželo {duration}'
14
15
  goalChat.plannedMessagesTitle: Naplánované zprávy
15
16
  goalChat.plannedMessagesEmptyLabel: Žádné naplánované zprávy. Agent se probudí při nové zprávě nebo aktualizaci knihy.
16
17
  goalChat.plannedMessagesLoadErrorLabel: Naplánované zprávy se nepodařilo načíst.
17
18
  goalChat.plannedMessageDefaultLabel: Naplánované probuzení
19
+ goalChat.plannedMessageIntervalLabel: 'Opakování: {interval}'
18
20
  goalChat.plannedMessageCancelLabel: Zrušit
19
21
  goalChat.plannedMessageCancellingLabel: Ruším
20
22
  common.projects: Projekty
@@ -580,6 +582,7 @@ chat.toolTitle.projectFileWriter: Zápis souboru projektu
580
582
  chat.toolTitle.projectFileDeleter: Mazání souboru projektu
581
583
  chat.toolTitle.projectBranchCreator: Vytvoření větve projektu
582
584
  chat.toolTitle.projectPullRequestCreator: Vytvoření pull requestu
585
+ chat.toolTitle.agentProjectTouched: Projekt
583
586
  chatTimeout.defaultLabel: Naplánovaný časovač
584
587
  chatTimeout.singleActiveLabel: 1 aktivní časovač
585
588
  chatTimeout.multipleActiveLabel: '{count} aktivní časovače'
@@ -11,10 +11,12 @@ common.book: Book
11
11
  common.goalChat: Goal
12
12
  goalChat.noticeTitle: This is the agent's goal chat
13
13
  goalChat.noticeDescription: The agent writes here on its own to plan work towards its goal, so this thread is read-only for people.
14
+ goalChat.executionDurationLabel: 'executed for {duration}'
14
15
  goalChat.plannedMessagesTitle: Planned messages
15
16
  goalChat.plannedMessagesEmptyLabel: No planned messages. The agent will wake up when invoked by a new message or book update.
16
17
  goalChat.plannedMessagesLoadErrorLabel: Failed to load planned messages.
17
18
  goalChat.plannedMessageDefaultLabel: Planned wake-up
19
+ goalChat.plannedMessageIntervalLabel: Repeats every {interval}
18
20
  goalChat.plannedMessageCancelLabel: Cancel
19
21
  goalChat.plannedMessageCancellingLabel: Cancelling
20
22
  common.projects: Projects
@@ -584,6 +586,7 @@ chat.toolTitle.projectFileWriter: Writing project file
584
586
  chat.toolTitle.projectFileDeleter: Deleting project file
585
587
  chat.toolTitle.projectBranchCreator: Creating project branch
586
588
  chat.toolTitle.projectPullRequestCreator: Creating pull request
589
+ chat.toolTitle.agentProjectTouched: Project
587
590
  chatTimeout.defaultLabel: Scheduled timeout
588
591
  chatTimeout.singleActiveLabel: 1 active timeout
589
592
  chatTimeout.multipleActiveLabel: '{count} active timeouts'
@@ -1,4 +1,5 @@
1
1
  import { spaceTrim } from 'spacetrim';
2
+ import { formatTimeoutDurationHuman } from '../../../../src/book-components/Chat/utils/timeoutToolCallPresentation';
2
3
  import type { string_javascript_name } from '../../../../src/types/string_person_fullname';
3
4
  import type { ToolFunction } from '../../../../src/scripting/javascript/JavascriptExecutionToolsOptions';
4
5
  import { createToolExecutionEnvelope } from '../../../../src/commitments/_common/toolExecutionEnvelope';
@@ -45,7 +46,9 @@ export function createAgentGoalChatTimeoutToolFunctions(
45
46
  });
46
47
 
47
48
  return createToolExecutionEnvelope({
48
- assistantMessage: `Planned message ${JSON.stringify(result.timeoutId)} for ${result.dueAt}.`,
49
+ assistantMessage: `Planned message ${JSON.stringify(
50
+ result.timeoutId,
51
+ )} ${createPlannedMessageScheduleLabel(result)}.`,
49
52
  toolResult: result,
50
53
  });
51
54
  } catch (error) {
@@ -147,7 +150,9 @@ function createPlannedMessagesAssistantSummary(
147
150
  const visiblePlannedMessages = plannedMessages.slice(0, MAX_ASSISTANT_VISIBLE_PLANNED_MESSAGES);
148
151
  const summaryRows = visiblePlannedMessages.map((plannedMessage, index) => {
149
152
  const message = plannedMessage.message?.trim() || 'Wake up and continue working towards the current goal.';
150
- return `${index + 1}. ${plannedMessage.timeoutId} at ${plannedMessage.dueAt}: ${message}`;
153
+ return `${index + 1}. ${plannedMessage.timeoutId} ${createPlannedMessageScheduleLabel(
154
+ plannedMessage,
155
+ )}: ${message}`;
151
156
  });
152
157
  const hiddenPlannedMessageCount = plannedMessages.length - visiblePlannedMessages.length;
153
158
 
@@ -164,6 +169,23 @@ function createPlannedMessagesAssistantSummary(
164
169
  );
165
170
  }
166
171
 
172
+ /**
173
+ * Describes when one planned message wakes the agent.
174
+ *
175
+ * @param plannedMessage - Planned message with its repeat interval and nearest execution time.
176
+ * @returns Schedule fragment such as `repeating every 5 minutes (next at ...)`.
177
+ */
178
+ function createPlannedMessageScheduleLabel(plannedMessage: {
179
+ readonly dueAt: string;
180
+ readonly intervalMs: number | null;
181
+ }): string {
182
+ if (!plannedMessage.intervalMs) {
183
+ return `at ${plannedMessage.dueAt}`;
184
+ }
185
+
186
+ return `repeating every ${formatTimeoutDurationHuman(plannedMessage.intervalMs)} (next at ${plannedMessage.dueAt})`;
187
+ }
188
+
167
189
  /**
168
190
  * Serializes one caught tool failure without crashing the surrounding model run.
169
191
  *
@@ -27,13 +27,14 @@ export function createAgentGoalChatTimeoutTools(
27
27
  tools.push({
28
28
  name: AGENT_GOAL_CHAT_TIMEOUT_TOOL_NAMES.set,
29
29
  description:
30
- 'Plan a message that will wake you in your singleton goal chat after a delay. Use this for useful autonomous follow-up towards your current goal; the wake-up is not tied to the human conversation where you schedule it.',
30
+ 'Plan a message that repeatedly wakes you in your singleton goal chat, like `setInterval`. Use this for useful autonomous follow-up towards your current goal; the wake-up is not tied to the human conversation where you schedule it, and it keeps repeating until you cancel it. Do not plan a message that matches one you already planned.',
31
31
  parameters: {
32
32
  type: 'object',
33
33
  properties: {
34
34
  milliseconds: {
35
35
  type: 'number',
36
- description: 'Positive delay in milliseconds before the planned message wakes you.',
36
+ description:
37
+ 'Repeat interval in milliseconds between two wake-ups by this planned message (at least 60000).',
37
38
  },
38
39
  message: {
39
40
  type: 'string',
@@ -51,7 +52,7 @@ export function createAgentGoalChatTimeoutTools(
51
52
  tools.push({
52
53
  name: AGENT_GOAL_CHAT_TIMEOUT_TOOL_NAMES.list,
53
54
  description:
54
- 'List your planned goal-chat messages, including their identifiers and execution times, across every conversation in which you were invoked.',
55
+ 'List your planned goal-chat messages, including their identifiers, repeat intervals, and next execution times, across every conversation in which you were invoked. Keep the listed messages as they are unless they no longer match your goal.',
55
56
  parameters: {
56
57
  type: 'object',
57
58
  properties: {
@@ -73,7 +74,7 @@ export function createAgentGoalChatTimeoutTools(
73
74
  tools.push({
74
75
  name: AGENT_GOAL_CHAT_TIMEOUT_TOOL_NAMES.cancel,
75
76
  description:
76
- 'Cancel one planned goal-chat message by its timeout identifier. List planned messages first when the identifier is not known.',
77
+ 'Stop one repeating planned goal-chat message by its timeout identifier. List planned messages first when the identifier is not known.',
77
78
  parameters: {
78
79
  type: 'object',
79
80
  properties: {