@promptbook/cli 0.112.0-132 → 0.112.0-134

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 (101) 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]/api/book/route.ts +2 -0
  35. package/apps/agents-server/src/app/agents/[agentName]/book/useBookEditorHistory.ts +9 -2
  36. package/apps/agents-server/src/app/agents/[agentName]/chat/useAgentChatHistoryClientState.ts +2 -15
  37. package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +6 -1
  38. package/apps/agents-server/src/app/agents/[agentName]/integration/CalendarIntegrationSection.tsx +23 -5
  39. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsClient.tsx +4 -2
  40. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsFiltersCard.tsx +9 -4
  41. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsTableCard.tsx +4 -1
  42. package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsTableRow.tsx +10 -3
  43. package/apps/agents-server/src/app/api/admin/update/commits/route.ts +35 -0
  44. package/apps/agents-server/src/app/api/admin/update/route.ts +10 -2
  45. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +5 -0
  46. package/apps/agents-server/src/app/api/internal/user-chat-jobs/run/route.ts +14 -2
  47. package/apps/agents-server/src/app/api/v1/agents/[agentId]/route.ts +4 -0
  48. package/apps/agents-server/src/app/api/v1/agents/route.ts +2 -0
  49. package/apps/agents-server/src/app/dashboard/page.tsx +11 -16
  50. package/apps/agents-server/src/app/recycle-bin/actions.ts +3 -0
  51. package/apps/agents-server/src/app/swagger/SwaggerApiKeysPanel.tsx +4 -1
  52. package/apps/agents-server/src/app/system/user-memory/UserMemoryClient.tsx +4 -2
  53. package/apps/agents-server/src/app/system/user-wallet/UserWalletClient.tsx +3 -0
  54. package/apps/agents-server/src/app/system/user-wallet/UserWalletRecordsTable.tsx +5 -1
  55. package/apps/agents-server/src/app/system/utilities/mocked-chats/MockedChatsEditorClient.tsx +3 -0
  56. package/apps/agents-server/src/app/system/utilities/mocked-chats/MockedChatsEditorSidebar.tsx +7 -8
  57. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -2
  58. package/apps/agents-server/src/components/UsersList/UsersList.tsx +4 -2
  59. package/apps/agents-server/src/database/migrations/2026-06-2600-agent-directory-performance-indexes.sql +14 -0
  60. package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +92 -1
  61. package/apps/agents-server/src/tools/createAgentProgressTools.ts +8 -6
  62. package/apps/agents-server/src/utils/localization/formatServerLanguageHumanReadableDate.ts +70 -0
  63. package/apps/agents-server/src/utils/localization/getRequestServerLanguage.ts +32 -0
  64. package/apps/agents-server/src/utils/userChat/createRunUserChatJobExecutionContext.ts +17 -0
  65. package/apps/agents-server/src/utils/userChat/createRunUserChatJobPersistenceController.ts +0 -21
  66. package/apps/agents-server/src/utils/userChat/listUserChats.ts +5 -1
  67. package/apps/agents-server/src/utils/userChat/persistUserChatJobProgressCard.ts +31 -0
  68. package/apps/agents-server/src/utils/userChat/retryUserChatJob.ts +0 -4
  69. package/apps/agents-server/src/utils/userChat/runUserChatJob.ts +29 -0
  70. package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +2 -2
  71. package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +99 -401
  72. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +625 -51
  73. package/esm/index.es.js +252 -41
  74. package/esm/index.es.js.map +1 -1
  75. package/esm/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +5 -1
  76. package/esm/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +7 -0
  77. package/esm/scripts/run-codex-prompts/common/coderRunEstimateCache.d.ts +31 -0
  78. package/esm/scripts/run-codex-prompts/common/progressFormatting.d.ts +2 -0
  79. package/esm/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +3 -1
  80. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +7 -0
  81. package/esm/src/book-3.0/cliAgentEnv.d.ts +1 -1
  82. package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  83. package/esm/src/version.d.ts +1 -1
  84. package/package.json +1 -1
  85. package/src/book-3.0/cliAgentEnv.ts +1 -1
  86. package/src/cli/cli-commands/agents-server/startAgentsServer.ts +1 -1
  87. package/src/cli/cli-commands/coder/ThinkingLevel.ts +3 -1
  88. package/src/other/templates/getTemplatesPipelineCollection.ts +911 -741
  89. package/src/version.ts +2 -2
  90. package/src/versions.txt +2 -0
  91. package/umd/index.umd.js +254 -44
  92. package/umd/index.umd.js.map +1 -1
  93. package/umd/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +5 -1
  94. package/umd/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +7 -0
  95. package/umd/scripts/run-codex-prompts/common/coderRunEstimateCache.d.ts +31 -0
  96. package/umd/scripts/run-codex-prompts/common/progressFormatting.d.ts +2 -0
  97. package/umd/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +3 -1
  98. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +7 -0
  99. package/umd/src/book-3.0/cliAgentEnv.d.ts +1 -1
  100. package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
  101. package/umd/src/version.d.ts +1 -1
