@promptbook/cli 0.112.0-132 → 0.112.0-133

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 (93) hide show
  1. package/apps/agents-server/src/app/admin/api-tokens/ApiTokensClient.tsx +4 -1
  2. package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +3 -0
  3. package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackTable.tsx +13 -8
  4. package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +3 -0
  5. package/apps/agents-server/src/app/admin/chat-history/ChatHistoryTable.tsx +13 -8
  6. package/apps/agents-server/src/app/admin/custom-css/CustomCssClient.tsx +4 -0
  7. package/apps/agents-server/src/app/admin/custom-css/CustomCssEditorPanel.tsx +7 -1
  8. package/apps/agents-server/src/app/admin/custom-css/CustomCssFilesPanel.tsx +7 -1
  9. package/apps/agents-server/src/app/admin/custom-js/CustomJsClient.tsx +4 -0
  10. package/apps/agents-server/src/app/admin/custom-js/CustomJsEditorPanel.tsx +7 -1
  11. package/apps/agents-server/src/app/admin/custom-js/CustomJsFilesPanel.tsx +7 -1
  12. package/apps/agents-server/src/app/admin/files/FilesGalleryClient.tsx +4 -0
  13. package/apps/agents-server/src/app/admin/files/FilesGalleryGrid.tsx +9 -11
  14. package/apps/agents-server/src/app/admin/files/FilesGalleryTable.tsx +9 -8
  15. package/apps/agents-server/src/app/admin/images/ImagesGalleryClient.tsx +4 -0
  16. package/apps/agents-server/src/app/admin/images/ImagesGalleryGrid.tsx +16 -11
  17. package/apps/agents-server/src/app/admin/images/ImagesGalleryTable.tsx +18 -9
  18. package/apps/agents-server/src/app/admin/login-methods/shibboleth/page.tsx +9 -9
  19. package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +7 -6
  20. package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +11 -10
  21. package/apps/agents-server/src/app/admin/task-manager/TaskManagerClient.tsx +4 -2
  22. package/apps/agents-server/src/app/admin/task-manager/TaskManagerTaskRow.tsx +14 -15
  23. package/apps/agents-server/src/app/admin/task-manager/TaskManagerTasksCard.tsx +4 -1
  24. package/apps/agents-server/src/app/admin/task-manager/useTaskManagerState.ts +6 -9
  25. package/apps/agents-server/src/app/admin/update/CustomCommitPicker.tsx +258 -0
  26. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +305 -108
  27. package/apps/agents-server/src/app/admin/usage/UsageClient.tsx +4 -2
  28. package/apps/agents-server/src/app/admin/usage/UsageClientAnalyticsPanels.tsx +6 -4
  29. package/apps/agents-server/src/app/admin/usage/UsageClientFormatting.ts +6 -12
  30. package/apps/agents-server/src/app/admin/usage/UsageClientTimelineChart.tsx +6 -3
  31. package/apps/agents-server/src/app/admin/usage/useUsageClientState.ts +12 -4
  32. package/apps/agents-server/src/app/admin/users/[userId]/UserDetailClient.tsx +8 -3
  33. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +6 -20
  34. package/apps/agents-server/src/app/agents/[agentName]/book/useBookEditorHistory.ts +9 -2
  35. package/apps/agents-server/src/app/agents/[agentName]/chat/useAgentChatHistoryClientState.ts +2 -15
  36. package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +6 -1
  37. package/apps/agents-server/src/app/agents/[agentName]/integration/CalendarIntegrationSection.tsx +23 -5
  38. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsClient.tsx +4 -2
  39. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsFiltersCard.tsx +9 -4
  40. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsTableCard.tsx +4 -1
  41. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsTableRow.tsx +10 -3
  42. package/apps/agents-server/src/app/api/admin/update/commits/route.ts +35 -0
  43. package/apps/agents-server/src/app/api/admin/update/route.ts +10 -2
  44. package/apps/agents-server/src/app/api/internal/user-chat-jobs/run/route.ts +14 -2
  45. package/apps/agents-server/src/app/dashboard/page.tsx +11 -16
  46. package/apps/agents-server/src/app/swagger/SwaggerApiKeysPanel.tsx +4 -1
  47. package/apps/agents-server/src/app/system/user-memory/UserMemoryClient.tsx +4 -2
  48. package/apps/agents-server/src/app/system/user-wallet/UserWalletClient.tsx +3 -0
  49. package/apps/agents-server/src/app/system/user-wallet/UserWalletRecordsTable.tsx +5 -1
  50. package/apps/agents-server/src/app/system/utilities/mocked-chats/MockedChatsEditorClient.tsx +3 -0
  51. package/apps/agents-server/src/app/system/utilities/mocked-chats/MockedChatsEditorSidebar.tsx +7 -8
  52. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -2
  53. package/apps/agents-server/src/components/UsersList/UsersList.tsx +4 -2
  54. package/apps/agents-server/src/tools/createAgentProgressTools.ts +8 -6
  55. package/apps/agents-server/src/utils/localization/formatServerLanguageHumanReadableDate.ts +70 -0
  56. package/apps/agents-server/src/utils/localization/getRequestServerLanguage.ts +32 -0
  57. package/apps/agents-server/src/utils/userChat/createRunUserChatJobExecutionContext.ts +17 -0
  58. package/apps/agents-server/src/utils/userChat/createRunUserChatJobPersistenceController.ts +0 -21
  59. package/apps/agents-server/src/utils/userChat/persistUserChatJobProgressCard.ts +31 -0
  60. package/apps/agents-server/src/utils/userChat/retryUserChatJob.ts +0 -4
  61. package/apps/agents-server/src/utils/userChat/runUserChatJob.ts +29 -0
  62. package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +2 -2
  63. package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +99 -401
  64. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +625 -51
  65. package/esm/index.es.js +252 -41
  66. package/esm/index.es.js.map +1 -1
  67. package/esm/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +5 -1
  68. package/esm/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +7 -0
  69. package/esm/scripts/run-codex-prompts/common/coderRunEstimateCache.d.ts +31 -0
  70. package/esm/scripts/run-codex-prompts/common/progressFormatting.d.ts +2 -0
  71. package/esm/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +3 -1
  72. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +7 -0
  73. package/esm/src/book-3.0/cliAgentEnv.d.ts +1 -1
  74. package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  75. package/esm/src/version.d.ts +1 -1
  76. package/package.json +1 -1
  77. package/src/book-3.0/cliAgentEnv.ts +1 -1
  78. package/src/cli/cli-commands/agents-server/startAgentsServer.ts +1 -1
  79. package/src/cli/cli-commands/coder/ThinkingLevel.ts +3 -1
  80. package/src/other/templates/getTemplatesPipelineCollection.ts +711 -791
  81. package/src/version.ts +2 -2
  82. package/src/versions.txt +1 -0
  83. package/umd/index.umd.js +254 -44
  84. package/umd/index.umd.js.map +1 -1
  85. package/umd/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +5 -1
  86. package/umd/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +7 -0
  87. package/umd/scripts/run-codex-prompts/common/coderRunEstimateCache.d.ts +31 -0
  88. package/umd/scripts/run-codex-prompts/common/progressFormatting.d.ts +2 -0
  89. package/umd/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +3 -1
  90. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +7 -0
  91. package/umd/src/book-3.0/cliAgentEnv.d.ts +1 -1
  92. package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  93. package/umd/src/version.d.ts +1 -1
