@promptbook/cli 0.113.0-4 → 0.113.0-8

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 (141) hide show
  1. package/apps/agents-server/src/app/admin/update/AdvancedOriginRepositoryPanel.tsx +76 -0
  2. package/apps/agents-server/src/app/admin/update/CurrentDeploymentCard.tsx +160 -0
  3. package/apps/agents-server/src/app/admin/update/PendingCommitsCard.tsx +86 -0
  4. package/apps/agents-server/src/app/admin/update/TargetEnvironmentCard.tsx +331 -0
  5. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +20 -873
  6. package/apps/agents-server/src/app/admin/update/UpdateJobCard.tsx +329 -0
  7. package/apps/agents-server/src/app/admin/update/UpdateOverview.ts +74 -0
  8. package/apps/agents-server/src/app/admin/update/buildDeploymentTimeBehindLabel.ts +29 -0
  9. package/apps/agents-server/src/app/admin/update/formatHumanReadableTimestamp.ts +18 -0
  10. package/apps/agents-server/src/app/admin/update/getUpdateJobFailureMessage.ts +13 -0
  11. package/apps/agents-server/src/app/admin/update/getUpdateJobSuccessMessage.ts +13 -0
  12. package/apps/agents-server/src/app/admin/update/useUpdateClientState.ts +441 -0
  13. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +13 -1
  14. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageClient.tsx +14 -559
  15. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageHeader.tsx +84 -0
  16. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/AgentCodePageSection.tsx +52 -0
  17. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/GeneratedCodePreview.tsx +138 -0
  18. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/GeneratedHarnessSection.tsx +98 -0
  19. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/SourceBookSection.tsx +66 -0
  20. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/TranspiledCodeErrorBanner.tsx +30 -0
  21. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/TranspiledCodeExportWarningBanner.tsx +62 -0
  22. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/Transpiler.ts +9 -0
  23. package/apps/agents-server/src/app/agents/[agentName]/export-as-transpiled-code/useAgentCodeExportState.ts +478 -0
  24. package/apps/agents-server/src/app/system/utilities/mocked-chats/cloneMockedChatPreset.ts +17 -0
  25. package/apps/agents-server/src/app/system/utilities/mocked-chats/confirmDeleteMockedChat.ts +16 -0
  26. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedMessage.ts +81 -0
  27. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedParticipant.ts +146 -0
  28. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDraftWithUpdatedSettings.ts +19 -0
  29. package/apps/agents-server/src/app/system/utilities/mocked-chats/createDuplicatedDraft.ts +66 -0
  30. package/apps/agents-server/src/app/system/utilities/mocked-chats/findMockedChatById.ts +51 -0
  31. package/apps/agents-server/src/app/system/utilities/mocked-chats/persistMockedChats.ts +51 -0
  32. package/apps/agents-server/src/app/system/utilities/mocked-chats/useMockedChatsEditorState.ts +16 -426
  33. package/apps/agents-server/src/components/Homepage/buildFreeGraphBoxLayout.ts +446 -0
  34. package/apps/agents-server/src/components/Homepage/buildGraphLayoutNodes.ts +415 -157
  35. package/apps/agents-server/src/components/Homepage/useAgentsGraphCanvasState.ts +2 -1
  36. package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +3 -1541
  37. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/LocalSqliteQueryBuilder.ts +623 -0
  38. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/LocalSqliteSupabaseClient.ts +64 -0
  39. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/ensureTable.ts +93 -0
  40. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteFilters.ts +202 -0
  41. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteMutationPayload.ts +131 -0
  42. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteRowOperations.ts +102 -0
  43. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteSql.ts +59 -0
  44. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteTableSchema.ts +329 -0
  45. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteTypes.ts +84 -0
  46. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/localSqliteValueCodec.ts +63 -0
  47. package/apps/agents-server/src/database/sqlite/localSqliteSupabase/normalizeSqliteError.ts +22 -0
  48. package/apps/agents-server/src/search/createDefaultServerSearchProviders/loadLocalOrganizationSearchDataset.ts +12 -1
  49. package/apps/agents-server/src/tools/send_email.ts +1 -2
  50. package/apps/agents-server/src/utils/agentOrganization/loadAgentOrganizationState.ts +13 -1
  51. package/apps/agents-server/src/utils/backup/createServerBackupZipStream.ts +7 -1382
  52. package/apps/agents-server/src/utils/backup/serverBackup/appendAgentBackupEntriesToZip.ts +27 -0
  53. package/apps/agents-server/src/utils/backup/serverBackup/appendConversationBackupEntriesToZip.ts +140 -0
  54. package/apps/agents-server/src/utils/backup/serverBackup/appendFileBackupEntriesToZip.ts +147 -0
  55. package/apps/agents-server/src/utils/backup/serverBackup/appendMessageBackupEntriesToZip.ts +65 -0
  56. package/apps/agents-server/src/utils/backup/serverBackup/appendMetadataBackupEntriesToZip.ts +39 -0
  57. package/apps/agents-server/src/utils/backup/serverBackup/appendSectionEntriesToZip.ts +92 -0
  58. package/apps/agents-server/src/utils/backup/serverBackup/appendUserBackupEntriesToZip.ts +80 -0
  59. package/apps/agents-server/src/utils/backup/serverBackup/createRedactedWalletBackupRecord.ts +34 -0
  60. package/apps/agents-server/src/utils/backup/serverBackup/createServerBackupManifest.ts +82 -0
  61. package/apps/agents-server/src/utils/backup/serverBackup/downloadBackupBinaryContent.ts +39 -0
  62. package/apps/agents-server/src/utils/backup/serverBackup/resolveFeedbackThreadMessages.ts +53 -0
  63. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupAttachments.ts +151 -0
  64. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupContext.ts +167 -0
  65. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupFilenames.ts +161 -0
  66. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupPreviews.ts +97 -0
  67. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupRowUtilities.ts +158 -0
  68. package/apps/agents-server/src/utils/backup/serverBackup/serverBackupTypes.ts +59 -0
  69. package/apps/agents-server/src/utils/createLocalAgentSourceImporter.ts +159 -0
  70. package/apps/agents-server/src/utils/createMissingImportedAgentFallback.ts +60 -0
  71. package/apps/agents-server/src/utils/customDomainRouting.ts +157 -12
  72. package/apps/agents-server/src/utils/externalChatRunner/processExternalUserChatJob.ts +1 -1
  73. package/apps/agents-server/src/utils/importAgentWithFallback.ts +1 -58
  74. package/apps/agents-server/src/utils/localAgentRouteReferences.ts +167 -0
  75. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +1 -1
  76. package/apps/agents-server/src/utils/managementApi/managementApiAgents.ts +17 -3
  77. package/apps/agents-server/src/utils/messages/sendMessage.ts +5 -3
  78. package/apps/agents-server/src/utils/resolveAgentStateFromSource.ts +7 -1
  79. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +54 -5
  80. package/apps/agents-server/src/utils/resolveServerAgentContext.ts +12 -1
  81. package/apps/agents-server/src/utils/resolveStoredAgentState.ts +2 -1
  82. package/apps/agents-server/src/utils/speech-to-text/SpeechToTextFailoverRecognition/SpeechToTextFailoverRecognitionProviderRuntime.ts +0 -1
  83. package/apps/agents-server/src/utils/speech-to-text/SpeechToTextFailoverRecognition.ts +34 -2
  84. package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +39 -9
  85. package/apps/agents-server/tests/e2e/support/ChatHistoryNavigationSupport.ts +5 -0
  86. package/esm/index.es.js +875 -60
  87. package/esm/index.es.js.map +1 -1
  88. package/esm/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +7 -0
  89. package/esm/scripts/find-refactor-candidates/selectMostImportantRefactorCandidates.d.ts +12 -0
  90. package/esm/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +20 -0
  91. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +5 -0
  92. package/esm/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +29 -0
  93. package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +4 -0
  94. package/esm/src/avatars/renderAvatarVisualAsciiArt.d.ts +75 -0
  95. package/esm/src/book-components/Chat/Chat/insertDictationChunk.d.ts +1 -1
  96. package/esm/src/book-components/Chat/Chat/insertDictationChunk.test.d.ts +1 -0
  97. package/esm/src/book-components/Chat/Chat/learnDictationDictionary.test.d.ts +1 -0
  98. package/esm/src/book-components/Chat/Chat/refineFinalDictationChunk.test.d.ts +1 -0
  99. package/esm/src/cli/cli-commands/common/createPositiveIntegerOptionParser.d.ts +10 -0
  100. package/esm/src/utils/ascii-art/$detectTerminalAnsiColorDepth.d.ts +15 -0
  101. package/esm/src/utils/ascii-art/convertImageDataToAsciiArt.d.ts +80 -0
  102. package/esm/src/version.d.ts +1 -1
  103. package/package.json +1 -1
  104. package/src/avatars/avatarAnimationScheduler.ts +2 -1
  105. package/src/avatars/renderAvatarVisualAsciiArt.ts +138 -0
  106. package/src/avatars/visuals/octopus3d3AvatarVisual.ts +6 -2
  107. package/src/avatars/visuals/octopus3d4AvatarVisual.ts +14 -18
  108. package/src/book-components/Chat/Chat/insertDictationChunk.ts +3 -3
  109. package/src/book-components/Chat/Chat/learnDictationDictionary.ts +1 -1
  110. package/src/book-components/Chat/Chat/useChatInputAreaDictation.ts +133 -26
  111. package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +3 -1
  112. package/src/cli/cli-commands/coder/boilerplateTemplates.ts +14 -21
  113. package/src/cli/cli-commands/coder/find-refactor-candidates.ts +9 -2
  114. package/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts +1 -4
  115. package/src/cli/cli-commands/coder/run.ts +3 -27
  116. package/src/cli/cli-commands/common/createPositiveIntegerOptionParser.ts +31 -0
  117. package/src/commitments/TEAM/TEAM.ts +5 -5
  118. package/src/other/templates/getTemplatesPipelineCollection.ts +770 -1169
  119. package/src/utils/ascii-art/$detectTerminalAnsiColorDepth.ts +45 -0
  120. package/src/utils/ascii-art/convertImageDataToAsciiArt.ts +396 -0
  121. package/src/version.ts +2 -2
  122. package/src/versions.txt +2 -0
  123. package/umd/index.umd.js +875 -60
  124. package/umd/index.umd.js.map +1 -1
  125. package/umd/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +7 -0
  126. package/umd/scripts/find-refactor-candidates/selectMostImportantRefactorCandidates.d.ts +12 -0
  127. package/umd/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +20 -0
  128. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +5 -0
  129. package/umd/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +29 -0
  130. package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +4 -0
  131. package/umd/src/avatars/renderAvatarVisualAsciiArt.d.ts +75 -0
  132. package/umd/src/book-components/Chat/Chat/insertDictationChunk.d.ts +1 -1
  133. package/umd/src/book-components/Chat/Chat/insertDictationChunk.test.d.ts +1 -0
  134. package/umd/src/book-components/Chat/Chat/learnDictationDictionary.test.d.ts +1 -0
  135. package/umd/src/book-components/Chat/Chat/refineFinalDictationChunk.test.d.ts +1 -0
  136. package/umd/src/cli/cli-commands/common/createPositiveIntegerOptionParser.d.ts +10 -0
  137. package/umd/src/utils/ascii-art/$detectTerminalAnsiColorDepth.d.ts +15 -0
  138. package/umd/src/utils/ascii-art/convertImageDataToAsciiArt.d.ts +80 -0
  139. package/umd/src/version.d.ts +1 -1
  140. package/esm/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +0 -6
  141. package/umd/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +0 -6