@@ -4,6 +4,8 @@ import type {
4
4
  UsageCallType,
5
5
  UsageMetricMode,
6
6
  } from '@/src/utils/usageAdmin';
7
+ import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
8
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
7
9
 
8
10
  /**
9
11
  * Source shape with metric values used across usage widgets.
@@ -90,20 +92,12 @@ export const UsageClientFormatting = {
90
92
  return usageCompactFormatter.format(value);
91
93
  },
92
94
 
93
- formatDateTime(iso: string): string {
94
- const date = new Date(iso);
95
- if (Number.isNaN(date.getTime())) {
96
- return iso;
97
- }
98
- return date.toLocaleString();
95
+ formatDateTime(iso: string, language: ServerLanguageCode): string {
96
+ return formatServerLanguageHumanReadableDate(iso, language);
99
97
  },
100
98
 
101
- formatShortDate(iso: string): string {
102
- const date = new Date(iso);
103
- if (Number.isNaN(date.getTime())) {
104
- return iso;
105
- }
106
- return date.toLocaleDateString();
99
+ formatShortDate(iso: string, language: ServerLanguageCode): string {
100
+ return formatServerLanguageHumanReadableDate(iso, language);
107
101
  },
108
102
 
109
103
  isIsoDateInputValue(value: string): boolean {
@@ -1,3 +1,4 @@
1
+ import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
1
2
  import type { UsageAnalyticsResponse, UsageMetricMode } from '@/src/utils/usageAdmin';
2
3
  import { useMemo } from 'react';
3
4
  import { UsageClientFormatting } from './UsageClientFormatting';
@@ -24,6 +25,7 @@ type UsageClientTimelinePoint = {
24
25
  * Props for `<UsageClientTimelineChart/>`.
25
26
  */
26
27
  type UsageClientTimelineChartProps = {
28
+ language: ServerLanguageCode;
27
29
  points: UsageAnalyticsResponse['timeline'];
28
30
  metric: UsageMetricMode;
29
31
  };