@@ -4,7 +4,6 @@
4
4
  export const dynamic = 'force-dynamic';
5
5
 
6
6
  import { getSingleLlmExecutionTools } from '@promptbook-local/core';
7
- import moment from 'moment';
8
7
  import { headers } from 'next/headers';
9
8
  import { AboutPromptbookInformation } from '../../../../../src/utils/misc/xAboutPromptbookInformation';
10
9
  import { HomepagePrimarySections } from '../../components/Homepage/HomepagePrimarySections';
@@ -17,21 +16,10 @@ import { getLongRunningTask } from '../../deamons/longRunningTask';
17
16
  import { $provideExecutionToolsForServer } from '../../tools/$provideExecutionToolsForServer';
18
17
  import { $provideServer } from '../../tools/$provideServer';
19
18
  import { isUserAdmin } from '../../utils/isUserAdmin';
19
+ import { formatServerLanguageHumanReadableDate } from '../../utils/localization/formatServerLanguageHumanReadableDate';
20
+ import { getRequestServerLanguage } from '../../utils/localization/getRequestServerLanguage';
20
21
  import { getHomePageAgents } from '../_data/getHomePageAgents';
21
22
 
22
- // Add calendar formats that include seconds
23
- /**
24
- * Map of calendar with seconds.
25
- */
26
- const calendarWithSeconds = {
27
- sameDay: '[Today at] LTS',
28
- nextDay: '[Tomorrow at] LTS',
29
- nextWeek: 'dddd [at] LTS',
30
- lastDay: '[Yesterday at] LTS',
31
- lastWeek: '[Last] dddd [at] LTS',
32
- sameElse: 'L [at] LTS',
33
- };
34
-
35
23
  /**
36
24
  * Renders the legacy dashboard with system and admin details.
37
25
  */