@@ -0,0 +1,82 @@
1
+ import {
2
+ DEFAULT_SERVER_BACKUP_SECTION_KEYS,
3
+ type ServerBackupSectionDefinition,
4
+ type ServerBackupSectionKey,
5
+ } from '../serverBackupSections';
6
+
7
+ /**
8
+ * Stable archive format identifier for the user-facing backup layout.
9
+ *
10
+ * @private constant of `createServerBackupZipStream`
11
+ */
12
+ const SERVER_BACKUP_FORMAT = 'promptbook-server-backup/v2';
13
+
14
+ /**
15
+ * High-level manifest written into every server backup ZIP.
16
+ *
17
+ * @private type of `createServerBackupZipStream`
18
+ */
19
+ export type ServerBackupManifest = {
20
+ /**
21
+ * Stable backup archive format identifier.
22
+ */
23
+ readonly format: typeof SERVER_BACKUP_FORMAT;
24
+ /**
25
+ * ISO timestamp when the archive was generated.
26
+ */
27
+ readonly generatedAt: string;
28
+ /**
29
+ * Whether the archive includes every default exportable section.
30
+ */
31
+ readonly isFullBackup: boolean;
32
+ /**
33
+ * Ordered list of exported section keys.
34
+ */
35
+ readonly selectedSections: ReadonlyArray<ServerBackupSectionKey>;
36
+ /**
37
+ * Human-readable section details for restore/debug tooling.
38
+ */
39
+ readonly sections: ReadonlyArray<{
40
+ readonly key: ServerBackupSectionKey;
41
+ readonly label: string;
42
+ readonly description: string;
43
+ readonly directoryName: string;
44
+ readonly selectionKind: ServerBackupSectionDefinition['selectionKind'];
45
+ readonly includesBooks: boolean;
46
+ }>;
47
+ };
48
+
49
+ /**
50
+ * Builds the high-level manifest describing one server backup archive.
51
+ *
52
+ * @param options - Generation timestamp and the resolved selection.
53
+ * @returns Manifest payload ready to serialize into `manifest.json`.
54
+ *
55
+ * @private function of `createServerBackupZipStream`
56
+ */
57
+ export function createServerBackupManifest(options: {
58
+ generatedAt: string;
59
+ selectedSectionKeys: ReadonlyArray<ServerBackupSectionKey>;
60
+ selectedSectionDefinitions: ReadonlyArray<ServerBackupSectionDefinition>;
61
+ }): ServerBackupManifest {
62
+ const { generatedAt, selectedSectionKeys, selectedSectionDefinitions } = options;
63
+
64
+ return {
65
+ format: SERVER_BACKUP_FORMAT,
66
+ generatedAt,
67
+ isFullBackup:
68
+ selectedSectionKeys.length === DEFAULT_SERVER_BACKUP_SECTION_KEYS.length &&
69
+ DEFAULT_SERVER_BACKUP_SECTION_KEYS.every((sectionKey, index) => selectedSectionKeys[index] === sectionKey),
70
+ selectedSections: selectedSectionKeys,
71
+ sections: selectedSectionDefinitions.map(
72
+ ({ key, label, description, directoryName, selectionKind, includesBooks }) => ({
73
+ key,
74
+ label,
75
+ description,
76
+ directoryName,
77
+ selectionKind,
78
+ includesBooks,
79
+ }),
80
+ ),
81
+ };
82
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Downloads one binary file for backup inclusion.
3
+ *
4
+ * @param url - Public binary URL to fetch.
5
+ * @returns Download result with either binary content or an explanatory error.
6
+ *
7
+ * @private function of `createServerBackupZipStream`
8
+ */
9
+ export async function downloadBackupBinaryContent(url: string | null): Promise<{
10
+ content: ArrayBuffer | null;
11
+ error: string | null;
12
+ }> {
13
+ if (!url) {
14
+ return {
15
+ content: null,
16
+ error: 'Missing content URL.',
17
+ };
18
+ }
19
+
20
+ try {
21
+ const response = await fetch(url);
22
+ if (!response.ok) {
23
+ return {
24
+ content: null,
25
+ error: `Failed to download content (${response.status} ${response.statusText}).`,
26
+ };
27
+ }
28
+
29
+ return {
30
+ content: await response.arrayBuffer(),
31
+ error: null,
32
+ };
33
+ } catch (error) {
34
+ return {
35
+ content: null,
36
+ error: error instanceof Error ? error.message : 'Unknown download error.',
37
+ };
38
+ }
39
+ }
@@ -0,0 +1,53 @@
1
+ import { isRecord } from './serverBackupRowUtilities';
2
+
3
+ /**
4
+ * Parses the persisted feedback chat thread into the same array shape used by chat JSON exports.
5
+ *
6
+ * @param rawChatThread - Raw persisted feedback thread.
7
+ * @returns Parsed chat messages together with an optional raw fallback string.
8
+ *
9
+ * @private function of `createServerBackupZipStream`
10
+ */
11
+ export function resolveFeedbackThreadMessages(rawChatThread: unknown): {
12
+ messages: Array<Record<string, unknown>>;
13
+ rawChatThreadText: string | null;
14
+ } {
15
+ if (Array.isArray(rawChatThread)) {
16
+ return {
17
+ messages: rawChatThread.filter(isRecord),
18
+ rawChatThreadText: null,
19
+ };
20
+ }
21
+
22
+ if (typeof rawChatThread !== 'string') {
23
+ return {
24
+ messages: [],
25
+ rawChatThreadText: null,
26
+ };
27
+ }
28
+
29
+ const normalizedChatThreadText = rawChatThread.trim();
30
+ if (normalizedChatThreadText.length === 0) {
31
+ return {
32
+ messages: [],
33
+ rawChatThreadText: null,
34
+ };
35
+ }
36
+
37
+ try {
38
+ const parsed = JSON.parse(normalizedChatThreadText);
39
+ if (Array.isArray(parsed)) {
40
+ return {
41
+ messages: parsed.filter(isRecord),
42
+ rawChatThreadText: null,
43
+ };
44
+ }
45
+ } catch {
46
+ // Keep the original text below so the backup still preserves the source thread payload.
47
+ }
48
+
49
+ return {
50
+ messages: [],
51
+ rawChatThreadText: normalizedChatThreadText,
52
+ };
53
+ }
@@ -0,0 +1,151 @@
1
+ import { resolveFeedbackThreadMessages } from './resolveFeedbackThreadMessages';
2
+ import { normalizeOptionalText, resolveSerializableArray } from './serverBackupRowUtilities';
3
+ import type { BackupAgentPreview, BackupUserPreview, ChatFeedbackRow, UserChatRow } from './serverBackupTypes';
4
+
5
+ /**
6
+ * Attachment reference stored alongside exported files/media metadata.
7
+ *
8
+ * @private type of `createServerBackupZipStream`
9
+ */
10
+ export type BackupAttachmentReference = {
11
+ readonly source: 'user-chat' | 'chat-feedback';
12
+ readonly messageIndex: number;
13
+ readonly messageId: string | null;
14
+ readonly attachmentName: string | null;
15
+ readonly sender: unknown;
16
+ readonly chatId?: string;
17
+ readonly chatTitle?: string | null;
18
+ readonly feedbackId?: number;
19
+ readonly user?: BackupUserPreview | null;
20
+ readonly agent?: BackupAgentPreview | null;
21
+ };
22
+
23
+ /**
24
+ * Builds one map of attachment URLs to the user-facing messages that reference them.
25
+ *
26
+ * @param options - Attachment map source data.
27
+ * @returns Attachment references keyed by URL.
28
+ *
29
+ * @private function of `createServerBackupZipStream`
30
+ */
31
+ export function createAttachmentReferencesByUrl(options: {
32
+ userChats: ReadonlyArray<UserChatRow>;
33
+ feedbackRows: ReadonlyArray<ChatFeedbackRow>;
34
+ userPreviewById: ReadonlyMap<number, BackupUserPreview>;
35
+ agentPreviewByPermanentId: ReadonlyMap<string, BackupAgentPreview>;
36
+ agentPreviewByName: ReadonlyMap<string, BackupAgentPreview>;
37
+ }): Map<string, Array<BackupAttachmentReference>> {
38
+ const attachmentReferencesByUrl = new Map<string, Array<BackupAttachmentReference>>();
39
+
40
+ const appendReference = (url: string, reference: BackupAttachmentReference): void => {
41
+ const references = attachmentReferencesByUrl.get(url) || [];
42
+ references.push(reference);
43
+ attachmentReferencesByUrl.set(url, references);
44
+ };
45
+
46
+ for (const chat of options.userChats) {
47
+ const userPreview = options.userPreviewById.get(chat.userId) || null;
48
+ const agentPreview = options.agentPreviewByPermanentId.get(chat.agentPermanentId) || null;
49
+
50
+ for (const [messageIndex, message] of resolveSerializableArray(chat.messages).entries()) {
51
+ for (const attachment of resolveMessageAttachments(message)) {
52
+ appendReference(attachment.url, {
53
+ source: 'user-chat',
54
+ chatId: chat.id,
55
+ chatTitle: chat.title,
56
+ messageIndex,
57
+ messageId: normalizeOptionalText((message as { id?: unknown }).id),
58
+ attachmentName: attachment.name,
59
+ sender: (message as { sender?: unknown }).sender ?? null,
60
+ user: userPreview,
61
+ agent: agentPreview,
62
+ });
63
+ }
64
+ }
65
+ }
66
+
67
+ for (const feedbackRow of options.feedbackRows) {
68
+ const agentPreview = options.agentPreviewByName.get(feedbackRow.agentName) || null;
69
+ const { messages } = resolveFeedbackThreadMessages(feedbackRow.chatThread);
70
+
71
+ for (const [messageIndex, message] of messages.entries()) {
72
+ for (const attachment of resolveMessageAttachments(message)) {
73
+ appendReference(attachment.url, {
74
+ source: 'chat-feedback',
75
+ feedbackId: feedbackRow.id,
76
+ messageIndex,
77
+ messageId: normalizeOptionalText((message as { id?: unknown }).id),
78
+ attachmentName: attachment.name,
79
+ sender: (message as { sender?: unknown }).sender ?? null,
80
+ agent: agentPreview,
81
+ });
82
+ }
83
+ }
84
+ }
85
+
86
+ return attachmentReferencesByUrl;
87
+ }
88
+
89
+ /**
90
+ * Resolves attachment references for the provided URLs.
91
+ *
92
+ * @param attachmentReferencesByUrl - Attachment reference map keyed by URL.
93
+ * @param candidateUrls - Possible URLs for one backed-up file.
94
+ * @returns Flattened reference list.
95
+ *
96
+ * @private function of `createServerBackupZipStream`
97
+ */
98
+ export function resolveAttachmentReferencesForUrls(
99
+ attachmentReferencesByUrl: ReadonlyMap<string, ReadonlyArray<BackupAttachmentReference>>,
100
+ candidateUrls: ReadonlyArray<string | null>,
101
+ ): Array<BackupAttachmentReference> {
102
+ const references: Array<BackupAttachmentReference> = [];
103
+
104
+ for (const candidateUrl of candidateUrls) {
105
+ const normalizedUrl = normalizeOptionalText(candidateUrl);
106
+ if (!normalizedUrl) {
107
+ continue;
108
+ }
109
+
110
+ const matches = attachmentReferencesByUrl.get(normalizedUrl) || [];
111
+ references.push(...matches);
112
+ }
113
+
114
+ return references;
115
+ }
116
+
117
+ /**
118
+ * Extracts normalized attachment previews from one serialized chat message.
119
+ *
120
+ * @param message - One serialized chat message candidate.
121
+ * @returns Normalized attachment previews with required URLs.
122
+ *
123
+ * @private function of `createServerBackupZipStream`
124
+ */
125
+ function resolveMessageAttachments(message: Record<string, unknown>): Array<{
126
+ name: string | null;
127
+ url: string;
128
+ }> {
129
+ const rawAttachments = message.attachments;
130
+ if (!Array.isArray(rawAttachments)) {
131
+ return [];
132
+ }
133
+
134
+ return rawAttachments.flatMap((attachment) => {
135
+ if (!attachment || typeof attachment !== 'object' || Array.isArray(attachment)) {
136
+ return [];
137
+ }
138
+
139
+ const url = normalizeOptionalText((attachment as { url?: unknown }).url);
140
+ if (!url) {
141
+ return [];
142
+ }
143
+
144
+ return [
145
+ {
146
+ name: normalizeOptionalText((attachment as { name?: unknown }).name),
147
+ url,
148
+ },
149
+ ];
150
+ });
151
+ }
@@ -0,0 +1,167 @@
1
+ import { $getTableName } from '../../../database/$getTableName';
2
+ import { $provideSupabaseForServer } from '../../../database/$provideSupabaseForServer';
3
+ import type { AgentsServerDatabase } from '../../../database/schema';
4
+ import { sortBackupRows } from './serverBackupRowUtilities';
5
+ import type { AgentRow, ChatFeedbackRow, UserChatRow, UserRow } from './serverBackupTypes';
6
+
7
+ /**
8
+ * JSON payload persisted for legacy table-backed exports.
9
+ *
10
+ * @private type of `createServerBackupZipStream`
11
+ */
12
+ export type BackupTableFilePayload = {
13
+ /**
14
+ * Logical entity name from the generated schema types.
15
+ */
16
+ readonly entity: keyof AgentsServerDatabase['public']['Tables'];
17
+ /**
18
+ * Physical table name used by the current server installation.
19
+ */
20
+ readonly databaseTable: string;
21
+ /**
22
+ * Total number of exported rows.
23
+ */
24
+ readonly rowCount: number;
25
+ /**
26
+ * Ordered snapshot rows.
27
+ */
28
+ readonly rows: ReadonlyArray<Record<string, unknown>>;
29
+ };
30
+
31
+ /**
32
+ * Shared context with lazy-loaded table snapshots reused across multiple sections.
33
+ *
34
+ * @private type of `createServerBackupZipStream`
35
+ */
36
+ export type ServerBackupContext = {
37
+ readonly supabase: ReturnType<typeof $provideSupabaseForServer>;
38
+ userRowsPromise?: Promise<Array<UserRow>>;
39
+ agentRowsPromise?: Promise<Array<AgentRow>>;
40
+ userChatRowsPromise?: Promise<Array<UserChatRow>>;
41
+ chatFeedbackRowsPromise?: Promise<Array<ChatFeedbackRow>>;
42
+ };
43
+
44
+ /**
45
+ * Creates the shared backup context with lazy table loaders.
46
+ *
47
+ * @param supabase - Server-side Supabase client.
48
+ * @returns Reusable backup context.
49
+ *
50
+ * @private function of `createServerBackupZipStream`
51
+ */
52
+ export function createServerBackupContext(
53
+ supabase: ReturnType<typeof $provideSupabaseForServer>,
54
+ ): ServerBackupContext {
55
+ return {
56
+ supabase,
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Reads one logical table and converts it into a deterministic JSON backup payload.
62
+ *
63
+ * @param supabase - Server-side Supabase client.
64
+ * @param tableKey - Logical table key from the generated schema.
65
+ * @returns Serializable table payload for the archive.
66
+ *
67
+ * @private function of `createServerBackupZipStream`
68
+ */
69
+ export async function loadBackupTableFilePayload(
70
+ supabase: ReturnType<typeof $provideSupabaseForServer>,
71
+ tableKey: keyof AgentsServerDatabase['public']['Tables'],
72
+ ): Promise<BackupTableFilePayload> {
73
+ const tableName = await $getTableName(tableKey);
74
+ const result = await supabase.from(tableName).select('*');
75
+
76
+ if (result.error) {
77
+ throw new Error(`Unable to load backup table ${tableKey}: ${result.error.message}`);
78
+ }
79
+
80
+ const rows = sortBackupRows(
81
+ (((result.data || []) as unknown as Array<Record<string, unknown>>).map((row) => ({ ...row }))),
82
+ );
83
+
84
+ return {
85
+ entity: tableKey,
86
+ databaseTable: tableName,
87
+ rowCount: rows.length,
88
+ rows,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Reads one logical table into typed rows.
94
+ *
95
+ * @param supabase - Server-side Supabase client.
96
+ * @param tableKey - Logical table key from the generated schema.
97
+ * @returns Ordered table rows.
98
+ *
99
+ * @private function of `createServerBackupZipStream`
100
+ */
101
+ export async function loadTableRows<TableKey extends keyof AgentsServerDatabase['public']['Tables']>(
102
+ supabase: ReturnType<typeof $provideSupabaseForServer>,
103
+ tableKey: TableKey,
104
+ ): Promise<Array<AgentsServerDatabase['public']['Tables'][TableKey]['Row']>> {
105
+ const tableName = await $getTableName(tableKey);
106
+ const result = await supabase.from(tableName).select('*');
107
+
108
+ if (result.error) {
109
+ throw new Error(`Unable to load backup table ${String(tableKey)}: ${result.error.message}`);
110
+ }
111
+
112
+ return sortBackupRows((result.data || []) as unknown as Array<Record<string, unknown>>) as Array<
113
+ AgentsServerDatabase['public']['Tables'][TableKey]['Row']
114
+ >;
115
+ }
116
+
117
+ /**
118
+ * Lazily loads users once for the backup run.
119
+ *
120
+ * @param context - Shared backup context.
121
+ * @returns Cached user rows.
122
+ *
123
+ * @private function of `createServerBackupZipStream`
124
+ */
125
+ export function loadUserRows(context: ServerBackupContext): Promise<Array<UserRow>> {
126
+ context.userRowsPromise = context.userRowsPromise || loadTableRows(context.supabase, 'User');
127
+ return context.userRowsPromise;
128
+ }
129
+
130
+ /**
131
+ * Lazily loads agents once for the backup run.
132
+ *
133
+ * @param context - Shared backup context.
134
+ * @returns Cached agent rows.
135
+ *
136
+ * @private function of `createServerBackupZipStream`
137
+ */
138
+ export function loadAgentRows(context: ServerBackupContext): Promise<Array<AgentRow>> {
139
+ context.agentRowsPromise = context.agentRowsPromise || loadTableRows(context.supabase, 'Agent');
140
+ return context.agentRowsPromise;
141
+ }
142
+
143
+ /**
144
+ * Lazily loads user chats once for the backup run.
145
+ *
146
+ * @param context - Shared backup context.
147
+ * @returns Cached user-chat rows.
148
+ *
149
+ * @private function of `createServerBackupZipStream`
150
+ */
151
+ export function loadUserChatRows(context: ServerBackupContext): Promise<Array<UserChatRow>> {
152
+ context.userChatRowsPromise = context.userChatRowsPromise || loadTableRows(context.supabase, 'UserChat');
153
+ return context.userChatRowsPromise;
154
+ }
155
+
156
+ /**
157
+ * Lazily loads feedback rows once for the backup run.
158
+ *
159
+ * @param context - Shared backup context.
160
+ * @returns Cached feedback rows.
161
+ *
162
+ * @private function of `createServerBackupZipStream`
163
+ */
164
+ export function loadChatFeedbackRows(context: ServerBackupContext): Promise<Array<ChatFeedbackRow>> {
165
+ context.chatFeedbackRowsPromise = context.chatFeedbackRowsPromise || loadTableRows(context.supabase, 'ChatFeedback');
166
+ return context.chatFeedbackRowsPromise;
167
+ }
@@ -0,0 +1,161 @@
1
+ import { sanitizeBackupPathSegment } from '../sanitizeBackupPathSegment';
2
+ import { normalizeOptionalText } from './serverBackupRowUtilities';
3
+
4
+ /**
5
+ * JSON file extension used by the per-entity exports.
6
+ *
7
+ * @private constant of `createServerBackupZipStream`
8
+ */
9
+ const JSON_FILE_EXTENSION = '.json';
10
+
11
+ /**
12
+ * Resolves the preferred binary backup filename while keeping the original file extension when available.
13
+ *
14
+ * @param fileName - Stored filename/path.
15
+ * @param fallbackUrl - Public file URL used when the filename is empty.
16
+ * @param fallbackStem - Deterministic fallback stem.
17
+ * @returns Safe filename for the ZIP archive.
18
+ *
19
+ * @private function of `createServerBackupZipStream`
20
+ */
21
+ export function resolveBinaryBackupFilename(
22
+ fileName: string | null | undefined,
23
+ fallbackUrl: string | null | undefined,
24
+ fallbackStem: string,
25
+ ): string {
26
+ const rawBaseName =
27
+ resolvePathBasename(fileName, '') ||
28
+ resolvePathBasename(fallbackUrl, '') ||
29
+ fallbackStem;
30
+
31
+ return sanitizeBackupPathSegment(rawBaseName, fallbackStem);
32
+ }
33
+
34
+ /**
35
+ * Creates one human-readable backup filename stem from optional labels.
36
+ *
37
+ * @param labels - Preferred labels ordered by importance.
38
+ * @param fallbackStem - Deterministic fallback stem.
39
+ * @returns Safe filename stem without extension.
40
+ *
41
+ * @private function of `createServerBackupZipStream`
42
+ */
43
+ export function createBackupStem(
44
+ labels: ReadonlyArray<string | null | undefined>,
45
+ fallbackStem: string,
46
+ ): string {
47
+ const normalizedLabels = labels
48
+ .map((label) => normalizeOptionalText(label))
49
+ .filter((label): label is string => Boolean(label));
50
+
51
+ return sanitizeBackupPathSegment(
52
+ normalizedLabels.length > 0 ? normalizedLabels.join(' -- ') : fallbackStem,
53
+ fallbackStem,
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Ensures one filename ends with `.json`.
59
+ *
60
+ * @param filenameStem - Filename stem or full filename.
61
+ * @returns JSON filename.
62
+ *
63
+ * @private function of `createServerBackupZipStream`
64
+ */
65
+ export function ensureJsonFilename(filenameStem: string): string {
66
+ return filenameStem.endsWith(JSON_FILE_EXTENSION) ? filenameStem : `${filenameStem}${JSON_FILE_EXTENSION}`;
67
+ }
68
+
69
+ /**
70
+ * Creates the metadata sidecar filename for a JSON export file.
71
+ *
72
+ * @param jsonFilename - Main JSON filename.
73
+ * @returns Sidecar metadata filename.
74
+ *
75
+ * @private function of `createServerBackupZipStream`
76
+ */
77
+ export function createJsonMetadataFilename(jsonFilename: string): string {
78
+ return jsonFilename.endsWith(JSON_FILE_EXTENSION)
79
+ ? `${jsonFilename.slice(0, -JSON_FILE_EXTENSION.length)}.metadata.json`
80
+ : `${jsonFilename}.metadata.json`;
81
+ }
82
+
83
+ /**
84
+ * Creates the metadata sidecar filename for a binary export file.
85
+ *
86
+ * @param filename - Main binary filename.
87
+ * @returns Sidecar metadata filename.
88
+ *
89
+ * @private function of `createServerBackupZipStream`
90
+ */
91
+ export function createBinaryMetadataFilename(filename: string): string {
92
+ return `${filename}.metadata.json`;
93
+ }
94
+
95
+ /**
96
+ * Creates a unique filename inside one ZIP folder with deterministic suffixes.
97
+ *
98
+ * @param usedFilenames - Already allocated filenames in the folder.
99
+ * @param filename - Preferred filename candidate.
100
+ * @param suffixBase - Deterministic suffix base used on collisions.
101
+ * @returns Unique filename for the ZIP folder.
102
+ *
103
+ * @private function of `createServerBackupZipStream`
104
+ */
105
+ export function createUniqueBackupFilename(
106
+ usedFilenames: Set<string>,
107
+ filename: string,
108
+ suffixBase: string,
109
+ ): string {
110
+ if (!usedFilenames.has(filename)) {
111
+ usedFilenames.add(filename);
112
+ return filename;
113
+ }
114
+
115
+ const extensionIndex = filename.lastIndexOf('.');
116
+ const hasExtension = extensionIndex > 0;
117
+ const filenameBase = hasExtension ? filename.slice(0, extensionIndex) : filename;
118
+ const extension = hasExtension ? filename.slice(extensionIndex) : '';
119
+
120
+ for (let suffixIndex = 0; suffixIndex < Number.MAX_SAFE_INTEGER; suffixIndex += 1) {
121
+ const suffix = suffixIndex === 0 ? `--${suffixBase}` : `--${suffixBase}-${suffixIndex + 1}`;
122
+ const nextFilename = `${filenameBase}${suffix}${extension}`;
123
+
124
+ if (!usedFilenames.has(nextFilename)) {
125
+ usedFilenames.add(nextFilename);
126
+ return nextFilename;
127
+ }
128
+ }
129
+
130
+ throw new Error(`Unable to allocate unique backup filename for ${suffixBase}.`);
131
+ }
132
+
133
+ /**
134
+ * Resolves the basename from a stored path or URL.
135
+ *
136
+ * @param value - Stored path or URL.
137
+ * @param fallback - Fallback filename when no basename exists.
138
+ * @returns Basename without parent directories or query strings.
139
+ *
140
+ * @private function of `createServerBackupZipStream`
141
+ */
142
+ export function resolvePathBasename(value: string | null | undefined, fallback: string): string {
143
+ const normalizedValue = normalizeOptionalText(value);
144
+ if (!normalizedValue) {
145
+ return fallback;
146
+ }
147
+
148
+ try {
149
+ const url = new URL(normalizedValue);
150
+ const urlPathSegments = url.pathname.split('/').filter(Boolean);
151
+ const urlBaseName = urlPathSegments[urlPathSegments.length - 1];
152
+ if (urlBaseName) {
153
+ return decodeURIComponent(urlBaseName);
154
+ }
155
+ } catch {
156
+ // Continue with plain path parsing below.
157
+ }
158
+
159
+ const pathSegments = normalizedValue.split(/[\\/]/).filter(Boolean);
160
+ return pathSegments[pathSegments.length - 1] || fallback;
161
+ }