@@ -34,7 +36,7 @@ type UsageClientTimelineChartProps = {
34
36
  * @private function of UsageClient
35
37
  */
36
38
  export function UsageClientTimelineChart(props: UsageClientTimelineChartProps) {
37
- const { points, metric } = props;
39
+ const { language, points, metric } = props;
38
40
 
39
41
  const chartGeometry = useMemo(() => {
40
42
  const paddedWidth = TIMELINE_WIDTH;
@@ -73,11 +75,12 @@ export function UsageClientTimelineChart(props: UsageClientTimelineChartProps) {
73
75
 
74
76
  const linePath = toLinePath(chartGeometry.coordinates);
75
77
  const areaPath = toAreaPath(chartGeometry.coordinates, chartGeometry.height - chartGeometry.paddingY);
76
- const firstLabel = UsageClientFormatting.formatShortDate(points[0].bucketStart);
78
+ const firstLabel = UsageClientFormatting.formatShortDate(points[0].bucketStart, language);
77
79
  const middleLabel = UsageClientFormatting.formatShortDate(
78
80
  points[Math.floor(points.length / 2)]?.bucketStart || points[0].bucketStart,
81
+ language,
79
82
  );
80
- const lastLabel = UsageClientFormatting.formatShortDate(points[points.length - 1].bucketStart);
83
+ const lastLabel = UsageClientFormatting.formatShortDate(points[points.length - 1].bucketStart, language);
81
84
 
82
85
  return (
83
86
  <div>
@@ -8,6 +8,7 @@ import {
8
8
  type UsageMetricMode,
9
9
  type UsageTimeframePreset,
10
10
  } from '@/src/utils/usageAdmin';
11
+ import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
11
12
  import { usePathname, useRouter } from 'next/navigation';
12
13
  import { useCallback, useEffect, useMemo, useState } from 'react';
13
14
  import { UsageClientFormatting } from './UsageClientFormatting';
@@ -142,13 +143,17 @@ function resolveUsageClientSelectedScopeLabel(
142
143
  /**
143
144
  * Resolves the human-readable timeframe range chip label.
144
145
  */
145
- function resolveUsageClientTimeframeRangeLabel(data: UsageAnalyticsResponse | null): string | null {
146
+ function resolveUsageClientTimeframeRangeLabel(
147
+ data: UsageAnalyticsResponse | null,
148
+ language: ServerLanguageCode,
149
+ ): string | null {
146
150
  if (!data) {
147
151
  return null;
148
152
  }
149
153
 
150
- return `${UsageClientFormatting.formatDateTime(data.timeframe.from)} - ${UsageClientFormatting.formatDateTime(
154
+ return `${UsageClientFormatting.formatDateTime(data.timeframe.from, language)} - ${UsageClientFormatting.formatDateTime(
151
155
  data.timeframe.to,
156
+ language,
152
157
  )}`;
153
158
  }
154
159
 
@@ -164,7 +169,10 @@ function resolveUsageClientAnalyticsErrorMessage(fetchError: unknown): string {
164
169
  *
165
170
  * @private function of <UsageClient/>
166
171
  */
167
- export function useUsageClientState(props: UseUsageClientStateProps): UseUsageClientStateResult {
172
+ export function useUsageClientState(
173
+ props: UseUsageClientStateProps,
174
+ language: ServerLanguageCode,
175
+ ): UseUsageClientStateResult {
168
176
  const { agents, folders } = props;
169
177
  const router = useRouter();
170
178
  const pathname = usePathname();
@@ -271,7 +279,7 @@ export function useUsageClientState(props: UseUsageClientStateProps): UseUsageCl
271
279
  [agentName, agents, folderId, folders],
272
280
  );
273
281
 
274
- const timeframeRangeLabel = useMemo(() => resolveUsageClientTimeframeRangeLabel(data), [data]);
282
+ const timeframeRangeLabel = useMemo(() => resolveUsageClientTimeframeRangeLabel(data, language), [data, language]);
275
283
 
276
284
  const handleAgentNameChange = useCallback((value: string) => {
277
285
  setAgentName(value);
@@ -7,6 +7,7 @@ import { Card } from '../../../../components/Homepage/Card';
7
7
  import { Section } from '../../../../components/Homepage/Section';
8
8
  import { useServerLanguage } from '../../../../components/ServerLanguage/ServerLanguageProvider';
9
9
  import { useUsersAdmin } from '../../../../components/UsersList/useUsersAdmin';
10
+ import { formatServerLanguageHumanReadableDate } from '../../../../utils/localization/formatServerLanguageHumanReadableDate';
10
11
 
11
12
  /**
12
13
  * Props for user detail client.
@@ -26,7 +27,7 @@ type UserDetailClientProps = {
26
27
  */
27
28
  export function UserDetailClient({ userId }: UserDetailClientProps) {
28
29
  const router = useRouter();
29
- const { t } = useServerLanguage();
30
+ const { language, t } = useServerLanguage();
30
31
  const { users, loading, error, deleteUser, toggleAdmin } = useUsersAdmin();
31
32
 
32
33
  const user = useMemo(
@@ -108,11 +109,15 @@ export function UserDetailClient({ userId }: UserDetailClientProps) {
108
109
  )}
109
110
  <p className="text-gray-500 text-sm mt-1">
110
111
  {t('users.createdAtLabel')}:{' '}
111
- {user.createdAt ? new Date(user.createdAt).toLocaleString() : t('users.unknownValue')}
112
+ {formatServerLanguageHumanReadableDate(user.createdAt, language, {
113
+ fallbackLabel: t('users.unknownValue'),
114
+ })}
112
115
  </p>
113
116
  <p className="text-gray-500 text-sm mt-1">
114
117
  {t('users.lastUpdatedLabel')}:{' '}
115
- {user.updatedAt ? new Date(user.updatedAt).toLocaleString() : t('users.unknownValue')}
118
+ {formatServerLanguageHumanReadableDate(user.updatedAt, language, {
119
+ fallbackLabel: t('users.unknownValue'),
120
+ })}
116
121
  </p>
117
122
  </div>
118
123
  <div className="space-x-2">
@@ -28,7 +28,7 @@ import type { ServerLanguageCode } from '../../../languages/ServerLanguageRegist
28
28
  import { buildFreshAgentChatHref } from '../../../utils/agentRouting/agentRouteHrefs';
29
29
  import { executeQuickActionButton } from '../../../utils/chat/executeQuickActionButton';
30
30
  import { resolveChatMessageValidationIssue } from '../../../utils/chat/validateChatMessageContent';
31
- import { createServerLanguageMoment } from '../../../utils/localization/createServerLanguageMoment';
31
+ import { formatServerLanguageHumanReadableDate } from '../../../utils/localization/formatServerLanguageHumanReadableDate';
32
32
  import { createDefaultSpeechRecognition } from '../../../utils/speech-to-text/createDefaultSpeechRecognition';
33
33
  import { chatFileUploadHandler } from '../../../utils/upload/createBookEditorUploadHandler';
34
34
  import { createUserChatClientMessageId, type UserChatSummary } from '../../../utils/userChatClient';
@@ -120,21 +120,6 @@ type OptimisticChatNavigationState = {
120
120
  attachments?: ChatMessage['attachments'];
121
121
  };
122
122
 
123
- /**
124
- * Parses one profile-chat timestamp using the active Agents Server language.
125
- *
126
- * @param timestamp - ISO string describing the chat update time.
127
- * @param language - Active Agents Server language code.
128
- * @returns Localized moment instance or `null` when the timestamp is invalid.
129
- */
130
- function resolveProfileChatTimestampMoment(
131
- timestamp: string,
132
- language: ServerLanguageCode,
133
- ): ReturnType<typeof createServerLanguageMoment> | null {
134
- const parsed = createServerLanguageMoment(timestamp, language);
135
- return parsed.isValid() ? parsed : null;
136
- }
137
-
138
123
  /**
139
124
  * Returns true when a message has non-whitespace content.
140
125
  */
@@ -643,9 +628,10 @@ function ExistingChatsPanel({
643
628
  </div>
644
629
  <div className="mt-4 space-y-3 overflow-y-auto pr-1" style={{ maxHeight: `${scrollViewportHeight}px` }}>
645
630
  {chats.map((chat) => {
646
- const updatedAtMoment = resolveProfileChatTimestampMoment(chat.updatedAt, language);
647
- const fullTimeLabel = updatedAtMoment ? updatedAtMoment.format('L LT') : chat.updatedAt;
648
- const timeLabel = updatedAtMoment ? updatedAtMoment.fromNow() || fullTimeLabel : chat.updatedAt;
631
+ const fullTimeLabel = formatServerLanguageHumanReadableDate(chat.updatedAt, language, {
632
+ isExactDateIncluded: true,
633
+ });
634
+ const timeLabel = formatServerLanguageHumanReadableDate(chat.updatedAt, language);
649
635
  const title = chat.title || formatText('Untitled chat');
650
636
  const previewText = hasMessageContent(chat.preview)
651
637
  ? chat.preview
@@ -678,7 +664,7 @@ function ExistingChatsPanel({
678
664
  </span>
679
665
  </div>
680
666
  <time
681
- dateTime={updatedAtMoment ? updatedAtMoment.toISOString() : chat.updatedAt}
667
+ dateTime={chat.updatedAt}
682
668
  title={fullTimeLabel}
683
669
  className="text-[0.65rem] font-semibold text-slate-400 dark:text-slate-500"
684
670
  >
@@ -1,5 +1,6 @@
1
1
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
2
2
  import { $provideAgentReferenceResolver } from '@/src/utils/agentReferenceResolver/$provideAgentReferenceResolver';
3
+ import { invalidateCachedActiveOrganizationSnapshots } from '@/src/utils/agentOrganization/loadAgentOrganizationState';
3
4
  import {
4
5
  parseBookScopedAgentIdentifier,
5
6
  resolveBookScopedAgentContext,
@@ -172,6 +173,7 @@ export async function PUT(request: Request, { params }: { params: Promise<{ agen
172
173
 
173
174
  const agentId = await collection.getAgentPermanentId(agentName);
174
175
  await collection.updateAgentSource(agentId, agentSource, { versionName });
176
+ invalidateCachedActiveOrganizationSnapshots();
175
177
  // <- TODO: [🐱‍🚀] Properly type as string_book
176
178
 
177
179
  return new Response(
@@ -1,6 +1,9 @@
1
1
  import type { string_book } from '@promptbook-local/types';
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { showAlert, showConfirm, showPrompt } from '@/src/components/AsyncDialogs/asyncDialogs';
4
+ import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
5
+ import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
6
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
4
7
  import type { BookEditorHistoryVersionItem } from './BookEditorHistoryPanel';
5
8
  import { resolveBookEditorApiErrorMessage } from './resolveBookEditorApiErrorMessage';
6
9
 
@@ -340,6 +343,7 @@ function resolveSelectedHistoryId<HistoryItem extends { readonly id: number }>(
340
343
  */
341
344
  function buildHistoryVersionItems(
342
345
  historyEntries: ReadonlyArray<AgentHistoryEntry>,
346
+ language: ServerLanguageCode,
343
347
  ): Array<BookEditorHistoryVersionItem> {
344
348
  const totalVersions = historyEntries.length;
345
349
 
@@ -347,7 +351,9 @@ function buildHistoryVersionItems(
347
351
  id: entry.id,
348
352
  versionName: normalizeHistoryVersionName(entry.versionName),
349
353
  versionLabel: `Version ${totalVersions - index}`,
350
- createdAtLabel: new Date(entry.createdAt).toLocaleString(),
354
+ createdAtLabel: formatServerLanguageHumanReadableDate(entry.createdAt, language, {
355
+ isExactDateIncluded: true,
356
+ }),
351
357
  hash: entry.agentHash,
352
358
  hashPreview: entry.agentHash.slice(0, 8),
353
359
  source: entry.agentSource,
@@ -800,6 +806,7 @@ export function useBookEditorHistory({
800
806
  replaceWithRestoredSource,
801
807
  requestDiagnostics,
802
808
  }: UseBookEditorHistoryProps) {
809
+ const { language } = useServerLanguage();
803
810
  const [isHistoryOpen, setIsHistoryOpen] = useState(false);
804
811
  const [historyNameQuery, setHistoryNameQuery] = useState('');
805
812
  const [isNamedHistoryOnly, setIsNamedHistoryOnly] = useState(false);
@@ -840,7 +847,7 @@ export function useBookEditorHistory({
840
847
  *
841
848
  * @private function of useBookEditorHistory
842
849
  */
843
- const historyVersions = useMemo(() => buildHistoryVersionItems(historyEntries), [historyEntries]);
850
+ const historyVersions = useMemo(() => buildHistoryVersionItems(historyEntries, language), [historyEntries, language]);
844
851
 
845
852
  /**
846
853
  * Filtered history items matching the current panel controls.
@@ -9,9 +9,8 @@ import { usePrivateModePreferences } from '../../../../components/PrivateModePre
9
9
  import { useBrowserPushNotifications } from '../../../../components/PushNotifications/BrowserPushNotificationsProvider';
10
10
  import { useServerLanguage } from '../../../../components/ServerLanguage/ServerLanguageProvider';
11
11
  import { useActiveBrowserTab } from '../../../../hooks/useActiveBrowserTab';
12
- import type { ServerLanguageCode } from '../../../../languages/ServerLanguageRegistry';
13
12
  import type { ChatFeedbackMode } from '../../../../utils/chatFeedbackMode';
14
- import { createServerLanguageMoment } from '../../../../utils/localization/createServerLanguageMoment';
13
+ import { formatServerLanguageHumanReadableDate } from '../../../../utils/localization/formatServerLanguageHumanReadableDate';
15
14
  import { consumeShareTargetPayloadFromBrowser } from '../../../../utils/shareTargetClient';
16
15
  import type { UserChatJob, UserChatSummary, UserChatTimeout } from '../../../../utils/userChatClient';
17
16
  import { FORCE_NEW_CHAT_QUERY_VALUE } from '../agentChatNavigationUtils';
@@ -309,7 +308,7 @@ export function useAgentChatHistoryClientState(
309
308
  useHoistedMobileMenuItems(hoistedMobileMenuItems);
310
309
 
311
310
  const formatChatTimestamp = useCallback(
312
- (timestamp: string): string => formatRelativeChatTimestamp(timestamp, language),
311
+ (timestamp: string): string => formatServerLanguageHumanReadableDate(timestamp, language),
313
312
  [language],
314
313
  );
315
314
 
@@ -359,15 +358,3 @@ function hasAutoExecutePayload(
359
358
  ): boolean {
360
359
  return Boolean(message) || Boolean(attachments?.length);
361
360
  }
362
-
363
- /**
364
- * Formats one chat timestamp into relative text using the active server language.
365
- */
366
- function formatRelativeChatTimestamp(timestamp: string, language: ServerLanguageCode): string {
367
- const parsed = createServerLanguageMoment(timestamp, language);
368
- if (!parsed.isValid()) {
369
- return timestamp;
370
- }
371
-
372
- return parsed.fromNow();
373
- }
@@ -3,6 +3,8 @@ import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentColle
3
3
  import { formatAgentNamingText } from '@/src/utils/agentNaming';
4
4
  import { getCurrentUser } from '@/src/utils/getCurrentUser';
5
5
  import { getAgentNaming } from '@/src/utils/getAgentNaming';
6
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
7
+ import { getRequestServerLanguage } from '@/src/utils/localization/getRequestServerLanguage';
6
8
  import { HistoryIcon } from 'lucide-react';
7
9
  import { RestoreVersionButton } from './RestoreVersionButton';
8
10
  import { enforceCanonicalLocalAgentId, getAgentName } from '../_utils';
@@ -39,6 +41,7 @@ export default async function AgentHistoryPage({ params }: { params: Promise<{ a
39
41
  const collection = await $provideAgentCollectionForServer();
40
42
  const history = await collection.listAgentHistory(agentName);
41
43
  const agentNaming = await getAgentNaming();
44
+ const language = await getRequestServerLanguage();
42
45
 
43
46
  return (
44
47
  <div className="container mx-auto p-6 max-w-4xl">
@@ -69,7 +72,9 @@ export default async function AgentHistoryPage({ params }: { params: Promise<{ a
69
72
  <div className="flex justify-between items-start mb-2">
70
73
  <div>
71
74
  <time className="block mb-1 text-sm font-normal leading-none text-gray-400">
72
- {new Date(item.createdAt).toLocaleString()}
75
+ {formatServerLanguageHumanReadableDate(item.createdAt, language, {
76
+ isExactDateIncluded: true,
77
+ })}
73
78
  </time>
74
79
  <h3 className="text-lg font-semibold text-gray-900">
75
80
  {item.versionName || `Version ${history.length - index}`}
@@ -1,7 +1,10 @@
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 type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
4
6
  import { buildCalendarOAuthConnectUrl } from '@/src/utils/calendarOAuthClient';
7
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
5
8
  import { Calendar, Link2, RefreshCw, Unlink } from 'lucide-react';
6
9
  import { useCallback, useEffect, useMemo, useState } from 'react';
7
10
 
@@ -71,6 +74,7 @@ type CalendarIntegrationSectionProps = {
71
74
  * Renders connected-calendar settings for one agent, including re-auth/disconnect and activity timeline.
72
75
  */
73
76
  export function CalendarIntegrationSection({ agentName, agentPermanentId }: CalendarIntegrationSectionProps) {
77
+ const { language } = useServerLanguage();
74
78
  const {
75
79
  activeConnections,
76
80
  disconnectConnection,
@@ -124,7 +128,7 @@ export function CalendarIntegrationSection({ agentName, agentPermanentId }: Cale
124
128
  onDisconnect={(connection) => void disconnectConnection(connection)}
125
129
  />
126
130
 
127
- <CalendarActivitySection activity={payload?.activity || []} />
131
+ <CalendarActivitySection activity={payload?.activity || []} language={language} />
128
132
  </>
129
133
  )}
130
134
  </div>
@@ -497,7 +501,13 @@ function CalendarConnectionCard({
497
501
  /**
498
502
  * Renders the recent calendar activity section.
499
503
  */
500
- function CalendarActivitySection({ activity }: { activity: ReadonlyArray<CalendarActivityItem> }) {
504
+ function CalendarActivitySection({
505
+ activity,
506
+ language,
507
+ }: {
508
+ activity: ReadonlyArray<CalendarActivityItem>;
509
+ language: ServerLanguageCode;
510
+ }) {
501
511
  return (
502
512
  <div className="mt-6 space-y-3">
503
513
  <h3 className="text-sm font-semibold uppercase tracking-wide text-gray-700">Recent activity</h3>
@@ -506,7 +516,7 @@ function CalendarActivitySection({ activity }: { activity: ReadonlyArray<Calenda
506
516
  ) : (
507
517
  <div className="space-y-2">
508
518
  {activity.map((item) => (
509
- <CalendarActivityCard key={item.id} activity={item} />
519
+ <CalendarActivityCard key={item.id} activity={item} language={language} />
510
520
  ))}
511
521
  </div>
512
522
  )}
@@ -517,7 +527,13 @@ function CalendarActivitySection({ activity }: { activity: ReadonlyArray<Calenda
517
527
  /**
518
528
  * Renders one recent calendar activity row.
519
529
  */
520
- function CalendarActivityCard({ activity }: { activity: CalendarActivityItem }) {
530
+ function CalendarActivityCard({
531
+ activity,
532
+ language,
533
+ }: {
534
+ activity: CalendarActivityItem;
535
+ language: ServerLanguageCode;
536
+ }) {
521
537
  return (
522
538
  <div className="rounded-lg border border-blue-100 bg-white/90 px-3 py-2">
523
539
  <div className="flex flex-wrap items-center gap-2 text-xs">
@@ -529,7 +545,9 @@ function CalendarActivityCard({ activity }: { activity: CalendarActivityItem })
529
545
  >
530
546
  {activity.status}
531
547
  </span>
532
- <span className="text-gray-500">{new Date(activity.createdAt).toLocaleString()}</span>
548
+ <span className="text-gray-500">
549
+ {formatServerLanguageHumanReadableDate(activity.createdAt, language)}
550
+ </span>
533
551
  </div>
534
552
  {activity.calendarUrl && (
535
553
  <p className="mt-1 text-[11px] text-gray-600 break-all font-mono">{activity.calendarUrl}</p>
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
3
4
  import { AgentTimeoutsEditDialog } from './AgentTimeoutsEditDialog';
4
5
  import { AgentTimeoutsFiltersCard } from './AgentTimeoutsFiltersCard';
5
6
  import { AgentTimeoutsHeader } from './AgentTimeoutsHeader';
@@ -20,13 +21,14 @@ type AgentTimeoutsClientProps = {
20
21
  * @private route component of AgentTimeoutsPage
21
22
  */
22
23
  export function AgentTimeoutsClient({ agentName }: AgentTimeoutsClientProps) {
24
+ const { language } = useServerLanguage();
23
25
  const state = useAgentTimeoutsClientState({ agentName });
24
26
 
25
27
  return (
26
28
  <div className="container mx-auto mt-20 space-y-6 px-4 py-8">
27
29
  <AgentTimeoutsHeader state={state} />
28
30
  <AgentTimeoutsSummaryMetrics state={state} />
29
- <AgentTimeoutsFiltersCard state={state} />
31
+ <AgentTimeoutsFiltersCard language={language} state={state} />
30
32
 
31
33
  {state.errorMessage ? (
32
34
  <div className="rounded-2xl border border-red-200 bg-red-50 p-4 text-sm text-red-700">
@@ -34,7 +36,7 @@ export function AgentTimeoutsClient({ agentName }: AgentTimeoutsClientProps) {
34
36
  </div>
35
37
  ) : null}
36
38
 
37
- <AgentTimeoutsTableCard agentName={agentName} state={state} />
39
+ <AgentTimeoutsTableCard agentName={agentName} language={language} state={state} />
38
40
  <AgentTimeoutsEditDialog state={state} />
39
41
  </div>
40
42
  );
@@ -1,3 +1,5 @@
1
+ import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
2
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
1
3
  import type { useAgentTimeoutsClientState } from './useAgentTimeoutsClientState';
2
4
 
3
5
  /**
@@ -6,6 +8,7 @@ import type { useAgentTimeoutsClientState } from './useAgentTimeoutsClientState'
6
8
  * @private function of AgentTimeoutsClient
7
9
  */
8
10
  type AgentTimeoutsFiltersCardProps = {
11
+ language: ServerLanguageCode;
9
12
  state: ReturnType<typeof useAgentTimeoutsClientState>;
10
13
  };
11
14
 
@@ -36,8 +39,10 @@ const TIMEOUT_MANAGER_FILTER_OPTIONS: ReadonlyArray<TimeoutManagerFilterOption>
36
39
  *
37
40
  * @private function of AgentTimeoutsFiltersCard
38
41
  */
39
- function resolveLastRefreshedLabel(generatedAt: string | null): string {
40
- return generatedAt ? `Last refreshed ${new Date(generatedAt).toLocaleString()}` : 'Waiting for first refresh...';
42
+ function resolveLastRefreshedLabel(generatedAt: string | null, language: ServerLanguageCode): string {
43
+ return generatedAt
44
+ ? `Last refreshed ${formatServerLanguageHumanReadableDate(generatedAt, language)}`
45
+ : 'Waiting for first refresh...';
41
46
  }
42
47
 
43
48
  /**
@@ -45,7 +50,7 @@ function resolveLastRefreshedLabel(generatedAt: string | null): string {
45
50
  *
46
51
  * @private function of AgentTimeoutsClient
47
52
  */
48
- export function AgentTimeoutsFiltersCard({ state }: AgentTimeoutsFiltersCardProps) {
53
+ export function AgentTimeoutsFiltersCard({ language, state }: AgentTimeoutsFiltersCardProps) {
49
54
  return (
50
55
  <div className="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm">
51
56
  <div className="flex flex-wrap gap-2">
@@ -64,7 +69,7 @@ export function AgentTimeoutsFiltersCard({ state }: AgentTimeoutsFiltersCardProp
64
69
  </button>
65
70
  ))}
66
71
  </div>
67
- <div className="mt-3 text-xs text-gray-500">{resolveLastRefreshedLabel(state.generatedAt)}</div>
72
+ <div className="mt-3 text-xs text-gray-500">{resolveLastRefreshedLabel(state.generatedAt, language)}</div>
68
73
  </div>
69
74
  );
70
75
  }
@@ -1,3 +1,4 @@
1
+ import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
1
2
  import type { useAgentTimeoutsClientState } from './useAgentTimeoutsClientState';
2
3
  import { AgentTimeoutsTableRow } from './AgentTimeoutsTableRow';
3
4
 
@@ -8,6 +9,7 @@ import { AgentTimeoutsTableRow } from './AgentTimeoutsTableRow';
8
9
  */
9
10
  type AgentTimeoutsTableCardProps = {
10
11
  agentName: string;
12
+ language: ServerLanguageCode;
11
13
  state: ReturnType<typeof useAgentTimeoutsClientState>;
12
14
  };
13
15
 
@@ -16,7 +18,7 @@ type AgentTimeoutsTableCardProps = {
16
18
  *
17
19
  * @private function of AgentTimeoutsClient
18
20
  */
19
- export function AgentTimeoutsTableCard({ agentName, state }: AgentTimeoutsTableCardProps) {
21
+ export function AgentTimeoutsTableCard({ agentName, language, state }: AgentTimeoutsTableCardProps) {
20
22
  return (
21
23
  <div className="rounded-2xl border border-gray-200 bg-white shadow-sm">
22
24
  {state.isLoading ? (
@@ -42,6 +44,7 @@ export function AgentTimeoutsTableCard({ agentName, state }: AgentTimeoutsTableC
42
44
  <AgentTimeoutsTableRow
43
45
  key={timeout.timeoutId}
44
46
  agentName={agentName}
47
+ language={language}
45
48
  state={state}
46
49
  timeout={timeout}
47
50
  />
@@ -1,4 +1,6 @@
1
1
  import type { UserChatTimeout } from '@/src/utils/userChatClient';
2
+ import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
3
+ import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
2
4
  import Link from 'next/link';
3
5
  import type { useAgentTimeoutsClientState } from './useAgentTimeoutsClientState';
4
6
 
@@ -9,6 +11,7 @@ import type { useAgentTimeoutsClientState } from './useAgentTimeoutsClientState'
9
11
  */
10
12
  type AgentTimeoutsTableRowProps = {
11
13
  agentName: string;
14
+ language: ServerLanguageCode;
12
15
  state: ReturnType<typeof useAgentTimeoutsClientState>;
13
16
  timeout: UserChatTimeout;
14
17
  };
@@ -141,7 +144,7 @@ function truncateText(value: string, maxLength: number): string {
141
144
  *
142
145
  * @private function of AgentTimeoutsClient
143
146
  */
144
- export function AgentTimeoutsTableRow({ agentName, state, timeout }: AgentTimeoutsTableRowProps) {
147
+ export function AgentTimeoutsTableRow({ agentName, language, state, timeout }: AgentTimeoutsTableRowProps) {
145
148
  const isBusy = state.busyTimeoutId === timeout.timeoutId;
146
149
  const isPaused = isTimeoutPaused(timeout);
147
150
  const isEditable = isTimeoutEditable(timeout);
@@ -152,7 +155,9 @@ export function AgentTimeoutsTableRow({ agentName, state, timeout }: AgentTimeou
152
155
  <tr>
153
156
  <td className="px-4 py-3 align-top">
154
157
  <div className="font-mono text-[11px] font-semibold text-gray-900">{timeout.timeoutId}</div>
155
- <div className="mt-1 text-[11px] text-gray-500">Updated {new Date(timeout.updatedAt).toLocaleString()}</div>
158
+ <div className="mt-1 text-[11px] text-gray-500">
159
+ Updated {formatServerLanguageHumanReadableDate(timeout.updatedAt, language)}
160
+ </div>
156
161
  </td>
157
162
  <td className="px-4 py-3 align-top">
158
163
  <Link
@@ -167,7 +172,9 @@ export function AgentTimeoutsTableRow({ agentName, state, timeout }: AgentTimeou
167
172
  {statusPresentation.label}
168
173
  </span>
169
174
  </td>
170
- <td className="px-4 py-3 align-top">{new Date(timeout.dueAt).toLocaleString()}</td>
175
+ <td className="px-4 py-3 align-top">
176
+ {formatServerLanguageHumanReadableDate(timeout.dueAt, language, { isExactDateIncluded: true })}
177
+ </td>
171
178
  <td className="px-4 py-3 align-top">
172
179
  {timeout.recurrenceIntervalMs ? `Every ${formatDuration(timeout.recurrenceIntervalMs)}` : 'One-shot'}
173
180
  </td>
@@ -0,0 +1,35 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { isUserGlobalAdmin } from '@/src/utils/isUserGlobalAdmin';
3
+ import { listVpsSelfUpdateCandidateCommits } from '@/src/utils/vpsSelfUpdate';
4
+
5
+ export const runtime = 'nodejs';
6
+ export const dynamic = 'force-dynamic';
7
+
8
+ /**
9
+ * Lists candidate commits for the standalone VPS self-update custom-target picker.
10
+ */
11
+ export async function GET(request: Request) {
12
+ if (!(await isUserGlobalAdmin())) {
13
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
14
+ }
15
+
16
+ try {
17
+ const requestUrl = new URL(request.url);
18
+ const limitParameter = requestUrl.searchParams.get('limit');
19
+ const parsedLimit = limitParameter ? Number.parseInt(limitParameter, 10) : null;
20
+
21
+ const commits = await listVpsSelfUpdateCandidateCommits({
22
+ searchText: requestUrl.searchParams.get('search'),
23
+ authoredAfter: requestUrl.searchParams.get('after'),
24
+ authoredBefore: requestUrl.searchParams.get('before'),
25
+ limit: Number.isFinite(parsedLimit) ? parsedLimit : null,
26
+ });
27
+
28
+ return NextResponse.json({ commits });
29
+ } catch (error) {
30
+ return NextResponse.json(
31
+ { error: error instanceof Error ? error.message : 'Failed to load candidate commits.' },
32
+ { status: 500 },
33
+ );
34
+ }
35
+ }
@@ -24,7 +24,7 @@ export async function GET() {
24
24
  }
25
25
 
26
26
  /**
27
- * Starts a detached standalone VPS self-update for the selected environment.
27
+ * Starts a detached standalone VPS self-update for the selected environment or arbitrary ref.
28
28
  */
29
29
  export async function POST(request: Request) {
30
30
  if (!(await isUserGlobalAdmin())) {
@@ -35,6 +35,8 @@ export async function POST(request: Request) {
35
35
  const body = (await request.json().catch(() => null)) as
36
36
  | {
37
37
  readonly environment?: string;
38
+ readonly customRef?: string | null;
39
+ readonly originRepositoryUrl?: string | null;
38
40
  }
39
41
  | null;
40
42
 
@@ -42,7 +44,13 @@ export async function POST(request: Request) {
42
44
  return NextResponse.json({ error: 'Update environment is required.' }, { status: 400 });
43
45
  }
44
46
 
45
- return NextResponse.json(await startVpsSelfUpdate(body.environment), { status: 202 });
47
+ const overview = await startVpsSelfUpdate({
48
+ environmentId: body.environment,
49
+ customRef: body.customRef ?? null,
50
+ originRepositoryUrl: body.originRepositoryUrl ?? null,
51
+ });
52
+
53
+ return NextResponse.json(overview, { status: 202 });
46
54
  } catch (error) {
47
55
  return NextResponse.json(
48
56
  { error: error instanceof Error ? error.message : 'Failed to start the update.' },