@@ -40,6 +28,7 @@ export default async function DashboardPage() {
40
28
  const { publicUrl } = server;
41
29
  const isAdmin = await isUserAdmin(); /* <- TODO: [??] Here should be user permissions */
42
30
  const { agents, folders, homepageMessage, currentUser } = await getHomePageAgents();
31
+ const language = await getRequestServerLanguage();
43
32
 
44
33
  const longRunningTask = getLongRunningTask();
45
34
 
@@ -92,10 +81,16 @@ export default async function DashboardPage() {
92
81
  <TechInfoCard title={`Long running task ${longRunningTask.taskId}`}>
93
82
  <p className="text-gray-600">Tick: {longRunningTask.tick}</p>
94
83
  <p className="text-gray-600">
95
- Created At: {moment(longRunningTask.createdAt).calendar(undefined, calendarWithSeconds)}
84
+ Created At:{' '}
85
+ {formatServerLanguageHumanReadableDate(longRunningTask.createdAt, language, {
86
+ isExactDateIncluded: true,
87
+ })}
96
88
  </p>
97
89
  <p className="text-gray-600">
98
- Updated At: {moment(longRunningTask.updatedAt).calendar(undefined, calendarWithSeconds)}
90
+ Updated At:{' '}
91
+ {formatServerLanguageHumanReadableDate(longRunningTask.updatedAt, language, {
92
+ isExactDateIncluded: true,
93
+ })}
99
94
  </p>
100
95
  </TechInfoCard>
101
96
 
@@ -3,9 +3,11 @@
3
3
  import Link from 'next/link';
4
4
  import { Plus } from 'lucide-react';
5
5
  import { useEffect, useState } from 'react';
6
+ import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
6
7
  import { SecretInput } from '@/src/components/SecretInput/SecretInput';
7
8
  import type { ApiTokenEntry } from '@/src/utils/apiTokensClient';
8
9
  import { createApiToken, fetchApiTokens } from '@/src/utils/apiTokensClient';
10
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
9
11
 
10
12
  /**
11
13
  * Props for the compact Swagger API-key panel.
@@ -21,6 +23,7 @@ type SwaggerApiKeysPanelProps = {
21
23
  * Compact API-key panel shown above Swagger UI.
22
24
  */
23
25
  export function SwaggerApiKeysPanel({ isAdmin }: SwaggerApiKeysPanelProps) {
26
+ const { language } = useServerLanguage();
24
27
  const [tokens, setTokens] = useState<ApiTokenEntry[]>([]);
25
28
  const [isLoading, setIsLoading] = useState(isAdmin);
26
29
  const [isCreating, setIsCreating] = useState(false);
@@ -110,7 +113,7 @@ export function SwaggerApiKeysPanel({ isAdmin }: SwaggerApiKeysPanelProps) {
110
113
  {token.note || `API key #${token.id}`}
111
114
  </span>
112
115
  <span className="text-xs text-slate-400">
113
- {new Date(token.createdAt).toLocaleString()}
116
+ {formatServerLanguageHumanReadableDate(token.createdAt, language)}
114
117
  </span>
115
118
  </div>
116
119
  <SecretInput value={token.token} readOnly aria-label={`API key ${token.id}`} />
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { showConfirm } from '@/src/components/AsyncDialogs/asyncDialogs';
4
+ import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
5
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
4
6
  import { useEffect, useMemo, useState } from 'react';
5
7
 
6
8
  /**
@@ -42,6 +44,7 @@ type UserMemoryClientProps = {
42
44
  */
43
45
  export function UserMemoryClient(props: UserMemoryClientProps) {
44
46
  const { agents } = props;
47
+ const { language } = useServerLanguage();
45
48
  const [memories, setMemories] = useState<UserMemoryEntry[]>([]);
46
49
  const [loading, setLoading] = useState<boolean>(true);
47
50
  const [saving, setSaving] = useState<boolean>(false);
@@ -445,7 +448,7 @@ export function UserMemoryClient(props: UserMemoryClientProps) {
445
448
  )}
446
449
  </td>
447
450
  <td className="px-4 py-3 text-xs text-gray-500">
448
- {new Date(memory.updatedAt).toLocaleString()}
451
+ {formatServerLanguageHumanReadableDate(memory.updatedAt, language)}
449
452
  </td>
450
453
  <td className="px-4 py-3 text-right text-sm">
451
454
  {isEditing ? (
@@ -491,4 +494,3 @@ export function UserMemoryClient(props: UserMemoryClientProps) {
491
494
  </div>
492
495
  );
493
496
  }
494
-
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { WalletRecordDialog } from '@/src/components/WalletRecordDialog/WalletRecordDialog';
4
+ import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
4
5
  import type { GithubAppStatusResponse } from '@/src/utils/githubAppClient';
5
6
  import { UserWalletCreateCard } from './UserWalletCreateCard';
6
7
  import type { UserWalletAgentOption } from './UserWalletAgentOption';
@@ -34,6 +35,7 @@ function resolveGithubAppStatusDescription(status: GithubAppStatusResponse | nul
34
35
  */
35
36
  export function UserWalletClient(props: UserWalletClientProps) {
36
37
  const { agents } = props;
38
+ const { language } = useServerLanguage();
37
39
  const state = useUserWalletClientState({ agents });
38
40
 
39
41
  return (
@@ -118,6 +120,7 @@ export function UserWalletClient(props: UserWalletClientProps) {
118
120
 
119
121
  <UserWalletRecordsTable
120
122
  agents={agents}
123
+ language={language}
121
124
  editingDraft={state.editingDraft}
122
125
  isEditingSmtpRecord={state.isEditingSmtpRecord}
123
126
  isLoading={state.loading}
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import type { UserWalletRecord } from '@/src/utils/userWallet';
4
+ import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
5
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
4
6
  import type { UserWalletAgentOption } from './UserWalletAgentOption';
5
7
  import { formatWalletJsonSchemaForTextarea, type EditingUserWalletDraft, type UpdateUserWalletDraft } from './UserWalletDraft';
6
8
  import { UserWalletRecordFormFields } from './UserWalletRecordFormFields';
@@ -11,6 +13,7 @@ import { UserWalletRecordValue } from './UserWalletRecordValue';
11
13
  */
12
14
  type UserWalletRecordsTableProps = {
13
15
  agents: ReadonlyArray<UserWalletAgentOption>;
16
+ language: ServerLanguageCode;
14
17
  editingDraft: EditingUserWalletDraft | null;
15
18
  isEditingSmtpRecord: boolean;
16
19
  isLoading: boolean;
@@ -31,6 +34,7 @@ type UserWalletRecordsTableProps = {
31
34
  */
32
35
  export function UserWalletRecordsTable({
33
36
  agents,
37
+ language,
34
38
  editingDraft,
35
39
  isEditingSmtpRecord,
36
40
  isLoading,
@@ -118,7 +122,7 @@ export function UserWalletRecordsTable({
118
122
  )}
119
123
  </td>
120
124
  <td className="px-4 py-3 text-xs text-gray-500">
121
- {new Date(record.updatedAt).toLocaleString()}
125
+ {formatServerLanguageHumanReadableDate(record.updatedAt, language)}
122
126
  </td>
123
127
  <td className="px-4 py-3 text-right text-sm">
124
128
  {isEditing ? (
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useMemo, useState } from 'react';
4
+ import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
4
5
  import type { MockedChatPreset } from '@/src/utils/mockedChatsSchema';
5
6
  import { MockedChatsEditorForm } from './MockedChatsEditorForm';
6
7
  import { MockedChatsEditorSidebar } from './MockedChatsEditorSidebar';
@@ -17,6 +18,7 @@ type MockedChatsEditorClientProps = {
17
18
  * Mocked chat editor under `System -> Utilities`.
18
19
  */
19
20
  export function MockedChatsEditorClient({ initialMockedChats }: MockedChatsEditorClientProps) {
21
+ const { language } = useServerLanguage();
20
22
  const mockedChatsEditorState = useMockedChatsEditorState({ initialMockedChats });
21
23
  const viewerHref = useMemo(
22
24
  () => buildMockedChatViewerHref(mockedChatsEditorState.draftChat.id),
@@ -47,6 +49,7 @@ export function MockedChatsEditorClient({ initialMockedChats }: MockedChatsEdito
47
49
 
48
50
  <div className="grid gap-6 lg:grid-cols-[340px_minmax(0,1fr)]">
49
51
  <MockedChatsEditorSidebar
52
+ language={language}
50
53
  savedChats={mockedChatsEditorState.savedChats}
51
54
  selectedChatId={mockedChatsEditorState.selectedChatId}
52
55
  createNewDraft={mockedChatsEditorState.createNewDraft}
@@ -1,4 +1,6 @@
1
1
  import Link from 'next/link';
2
+ import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
3
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
2
4
  import type { UseMockedChatsEditorState } from './useMockedChatsEditorState';
3
5
 
4
6
  /**
@@ -8,6 +10,7 @@ type MockedChatsEditorSidebarProps = Pick<
8
10
  UseMockedChatsEditorState,
9
11
  'savedChats' | 'selectedChatId' | 'createNewDraft' | 'selectSavedChat'
10
12
  > & {
13
+ language: ServerLanguageCode;
11
14
  buildViewerHref: (mockedChatId: string) => string;
12
15
  };
13
16
 
@@ -16,13 +19,8 @@ type MockedChatsEditorSidebarProps = Pick<
16
19
  *
17
20
  * @private function of <MockedChatsEditorClient/>
18
21
  */
19
- function formatMockedChatUpdatedAt(updatedAt: string): string {
20
- const date = new Date(updatedAt);
21
- if (Number.isNaN(date.getTime())) {
22
- return 'just now';
23
- }
24
-
25
- return date.toLocaleString();
22
+ function formatMockedChatUpdatedAt(updatedAt: string, language: ServerLanguageCode): string {
23
+ return formatServerLanguageHumanReadableDate(updatedAt, language, { fallbackLabel: 'just now' });
26
24
  }
27
25
 
28
26
  /**
@@ -31,6 +29,7 @@ function formatMockedChatUpdatedAt(updatedAt: string): string {
31
29
  * @private function of <MockedChatsEditorClient/>
32
30
  */
33
31
  export function MockedChatsEditorSidebar({
32
+ language,
34
33
  savedChats,
35
34
  selectedChatId,
36
35
  createNewDraft,
@@ -74,7 +73,7 @@ export function MockedChatsEditorSidebar({
74
73
  <div className="min-w-0">
75
74
  <p className="truncate text-sm font-semibold text-slate-900">{chat.name}</p>
76
75
  <p className="mt-1 text-xs text-slate-500">
77
- Updated {formatMockedChatUpdatedAt(chat.updatedAt)}
76
+ Updated {formatMockedChatUpdatedAt(chat.updatedAt, language)}
78
77
  </p>
79
78
  </div>
80
79
  <Link
@@ -1,5 +1,7 @@
1
1
  import { formatAgentNamingText } from '../../utils/agentNaming';
2
2
  import { getAgentNaming } from '../../utils/getAgentNaming';
3
+ import { formatServerLanguageHumanReadableDate } from '../../utils/localization/formatServerLanguageHumanReadableDate';
4
+ import { getRequestServerLanguage } from '../../utils/localization/getRequestServerLanguage';
3
5
 
4
6
  /**
5
7
  * Renders a print-only header with server branding.
@@ -8,7 +10,7 @@ import { getAgentNaming } from '../../utils/getAgentNaming';
8
10
  * @returns Print header markup.
9
11
  */
10
12
  export async function PrintHeader({ title }: { title?: string }) {
11
- const agentNaming = await getAgentNaming();
13
+ const [agentNaming, language] = await Promise.all([getAgentNaming(), getRequestServerLanguage()]);
12
14
  return (
13
15
  <div className="hidden print:block mb-6 border-b-2 border-blue-600 pb-2">
14
16
  <div className="flex justify-between items-end">
@@ -23,7 +25,7 @@ export async function PrintHeader({ title }: { title?: string }) {
23
25
  {title && <h2 className="text-lg font-semibold text-gray-700">{title}</h2>}
24
26
  </div>
25
27
  <div className="text-xs text-gray-400 mt-1 text-right">
26
- {new Date().toLocaleDateString()}
28
+ {formatServerLanguageHumanReadableDate(new Date(), language, { isExactDateIncluded: true })}
27
29
  </div>
28
30
  </div>
29
31
  );
@@ -6,6 +6,7 @@ import { Card } from '../Homepage/Card';
6
6
  import { Section } from '../Homepage/Section';
7
7
  import { useServerLanguage } from '../ServerLanguage/ServerLanguageProvider';
8
8
  import { SecretInput } from '@/src/components/SecretInput/SecretInput';
9
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
9
10
  import { useUsersAdmin } from './useUsersAdmin';
10
11
 
11
12
  /**
@@ -26,7 +27,7 @@ type UsersListProps = {
26
27
  */
27
28
  export function UsersList({ allowCreate = true }: UsersListProps) {
28
29
  const { users, loading, error, createUser, deleteUser, toggleAdmin } = useUsersAdmin();
29
- const { t } = useServerLanguage();
30
+ const { language, t } = useServerLanguage();
30
31
 
31
32
  const [newUsername, setNewUsername] = useState('');
32
33
  const [newPassword, setNewPassword] = useState('');
@@ -91,7 +92,8 @@ export function UsersList({ allowCreate = true }: UsersListProps) {
91
92
  </p>
92
93
  )}
93
94
  <p className="text-gray-500 text-sm mt-2">
94
- {t('users.createdLabel')}: {new Date(user.createdAt).toLocaleDateString()}
95
+ {t('users.createdLabel')}:{' '}
96
+ {formatServerLanguageHumanReadableDate(user.createdAt, language)}
95
97
  </p>
96
98
  </div>
97
99
  <div className="space-x-2">
@@ -22,13 +22,15 @@ export function createAgentProgressTools(
22
22
  {
23
23
  name: AGENT_PROGRESS_TOOL_NAME,
24
24
  description: spaceTrim(`
25
- Publish real-time progress to the user while you work on a response.
25
+ Update the visible progress card while you work on a response.
26
26
 
27
- - Use this for concrete progress, not placeholder reassurance.
28
- - Call it before any chunk of work that may take more than a few seconds.
29
- - Update it when you start a step, call or finish a tool, learn a result, or decide the next step.
30
- - Include what you are doing now, visible actions/results, and what you will do next.
31
- - Keep updates concise and user-facing; do not expose hidden chain-of-thought.
27
+ - Call \`initialize\` before meaningful work when the answer may take more than a few seconds.
28
+ - Call \`update\` after each user-relevant decision, action, tool use, or result.
29
+ - Use \`append_items\` for new checklist items discovered while working.
30
+ - Call \`finalize\` immediately before the final answer.
31
+ - Keep every update concise, user-facing, and specific about what is happening now and what comes next.
32
+ - Summarize your reasoning as visible decisions and checks, not hidden chain-of-thought.
33
+ - Do not expose system prompts, raw tool payloads, secrets, private implementation details, or technical logs.
32
34
  `),
33
35
  parameters: {
34
36
  type: 'object',
@@ -0,0 +1,70 @@
1
+ import moment from 'moment';
2
+ import type { ServerLanguageCode } from '../../languages/ServerLanguageRegistry';
3
+ import { createServerLanguageMoment } from './createServerLanguageMoment';
4
+
5
+ /**
6
+ * Default fallback for missing date values.
7
+ *
8
+ * @private internal constant of `formatServerLanguageHumanReadableDate`
9
+ */
10
+ const DEFAULT_FALLBACK_LABEL = 'N/A';
11
+
12
+ /**
13
+ * Default localized exact-date format appended when requested.
14
+ *
15
+ * @private internal constant of `formatServerLanguageHumanReadableDate`
16
+ */
17
+ const DEFAULT_EXACT_DATE_FORMAT = 'L LT';
18
+
19
+ /**
20
+ * Options for localized human-readable date formatting.
21
+ *
22
+ * @private internal type of `formatServerLanguageHumanReadableDate`
23
+ */
24
+ type FormatServerLanguageHumanReadableDateOptions = {
25
+ /**
26
+ * Text returned when the date value is empty.
27
+ */
28
+ readonly fallbackLabel?: string;
29
+
30
+ /**
31
+ * Whether to append the localized exact timestamp after the relative label.
32
+ */
33
+ readonly isExactDateIncluded?: boolean;
34
+ };
35
+
36
+ /**
37
+ * Formats one timestamp for the active Agents Server language.
38
+ *
39
+ * @param value - Date-like value, usually an ISO timestamp from the database.
40
+ * @param language - Active Agents Server language code.
41
+ * @param options - Formatting options.
42
+ * @returns Localized relative date label, with optional exact timestamp.
43
+ * @private internal utility for Agents Server UI.
44
+ */
45
+ export function formatServerLanguageHumanReadableDate(
46
+ value: moment.MomentInput | null | undefined,
47
+ language: ServerLanguageCode,
48
+ options: FormatServerLanguageHumanReadableDateOptions = {},
49
+ ): string {
50
+ const fallbackLabel = options.fallbackLabel ?? DEFAULT_FALLBACK_LABEL;
51
+
52
+ if (value === null || value === undefined || value === '') {
53
+ return fallbackLabel;
54
+ }
55
+
56
+ const localizedMoment =
57
+ typeof value === 'string'
58
+ ? moment(value, moment.ISO_8601, true).locale(language)
59
+ : createServerLanguageMoment(value, language);
60
+ if (!localizedMoment.isValid()) {
61
+ return typeof value === 'string' ? value : fallbackLabel;
62
+ }
63
+
64
+ const relativeLabel = localizedMoment.fromNow();
65
+ if (!options.isExactDateIncluded) {
66
+ return relativeLabel;
67
+ }
68
+
69
+ return `${relativeLabel} (${localizedMoment.format(DEFAULT_EXACT_DATE_FORMAT)})`;
70
+ }
@@ -0,0 +1,32 @@
1
+ import { cookies } from 'next/headers';
2
+ import { getMetadataMap } from '../../database/getMetadata';
3
+ import {
4
+ IS_SERVER_LANGUAGE_ENFORCED_METADATA_KEY,
5
+ parseServerLanguageEnforcedMetadata,
6
+ resolveServerLanguageCode,
7
+ SERVER_LANGUAGE_COOKIE_NAME,
8
+ SERVER_LANGUAGE_METADATA_KEY,
9
+ type ServerLanguageCode,
10
+ } from '../../languages/ServerLanguageRegistry';
11
+
12
+ /**
13
+ * Resolves the active Agents Server UI language for the current server-rendered request.
14
+ *
15
+ * @returns Active server language code derived from metadata and the language override cookie.
16
+ * @private internal utility for server-rendered Agents Server UI.
17
+ */
18
+ export async function getRequestServerLanguage(): Promise<ServerLanguageCode> {
19
+ const [metadata, cookieStore] = await Promise.all([
20
+ getMetadataMap([SERVER_LANGUAGE_METADATA_KEY, IS_SERVER_LANGUAGE_ENFORCED_METADATA_KEY]),
21
+ cookies(),
22
+ ]);
23
+
24
+ const cookieLanguage = cookieStore.get(SERVER_LANGUAGE_COOKIE_NAME)?.value || null;
25
+ const rawServerLanguage = metadata[SERVER_LANGUAGE_METADATA_KEY];
26
+ const isServerLanguageEnforced = parseServerLanguageEnforcedMetadata(
27
+ metadata[IS_SERVER_LANGUAGE_ENFORCED_METADATA_KEY],
28
+ );
29
+ const preferredLanguageSource = isServerLanguageEnforced ? rawServerLanguage : cookieLanguage || rawServerLanguage;
30
+
31
+ return resolveServerLanguageCode(preferredLanguageSource);
32
+ }
@@ -27,6 +27,7 @@ import type { ChatMessage, ChatPrompt, LlmToolDefinition, ToolCall } from '@prom
27
27
  import { resolveTeamInternalAgentAccessToken } from '../../../../../src/commitments/_common/teamInternalAgentAccess';
28
28
  import type { ChatPromptResult } from '../../../../../src/execution/PromptResult';
29
29
  import { createUserChatMessagePrompt } from './createUserChatMessagePrompt';
30
+ import type { UserChatProgressPhase } from './userChatProgressCard';
30
31
  import type { UserChatJobRecord } from './UserChatJobRecord';
31
32
 
32
33
  /**
@@ -53,6 +54,11 @@ type RunUserChatJobPromptSnapshotFactory = (
53
54
  },
54
55
  ) => NonNullable<ChatMessage['prompt']>;
55
56
 
57
+ /**
58
+ * Reports one user-facing durable chat progress phase.
59
+ */
60
+ type RunUserChatJobProgressReporter = (phase: UserChatProgressPhase) => Promise<void>;
61
+
56
62
  /**
57
63
  * Prepares the full agent/runtime context required to execute one durable user chat job.
58
64
  *
@@ -67,12 +73,18 @@ export async function createRunUserChatJobExecutionContext(options: {
67
73
  userMessageAttachments: ChatMessage['attachments'];
68
74
  thread: NonNullable<ChatPrompt['thread']>;
69
75
  promptContent: ChatPrompt['content'];
76
+ reportProgress?: RunUserChatJobProgressReporter;
70
77
  }) {
78
+ await options.reportProgress?.('reading_context');
79
+
71
80
  const [localServerUrl, collection, baseAgentReferenceResolver] = await Promise.all([
72
81
  resolveCurrentOrInternalServerOrigin(),
73
82
  $provideAgentCollectionForServer(),
74
83
  $provideAgentReferenceResolver(),
75
84
  ]);
85
+
86
+ await options.reportProgress?.('preparing_agent');
87
+
76
88
  const resolvedAgentContext = await resolveCachedServerAgentContext({
77
89
  collection,
78
90
  agentIdentifier: options.job.agentPermanentId,
@@ -91,6 +103,9 @@ export async function createRunUserChatJobExecutionContext(options: {
91
103
  const projectRepositories = extractProjectRepositoriesFromAgentSource(agentSource);
92
104
  const calendarConnections = extractUseCalendarConnectionsFromAgentSource(agentSource);
93
105
  const useEmailConfiguration = extractUseEmailConfigurationFromAgentSource(agentSource);
106
+
107
+ await options.reportProgress?.('checking_capabilities');
108
+
94
109
  const { projectGithubToken, calendarGoogleAccessToken, emailSmtpCredential } =
95
110
  await resolveRunUserChatJobCredentials({
96
111
  userId: options.job.userId,
@@ -164,6 +179,8 @@ export async function createRunUserChatJobExecutionContext(options: {
164
179
  });
165
180
  }
166
181
 
182
+ await options.reportProgress?.('starting_response');
183
+
167
184
  const agentKitResult = await agentKitCacheManager.getOrCreateAgentKitAgent(
168
185
  agentSource,
169
186
  resolvedAgentName,
@@ -4,7 +4,6 @@ import type { ChatPromptResult } from '../../../../../src/execution/PromptResult
4
4
  import { mergeToolCalls } from '../../../../../src/utils/toolCalls/mergeToolCalls';
5
5
  import { persistUserChatJobTerminalState } from './persistUserChatJobTerminalState';
6
6
  import { updateUserChatAssistantMessage } from './updateUserChatAssistantMessage';
7
- import { createRunningUserChatProgressCard } from './userChatProgressCard';
8
7
  import { USER_CHAT_JOB_ASSISTANT_MESSAGE_PERSIST_INTERVAL_MS } from './userChatJobRuntimeConstants';
9
8
  import type { UserChatJobRecord } from './UserChatJobRecord';
10
9
 
@@ -45,14 +44,6 @@ export function createRunUserChatJobPersistenceController(options: {
45
44
  let hasPendingAssistantMessageUpdate = false;
46
45
  let assistantMessageUpdateTimeout: ReturnType<typeof setTimeout> | null = null;
47
46
 
48
- /**
49
- * Creates a typed ISO timestamp for progress-card updates.
50
- *
51
- * @private function of `createRunUserChatJobPersistenceController`
52
- */
53
- const createProgressUpdatedAt = (): NonNullable<ChatMessage['progressCard']>['updatedAt'] =>
54
- new Date().toISOString() as NonNullable<ChatMessage['progressCard']>['updatedAt'];
55
-
56
47
  /**
57
48
  * Queues one assistant-message mutation behind any earlier persistence work.
58
49
  *
@@ -141,12 +132,6 @@ export function createRunUserChatJobPersistenceController(options: {
141
132
  lifecycleState: 'running',
142
133
  lifecycleError: undefined,
143
134
  isComplete: false,
144
- progressCard: createRunningUserChatProgressCard({
145
- currentProgressCard: message.progressCard,
146
- content: latestContent,
147
- toolCalls: latestToolCalls,
148
- updatedAt: createProgressUpdatedAt(),
149
- }),
150
135
  prompt: options.createPromptSnapshot({
151
136
  toolCalls: latestToolCalls,
152
137
  }),
@@ -201,12 +186,6 @@ export function createRunUserChatJobPersistenceController(options: {
201
186
  lifecycleState: 'running',
202
187
  lifecycleError: undefined,
203
188
  isComplete: false,
204
- progressCard: createRunningUserChatProgressCard({
205
- currentProgressCard: message.progressCard,
206
- content: latestContent,
207
- toolCalls: latestToolCalls,
208
- updatedAt: createProgressUpdatedAt(),
209
- }),
210
189
  availableTools: options.availableTools,
211
190
  prompt: options.createPromptSnapshot(),
212
191
  }),
@@ -0,0 +1,31 @@
1
+ import { createUserChatProgressCard, type UserChatProgressPhase } from './userChatProgressCard';
2
+ import { updateUserChatAssistantMessage } from './updateUserChatAssistantMessage';
3
+
4
+ /**
5
+ * Persists one durable chat progress phase onto the active assistant placeholder.
6
+ */
7
+ export async function persistUserChatJobProgressCard(options: {
8
+ readonly userId: number;
9
+ readonly agentPermanentId: string;
10
+ readonly chatId: string;
11
+ readonly assistantMessageId: string;
12
+ readonly phase: UserChatProgressPhase;
13
+ }): Promise<void> {
14
+ await updateUserChatAssistantMessage({
15
+ userId: options.userId,
16
+ agentPermanentId: options.agentPermanentId,
17
+ chatId: options.chatId,
18
+ assistantMessageId: options.assistantMessageId,
19
+ mutateMessage: (message) => {
20
+ const lifecycleState = options.phase === 'queued' ? 'queued' : 'running';
21
+
22
+ return {
23
+ ...message,
24
+ lifecycleState,
25
+ lifecycleError: undefined,
26
+ isComplete: false,
27
+ progressCard: createUserChatProgressCard(options.phase),
28
+ };
29
+ },
30
+ });
31
+ }
@@ -1,13 +1,11 @@
1
1
  import type { UserChatJobRecord } from './UserChatJobRecord';
2
2
  import type { UserChatJobRow } from './UserChatJobRow';
3
3
  import type { Json } from '@/src/database/schema';
4
- import type { ChatMessage } from '@promptbook-local/types';
5
4
  import { withoutLocalUserChatJobMetadata } from '../localChatRunner/LocalUserChatJobMetadata';
6
5
  import { getUserChatJobById } from './getUserChatJobById';
7
6
  import { mapUserChatJobRow } from './mapUserChatJobRow';
8
7
  import { provideUserChatJobTable } from './provideUserChatJobTable';
9
8
  import { updateUserChatAssistantMessage } from './updateUserChatAssistantMessage';
10
- import { createQueuedUserChatProgressCard } from './userChatProgressCard';
11
9
 
12
10
  /**
13
11
  * Requeues one failed durable chat job and resets its assistant placeholder.
@@ -25,7 +23,6 @@ export async function retryUserChatJob(jobId: string): Promise<UserChatJobRecord
25
23
  }
26
24
 
27
25
  const nowIso = new Date().toISOString();
28
- const progressUpdatedAt = nowIso as NonNullable<ChatMessage['progressCard']>['updatedAt'];
29
26
  const userChatJobTable = await provideUserChatJobTable();
30
27
  const { data, error } = await userChatJobTable
31
28
  .update({
@@ -70,7 +67,6 @@ export async function retryUserChatJob(jobId: string): Promise<UserChatJobRecord
70
67
  completedToolCalls: undefined,
71
68
  prompt: undefined,
72
69
  generationDurationMs: undefined,
73
- progressCard: createQueuedUserChatProgressCard(progressUpdatedAt),
74
70
  }),
75
71
  });
76
72
 
@@ -20,7 +20,9 @@ import { finalizeUserChatJob } from './finalizeUserChatJob';
20
20
  import { getUserChat } from './getUserChat';
21
21
  import { heartbeatUserChatJob } from './heartbeatUserChatJob';
22
22
  import { persistUserChatJobTerminalState } from './persistUserChatJobTerminalState';
23
+ import { persistUserChatJobProgressCard } from './persistUserChatJobProgressCard';
23
24
  import type { UserChatJobRecord } from './UserChatJobRecord';
25
+ import type { UserChatProgressPhase } from './userChatProgressCard';
24
26
  import { createReplyAwareUserChatPromptContent, createReplyAwareUserChatPromptMessage } from './userChatReplies';
25
27
  import { resolvePromptThreadBeforeUserMessage } from './userChatMessageLifecycle';
26
28
  import { isUserChatNotFoundScopeError } from './UserChatScopeError';
@@ -107,6 +109,7 @@ export async function runUserChatJob(job: UserChatJobRecord): Promise<RunUserCha
107
109
  const executionContext = await createRunUserChatJobExecutionContext({
108
110
  job,
109
111
  ...startContext,
112
+ reportProgress: createRunUserChatJobProgressReporter(job),
110
113
  });
111
114
 
112
115
  return await executeRunUserChatJob({
@@ -122,6 +125,32 @@ export async function runUserChatJob(job: UserChatJobRecord): Promise<RunUserCha
122
125
  }
123
126
  }
124
127
 
128
+ /**
129
+ * Creates a non-critical progress reporter for one durable chat job.
130
+ *
131
+ * @private function of `runUserChatJob`
132
+ */
133
+ function createRunUserChatJobProgressReporter(
134
+ job: Pick<UserChatJobRecord, 'userId' | 'agentPermanentId' | 'chatId' | 'assistantMessageId' | 'id'>,
135
+ ): (phase: UserChatProgressPhase) => Promise<void> {
136
+ return async (phase) => {
137
+ await persistUserChatJobProgressCard({
138
+ userId: job.userId,
139
+ agentPermanentId: job.agentPermanentId,
140
+ chatId: job.chatId,
141
+ assistantMessageId: job.assistantMessageId,
142
+ phase,
143
+ }).catch((error) => {
144
+ console.warn('[user-chat-job] progress_update_failed', {
145
+ chatId: job.chatId,
146
+ jobId: job.id,
147
+ phase,
148
+ error,
149
+ });
150
+ });
151
+ };
152
+ }
153
+
125
154
  /**
126
155
  * Loads and validates the chat/user/message snapshot required before one job can start.
127
156
  *