@promptbook/cli 0.112.0-103 → 0.112.0-105

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 (190) hide show
  1. package/apps/agents-server/src/app/AddAgentButton.tsx +0 -5
  2. package/apps/agents-server/src/app/actions.ts +50 -0
  3. package/apps/agents-server/src/app/admin/image-generator-test/ImageAttachmentsEditor.tsx +19 -3
  4. package/apps/agents-server/src/app/admin/limits/LimitsClient.tsx +11 -12
  5. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +34 -2
  6. package/apps/agents-server/src/app/admin/servers/CreateServerDialog.tsx +6 -1
  7. package/apps/agents-server/src/app/admin/servers/useCreateServerWizard.ts +13 -1
  8. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +11 -2
  9. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +14 -5
  10. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +7 -1
  11. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +11 -1
  12. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +6 -2
  13. package/apps/agents-server/src/app/api/health/route.ts +18 -0
  14. package/apps/agents-server/src/app/api/images/[filename]/route.ts +6 -2
  15. package/apps/agents-server/src/app/api/internal/agent-runner-limits/route.ts +51 -0
  16. package/apps/agents-server/src/app/api/upload/route.ts +48 -12
  17. package/apps/agents-server/src/app/layout.tsx +13 -0
  18. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +1 -4
  19. package/apps/agents-server/src/components/FileUploadAvailability/FileUploadAvailabilityContext.tsx +50 -0
  20. package/apps/agents-server/src/components/FileUploadAvailability/FileUploadUnavailableNotice.tsx +45 -0
  21. package/apps/agents-server/src/components/Header/Header.tsx +0 -11
  22. package/apps/agents-server/src/components/Header/useHeaderAgentMenus.tsx +0 -5
  23. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +85 -76
  24. package/apps/agents-server/src/components/NewAgentDialog/NewAgentDialog.tsx +7 -3
  25. package/apps/agents-server/src/components/NewAgentDialog/NewAgentWizardKnowledgeStep.tsx +6 -0
  26. package/apps/agents-server/src/components/NewAgentDialog/useNewAgentDialog.tsx +39 -16
  27. package/apps/agents-server/src/components/NewAgentDialog/useNewAgentWizardKnowledgeState.ts +8 -1
  28. package/apps/agents-server/src/constants/defaultAgentAvatarVisual.ts +1 -1
  29. package/apps/agents-server/src/constants/serverLimits.ts +22 -2
  30. package/apps/agents-server/src/database/migrations/2026-06-0200-default-agent-avatar-visual-octopus3d3.sql +16 -0
  31. package/apps/agents-server/src/database/seedDefaultAgents.ts +218 -0
  32. package/apps/agents-server/src/middleware.ts +2 -1
  33. package/apps/agents-server/src/tools/$provideCdnForServer.ts +114 -9
  34. package/apps/agents-server/src/utils/agentRouting/resolveAgentRouteTarget.ts +27 -4
  35. package/apps/agents-server/src/utils/defaultAgents/loadDefaultAgentBooks.ts +103 -0
  36. package/apps/agents-server/src/utils/knowledge/createInlineKnowledgeSourceUploader.ts +24 -5
  37. package/apps/agents-server/src/utils/serverLimits.ts +26 -1
  38. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerDefaultAgents.ts +1 -85
  39. package/apps/agents-server/src/utils/shareTargetPayloads.ts +20 -2
  40. package/apps/agents-server/src/utils/upload/fileUploadAvailability.ts +91 -0
  41. package/apps/agents-server/src/utils/upload/uploadFileToServer.ts +46 -2
  42. package/esm/apps/agents-server/src/constants/federatedAgentImport.d.ts +42 -0
  43. package/esm/apps/agents-server/src/constants/serverLimits.d.ts +207 -0
  44. package/esm/apps/agents-server/src/constants/toolUsageLimits.d.ts +55 -0
  45. package/esm/index.es.js +1109 -35
  46. package/esm/index.es.js.map +1 -1
  47. package/esm/scripts/run-agent-messages/main/AgentMessageFailureTracker.d.ts +27 -0
  48. package/esm/scripts/run-agent-messages/main/handleAgentWatchError.d.ts +4 -0
  49. package/esm/scripts/run-agent-messages/main/runAgentMessages.d.ts +1 -0
  50. package/esm/scripts/run-agent-messages/messages/moveAgentMessageToFailed.d.ts +17 -0
  51. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  52. package/esm/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
  53. package/esm/src/book-components/BookEditor/BookEditor.d.ts +5 -4
  54. package/esm/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
  55. package/esm/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
  56. package/esm/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
  57. package/esm/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
  58. package/esm/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +6 -0
  59. package/esm/src/version.d.ts +1 -1
  60. package/package.json +1 -1
  61. package/src/avatars/types/AvatarVisualDefinition.ts +1 -0
  62. package/src/avatars/visuals/avatarVisualRegistry.ts +2 -0
  63. package/src/avatars/visuals/octopus3d3AvatarVisual.ts +902 -0
  64. package/src/book-components/BookEditor/BookEditor.tsx +10 -7
  65. package/src/book-components/BookEditor/BookEditorMonaco.tsx +3 -1
  66. package/src/book-components/BookEditor/BookEditorTheme.ts +32 -0
  67. package/src/book-components/BookEditor/useBookEditorMonacoLanguage.ts +12 -15
  68. package/src/book-components/BookEditor/useBookEditorMonacoLifecycle.ts +1 -5
  69. package/src/book-components/BookEditor/useBookEditorMonacoStyles.ts +2 -1
  70. package/src/cli/cli-commands/agent-folder/agentProjectPaths.ts +7 -0
  71. package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +109 -9
  72. package/src/cli/cli-commands/agents-server/startAgentsServer.ts +132 -4
  73. package/src/other/templates/getTemplatesPipelineCollection.ts +690 -747
  74. package/src/utils/agents/resolveAgentAvatarImageUrl.ts +1 -1
  75. package/src/version.ts +2 -2
  76. package/src/versions.txt +2 -1
  77. package/umd/apps/agents-server/src/constants/federatedAgentImport.d.ts +42 -0
  78. package/umd/apps/agents-server/src/constants/serverLimits.d.ts +207 -0
  79. package/umd/apps/agents-server/src/constants/toolUsageLimits.d.ts +55 -0
  80. package/umd/index.umd.js +1109 -35
  81. package/umd/index.umd.js.map +1 -1
  82. package/umd/scripts/run-agent-messages/main/AgentMessageFailureTracker.d.ts +27 -0
  83. package/umd/scripts/run-agent-messages/main/handleAgentWatchError.d.ts +4 -0
  84. package/umd/scripts/run-agent-messages/main/runAgentMessages.d.ts +1 -0
  85. package/umd/scripts/run-agent-messages/messages/moveAgentMessageToFailed.d.ts +17 -0
  86. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  87. package/umd/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
  88. package/umd/src/book-components/BookEditor/BookEditor.d.ts +5 -4
  89. package/umd/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
  90. package/umd/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
  91. package/umd/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
  92. package/umd/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
  93. package/umd/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +6 -0
  94. package/umd/src/version.d.ts +1 -1
  95. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.test.ts.todo +0 -108
  96. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.test.ts.todo +0 -117
  97. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.test.ts.todo +0 -119
  98. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.test.ts.todo +0 -74
  99. package/apps/agents-server/tests/e2e/authentication-and-navigation.spec.ts.todo +0 -178
  100. package/src/_packages/browser.index.ts +0 -31
  101. package/src/_packages/browser.readme.md +0 -43
  102. package/src/book-2.0/agent-source/parseAgentSourceWithCommitments.test.ts.todo +0 -265
  103. package/src/book-components/BookEditor/BookEditorMonaco.test.tsx.todo +0 -115
  104. package/src/book-components/Chat/utils/renderMarkdown.test.ts.tmp +0 -199
  105. package/src/collection/agent-collection/constructors/agent-collection-in-directory/AgentCollectionInDirectory.test.ts.todo +0 -131
  106. package/src/commands/_common/parseCommand.test.ts.todo +0 -48
  107. package/src/commitments/META_LINK/META_LINK.test.ts.todo +0 -75
  108. package/src/conversion/validation/pipelineStringToJson-errors.test.ts.todo +0 -33
  109. package/src/dialogs/simple-prompt/SimplePromptInterfaceTools.ts +0 -51
  110. package/src/executables/browsers/locateSafari.test.ts.tmp +0 -15
  111. package/src/execution/PromptbookFetch.test-type.ts +0 -14
  112. package/src/execution/createPipelineExecutor/00-createPipelineExecutor.test.ts.todo +0 -0
  113. package/src/execution/execution-report/executionReportJsonToString.test.ts.todo +0 -83
  114. package/src/execution/utils/usageToHuman.test.ts.todo +0 -80
  115. package/src/llm-providers/_common/register/$provideLlmToolsForTestingAndScriptsAndPlayground.ts +0 -76
  116. package/src/llm-providers/_common/utils/assertUniqueModels.ts +0 -27
  117. package/src/llm-providers/_multiple/playground/playground.ts +0 -141
  118. package/src/llm-providers/_multiple/playground/tsconfig.json +0 -19
  119. package/src/llm-providers/agent/playground/playground.ts +0 -190
  120. package/src/llm-providers/agent/playground/tsconfig.json +0 -19
  121. package/src/llm-providers/anthropic-claude/playground/playground.ts +0 -99
  122. package/src/llm-providers/anthropic-claude/playground/tsconfig.json +0 -19
  123. package/src/llm-providers/azure-openai/playground/playground.ts +0 -101
  124. package/src/llm-providers/azure-openai/playground/tsconfig.json +0 -19
  125. package/src/llm-providers/ollama/playground/playground.ts +0 -120
  126. package/src/llm-providers/ollama/playground/tsconfig.json +0 -19
  127. package/src/llm-providers/openai/playground/playground.ts +0 -406
  128. package/src/llm-providers/openai/playground/tsconfig.json +0 -19
  129. package/src/llm-providers/remote/playground/playground.ts +0 -144
  130. package/src/llm-providers/remote/playground/tsconfig.json +0 -19
  131. package/src/llm-providers/vercel/playground/playground.ts +0 -133
  132. package/src/llm-providers/vercel/playground/tsconfig.json +0 -19
  133. package/src/personas/preparePersona.test.ts.todo +0 -126
  134. package/src/playground/backup/_playground-boilerplate.ts.txt +0 -37
  135. package/src/playground/backup/playground-agent-os.txt +0 -62
  136. package/src/playground/backup/playground-brj-app.ts.txt +0 -302
  137. package/src/playground/backup/playground-browser-playwright.txt +0 -110
  138. package/src/playground/backup/playground-claude-mcp.txt +0 -43
  139. package/src/playground/backup/playground-document-conversion.txt +0 -84
  140. package/src/playground/backup/playground-glob.ts.txt +0 -42
  141. package/src/playground/backup/playground-mcp-server.txt +0 -1
  142. package/src/playground/backup/playground-openai-agent-kit.txt +0 -73
  143. package/src/playground/backup/playground-openai-function-calling.txt +0 -131
  144. package/src/playground/backup/playground-openai-streaming.ts.txt +0 -68
  145. package/src/playground/backup/playground-scrape-knowledge.txt +0 -65
  146. package/src/playground/backup/playground-scraperFetch.ts.txt +0 -44
  147. package/src/playground/backup/playground-using-openai-compatible-route-on-agents-server.ts.txt +0 -49
  148. package/src/playground/backup/playground-write-pavolhejny-bio.txt +0 -120
  149. package/src/playground/permanent/_boilerplate.ts +0 -54
  150. package/src/playground/permanent/agent-with-browser-playground.ts +0 -92
  151. package/src/playground/permanent/error-handling-playground.ts +0 -103
  152. package/src/playground/playground.ts +0 -36
  153. package/src/playground/tsconfig.json +0 -19
  154. package/src/scrapers/_boilerplate/BoilerplateScraper.test.ts.todo +0 -73
  155. package/src/scrapers/_boilerplate/playground/boilerplate-scraper-playground.ts +0 -79
  156. package/src/scrapers/_boilerplate/playground/tsconfig.json +0 -19
  157. package/src/scrapers/_common/utils/files/blobToDataurl.test.ts.todo +0 -17
  158. package/src/scrapers/_common/utils/files/dataurlToBlob.test.ts.todo +0 -52
  159. package/src/scrapers/_common/utils/files/isValidDataurl.test.ts.todo +0 -42
  160. package/src/scrapers/_common/utils/files/shorten.test.ts.todo +0 -13
  161. package/src/scrapers/document/playground/document-scraper-playground.ts +0 -80
  162. package/src/scrapers/document/playground/tsconfig.json +0 -19
  163. package/src/scrapers/document-legacy/playground/legacy-document-scraper-playground.ts +0 -80
  164. package/src/scrapers/document-legacy/playground/tsconfig.json +0 -19
  165. package/src/scrapers/markdown/playground/markdown-scraper-playground.ts +0 -74
  166. package/src/scrapers/markdown/playground/tsconfig.json +0 -19
  167. package/src/scrapers/markitdown/MarkitdownScraper.test.ts.todo +0 -132
  168. package/src/scrapers/markitdown/playground/markitdown-scraper-playground.ts +0 -91
  169. package/src/scrapers/markitdown/playground/tsconfig.json +0 -19
  170. package/src/scrapers/pdf/PdfScraper.test.ts.todo +0 -52
  171. package/src/scrapers/pdf/playground/pdf-scraper-playground.ts +0 -75
  172. package/src/scrapers/pdf/playground/tsconfig.json +0 -19
  173. package/src/scrapers/website/playground/tsconfig.json +0 -19
  174. package/src/scrapers/website/playground/website-scraper-playground.ts +0 -82
  175. package/src/storage/_common/PromptbookStorage.test-type.ts +0 -14
  176. package/src/storage/local-storage/getIndexedDbStorage.ts +0 -36
  177. package/src/storage/local-storage/getLocalStorage.ts +0 -33
  178. package/src/storage/local-storage/getSessionStorage.ts +0 -33
  179. package/src/storage/local-storage/utils/IndexedDbStorageOptions.ts +0 -16
  180. package/src/storage/local-storage/utils/makePromptbookStorageFromIndexedDb.ts +0 -58
  181. package/src/storage/local-storage/utils/makePromptbookStorageFromWebStorage.ts +0 -45
  182. package/src/transpilers/formatted-book-in-markdown/FormattedBookInMarkdownTranspiler.test.ts.todo +0 -35
  183. package/src/transpilers/openai-sdk/playground/playground.ts +0 -85
  184. package/src/transpilers/openai-sdk/playground/tmp/chatbot-openaisdk-1.js +0 -194
  185. package/src/transpilers/openai-sdk/playground/tmp/package.json +0 -3
  186. package/src/transpilers/openai-sdk/playground/tsconfig.json +0 -18
  187. package/src/utils/editable/utils/findUsableParameters.test.ts.todo +0 -43
  188. package/src/utils/editable/utils/stringifyPipelineJson.test.ts.todo +0 -38
  189. package/src/utils/markdown/prettifyMarkdown.test.ts.tmp +0 -42
  190. package/src/utils/serialization/serializeToPromptbookJavascript.test.ts.todo +0 -116
@@ -1,6 +1,5 @@
1
1
  'use client';
2
2
 
3
- import { useRouter } from 'next/navigation';
4
3
  import { FileCard } from '../components/Homepage/FileCard';
5
4
  import { useAgentNaming } from '../components/AgentNaming/AgentNamingContext';
6
5
  import { showAlert } from '../components/AsyncDialogs/asyncDialogs';
@@ -21,15 +20,11 @@ type AddAgentButtonProps = {
21
20
  * Renders the add-agent card and creation dialog workflow.
22
21
  */
23
22
  export function AddAgentButton({ currentFolderId }: AddAgentButtonProps) {
24
- const router = useRouter();
25
23
  const { formatText } = useAgentNaming();
26
24
  const { t } = useServerLanguage();
27
25
  const addButtonLabel = formatText(t('agentCreation.addButtonLabel'));
28
26
 
29
27
  const { isPreparingDialog, openNewAgentDialog, dialog } = useNewAgentDialog({
30
- onCreated: ({ targetPath }) => {
31
- router.push(targetPath);
32
- },
33
28
  onCreateFailed: async (error) => {
34
29
  console.error('Failed to create agent:', error);
35
30
  await showAlert({
@@ -8,6 +8,9 @@ import { DEFAULT_NAME_POOL, NAME_POOL_METADATA_KEY, parseNamePool } from '../con
8
8
  import { NEW_AGENT_WIZZARD_METADATA_KEY, parseNewAgentWizardMode } from '../constants/newAgentWizard';
9
9
  import { getMetadata } from '../database/getMetadata';
10
10
  import { $provideAgentCollectionForServer } from '../tools/$provideAgentCollectionForServer';
11
+ import { invalidateCachedActiveOrganizationSnapshots } from '../utils/agentOrganization/loadAgentOrganizationState';
12
+ import { resolveAgentRouteTarget } from '../utils/agentRouting/resolveAgentRouteTarget';
13
+ import { buildAgentChatHref, buildAgentProfileHref } from '../utils/agentRouting/agentRouteHrefs';
11
14
  import { type AgentVisibility, parseAgentVisibility } from '../utils/agentVisibility';
12
15
  import { authenticateUser } from '../utils/authenticateUser';
13
16
  import { createAgentWithDefaultVisibility } from '../utils/createAgentWithDefaultVisibility';
@@ -15,6 +18,16 @@ import { resolveCurrentUserIdentity } from '../utils/currentUserIdentity';
15
18
  import { isUserAdmin } from '../utils/isUserAdmin';
16
19
  import { clearSession, setSession } from '../utils/session';
17
20
 
21
+ /**
22
+ * Maximum attempts used to confirm a freshly created agent route resolves before navigation starts.
23
+ */
24
+ const CREATED_AGENT_ROUTE_READY_ATTEMPTS = 20;
25
+
26
+ /**
27
+ * Delay between created-agent route-resolution retries.
28
+ */
29
+ const CREATED_AGENT_ROUTE_READY_DELAY_MS = 100;
30
+
18
31
  /**
19
32
  * Creates a new agent from the generated boilerplate template.
20
33
  *
@@ -34,6 +47,8 @@ export async function $createAgentAction(): Promise<{ agentName: string_agent_na
34
47
  const { agentName, permanentId } = await createAgentWithDefaultVisibility(collection, agentSource, {
35
48
  userId: currentUserIdentity?.userId,
36
49
  });
50
+ revalidateCreatedAgentPaths(permanentId);
51
+ await waitForCreatedAgentRoute(permanentId);
37
52
 
38
53
  return { agentName, permanentId };
39
54
  }
@@ -63,6 +78,39 @@ export async function $getNewAgentCreationSettingsAction(): Promise<{
63
78
  };
64
79
  }
65
80
 
81
+ /**
82
+ * Clears cached organization snapshots and route payloads after a new agent is created.
83
+ *
84
+ * @param permanentId - Canonical identifier of the newly created agent.
85
+ */
86
+ function revalidateCreatedAgentPaths(permanentId: string_agent_permanent_id): void {
87
+ invalidateCachedActiveOrganizationSnapshots();
88
+ revalidatePath('/', 'layout');
89
+ revalidatePath('/');
90
+ revalidatePath('/agents');
91
+ revalidatePath('/dashboard');
92
+ revalidatePath(buildAgentProfileHref(permanentId));
93
+ revalidatePath(buildAgentChatHref(permanentId));
94
+ }
95
+
96
+ /**
97
+ * Waits until the new agent can be resolved by the same routing helper the chat page uses.
98
+ *
99
+ * @param permanentId - Canonical identifier of the newly created agent.
100
+ */
101
+ async function waitForCreatedAgentRoute(permanentId: string_agent_permanent_id): Promise<void> {
102
+ for (let attempt = 0; attempt < CREATED_AGENT_ROUTE_READY_ATTEMPTS; attempt++) {
103
+ const routeTarget = await resolveAgentRouteTarget(permanentId, { forceRefresh: true });
104
+ if (routeTarget?.kind === 'local' && routeTarget.canonicalAgentId === permanentId) {
105
+ return;
106
+ }
107
+
108
+ await new Promise((resolve) => setTimeout(resolve, CREATED_AGENT_ROUTE_READY_DELAY_MS));
109
+ }
110
+
111
+ throw new Error(`Created agent "${permanentId}" could not be resolved for routing immediately after creation.`);
112
+ }
113
+
66
114
  /**
67
115
  * Creates a new agent using provided book content.
68
116
  *
@@ -89,6 +137,8 @@ export async function $createAgentFromBookAction(
89
137
  visibility: visibility ?? undefined,
90
138
  userId: currentUserIdentity?.userId,
91
139
  });
140
+ revalidateCreatedAgentPaths(permanentId);
141
+ await waitForCreatedAgentRoute(permanentId);
92
142
 
93
143
  return { agentName, permanentId };
94
144
  }
@@ -1,5 +1,7 @@
1
1
  import { useCallback, useRef, useState, type ChangeEvent } from 'react';
2
2
  import { showAlert } from '../../../components/AsyncDialogs/asyncDialogs';
3
+ import { FileUploadUnavailableNotice } from '../../../components/FileUploadAvailability/FileUploadUnavailableNotice';
4
+ import { useFileUploadAvailability } from '../../../components/FileUploadAvailability/FileUploadAvailabilityContext';
3
5
  import { getSafeCdnPath } from '../../../utils/cdn/utils/getSafeCdnPath';
4
6
  import { normalizeUploadFilename } from '../../../utils/normalization/normalizeUploadFilename';
5
7
  import { uploadFileToServer } from '../../../utils/upload/uploadFileToServer';
@@ -128,6 +130,7 @@ function CloseIcon({ size = 24, color = 'currentColor' }: IconProps) {
128
130
  */
129
131
  export function ImageAttachmentsEditor({ attachments, onChange, disabled }: ImageAttachmentsEditorProps) {
130
132
  const fileInputRef = useRef<HTMLInputElement>(null);
133
+ const fileUploadAvailability = useFileUploadAvailability();
131
134
  const [isUploading, setIsUploading] = useState(false);
132
135
 
133
136
  const handleFileUpload = useCallback(
@@ -138,6 +141,18 @@ export function ImageAttachmentsEditor({ attachments, onChange, disabled }: Imag
138
141
  return;
139
142
  }
140
143
 
144
+ if (!fileUploadAvailability.isUploadAvailable) {
145
+ await showAlert({
146
+ title: 'Upload unavailable',
147
+ message: fileUploadAvailability.message || 'File uploads are not available for this server.',
148
+ }).catch(() => undefined);
149
+
150
+ if (fileInputRef.current) {
151
+ fileInputRef.current.value = '';
152
+ }
153
+ return;
154
+ }
155
+
141
156
  setIsUploading(true);
142
157
 
143
158
  try {
@@ -158,7 +173,7 @@ export function ImageAttachmentsEditor({ attachments, onChange, disabled }: Imag
158
173
  }
159
174
  }
160
175
  },
161
- [attachments, onChange],
176
+ [attachments, fileUploadAvailability, onChange],
162
177
  );
163
178
 
164
179
  const handleRemoveAttachment = useCallback(
@@ -202,11 +217,11 @@ export function ImageAttachmentsEditor({ attachments, onChange, disabled }: Imag
202
217
  accept="image/*"
203
218
  multiple
204
219
  className="hidden"
205
- disabled={disabled || isUploading}
220
+ disabled={disabled || isUploading || !fileUploadAvailability.isUploadAvailable}
206
221
  />
207
222
  <button
208
223
  onClick={() => fileInputRef.current?.click()}
209
- disabled={disabled || isUploading}
224
+ disabled={disabled || isUploading || !fileUploadAvailability.isUploadAvailable}
210
225
  className="text-xs flex items-center gap-1 px-2 py-1 border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-50"
211
226
  type="button"
212
227
  >
@@ -214,6 +229,7 @@ export function ImageAttachmentsEditor({ attachments, onChange, disabled }: Imag
214
229
  {isUploading ? 'Uploading...' : 'Add Image'}
215
230
  </button>
216
231
  </div>
232
+ {!disabled && !fileUploadAvailability.isUploadAvailable && <FileUploadUnavailableNotice />}
217
233
  </div>
218
234
  );
219
235
  }
@@ -25,17 +25,14 @@ const LIMIT_INPUT_CLASS_NAME =
25
25
  *
26
26
  * @private client-side admin limits constant
27
27
  */
28
- const LIMIT_DEFINITIONS_BY_CATEGORY = SERVER_LIMIT_DEFINITIONS.reduce(
29
- (categories, definition) => {
30
- if (!categories[definition.category]) {
31
- categories[definition.category] = [];
32
- }
28
+ const LIMIT_DEFINITIONS_BY_CATEGORY = SERVER_LIMIT_DEFINITIONS.reduce((categories, definition) => {
29
+ if (!categories[definition.category]) {
30
+ categories[definition.category] = [];
31
+ }
33
32
 
34
- categories[definition.category]!.push(definition);
35
- return categories;
36
- },
37
- {} as Record<ServerLimitDefinition['category'], Array<ServerLimitDefinition>>,
38
- );
33
+ categories[definition.category]!.push(definition);
34
+ return categories;
35
+ }, {} as Record<ServerLimitDefinition['category'], Array<ServerLimitDefinition>>);
39
36
 
40
37
  /**
41
38
  * Dedicated admin page for configuring runtime server limits.
@@ -152,8 +149,8 @@ export function LimitsClient() {
152
149
  </p>
153
150
  <p>
154
151
  Missing dedicated rows automatically fall back to the legacy metadata values and then to the
155
- repository defaults, so existing installations keep their current behavior until limits are saved
156
- here.
152
+ repository defaults, so existing installations keep their current behavior until limits are
153
+ saved here.
157
154
  </p>
158
155
  </div>
159
156
  </Card>
@@ -172,6 +169,8 @@ export function LimitsClient() {
172
169
  'Limits used while retrying federated server agent-book imports.'}
173
170
  {category === 'Agent spawning' &&
174
171
  'Limits protecting the persistent `spawn_agent` tool from accidental or abusive overuse.'}
172
+ {category === 'Local agent runner' &&
173
+ 'Limits controlling local coding-agent retries for durable chat messages.'}
175
174
  </p>
176
175
  </div>
177
176
 
@@ -4,6 +4,7 @@ import { FileTextIcon, HashIcon, ImageIcon, ListIcon, ShieldIcon, ToggleLeftIcon
4
4
  import Link from 'next/link';
5
5
  import { Fragment, useEffect, useRef, useState } from 'react';
6
6
  import { showConfirm } from '../../../components/AsyncDialogs/asyncDialogs';
7
+ import { useFileUploadAvailability } from '../../../components/FileUploadAvailability/FileUploadAvailabilityContext';
7
8
  import { getMetadataDefinition, metadataDefaults, type MetadataDefinition } from '../../../database/metadataDefaults';
8
9
  import { getSafeCdnPath } from '../../../utils/cdn/utils/getSafeCdnPath';
9
10
  import { normalizeUploadFilename } from '../../../utils/normalization/normalizeUploadFilename';
@@ -99,6 +100,8 @@ type MetadataValueFieldProps = {
99
100
  value: string;
100
101
  onValueChange: (value: string) => void;
101
102
  isUploading: boolean;
103
+ isUploadAvailable: boolean;
104
+ uploadUnavailableMessage: string | null;
102
105
  fileInputRef: React.RefObject<HTMLInputElement | null>;
103
106
  onFileUpload: (event: React.ChangeEvent<HTMLInputElement>) => Promise<void> | void;
104
107
  fieldId: string;
@@ -117,6 +120,8 @@ const MetadataValueField = ({
117
120
  value,
118
121
  onValueChange,
119
122
  isUploading,
123
+ isUploadAvailable,
124
+ uploadUnavailableMessage,
120
125
  fileInputRef,
121
126
  onFileUpload,
122
127
  fieldId,
@@ -166,17 +171,27 @@ const MetadataValueField = ({
166
171
  className={METADATA_INPUT_CLASS_NAME}
167
172
  placeholder="Image URL..."
168
173
  />
169
- <input type="file" accept="image/*" className="hidden" ref={fileInputRef} onChange={onFileUpload} />
174
+ <input
175
+ type="file"
176
+ accept="image/*"
177
+ className="hidden"
178
+ ref={fileInputRef}
179
+ onChange={onFileUpload}
180
+ disabled={!isUploadAvailable}
181
+ />
170
182
  <button
171
183
  type="button"
172
184
  onClick={() => fileInputRef.current?.click()}
173
- disabled={isUploading}
185
+ disabled={isUploading || !isUploadAvailable}
174
186
  className="bg-gray-100 text-gray-700 px-3 py-2 rounded-md border border-gray-300 hover:bg-gray-200 flex items-center space-x-2 min-w-max"
175
187
  >
176
188
  <Upload className="w-4 h-4" />
177
189
  <span>{isUploading ? 'Uploading...' : 'Upload Image'}</span>
178
190
  </button>
179
191
  </div>
192
+ {!isUploadAvailable && uploadUnavailableMessage && (
193
+ <p className="text-sm text-amber-700">{uploadUnavailableMessage}</p>
194
+ )}
180
195
  {value && (
181
196
  <div className="mt-2 p-2 border border-gray-200 rounded-md bg-gray-50 inline-block">
182
197
  {/* eslint-disable-next-line @next/next/no-img-element */}
@@ -326,6 +341,7 @@ function mergeMetadataWithDefaults(data: MetadataEntry[]): MetadataEntry[] {
326
341
  * @private @@@
327
342
  */
328
343
  export function MetadataClient() {
344
+ const fileUploadAvailability = useFileUploadAvailability();
329
345
  const [metadata, setMetadata] = useState<MetadataEntry[]>([]);
330
346
  const [loading, setLoading] = useState(true);
331
347
  const [error, setError] = useState<string | null>(null);
@@ -499,6 +515,14 @@ export function MetadataClient() {
499
515
  const file = event.target.files?.[0];
500
516
  if (!file) return;
501
517
 
518
+ if (!fileUploadAvailability.isUploadAvailable) {
519
+ setError(fileUploadAvailability.message || 'File uploads are not available for this server.');
520
+ if (fileInputRef.current) {
521
+ fileInputRef.current.value = '';
522
+ }
523
+ return;
524
+ }
525
+
502
526
  try {
503
527
  setUploading(true);
504
528
 
@@ -599,6 +623,8 @@ export function MetadataClient() {
599
623
  value={addFormState.value}
600
624
  onValueChange={(value) => setAddFormState((prev) => ({ ...prev, value }))}
601
625
  isUploading={isAddUploading}
626
+ isUploadAvailable={fileUploadAvailability.isUploadAvailable}
627
+ uploadUnavailableMessage={fileUploadAvailability.message}
602
628
  fileInputRef={addFileInputRef}
603
629
  onFileUpload={handleAddFileUpload}
604
630
  />
@@ -767,6 +793,12 @@ export function MetadataClient() {
767
793
  setEditingFormState((prev) => ({ ...prev, value }))
768
794
  }
769
795
  isUploading={isEditUploading}
796
+ isUploadAvailable={
797
+ fileUploadAvailability.isUploadAvailable
798
+ }
799
+ uploadUnavailableMessage={
800
+ fileUploadAvailability.message
801
+ }
770
802
  fileInputRef={editFileInputRef}
771
803
  onFileUpload={handleEditFileUpload}
772
804
  />
@@ -2,6 +2,8 @@
2
2
 
3
3
  import { Loader2, Plus, Upload, X } from 'lucide-react';
4
4
  import type { ChangeEvent, RefObject } from 'react';
5
+ import { FileUploadUnavailableNotice } from '../../../components/FileUploadAvailability/FileUploadUnavailableNotice';
6
+ import { useFileUploadAvailability } from '../../../components/FileUploadAvailability/FileUploadAvailabilityContext';
5
7
  import { Dialog } from '../../../components/Portal/Dialog';
6
8
  import {
7
9
  type CreateServerWizardError,
@@ -125,6 +127,7 @@ function CreateServerForm(props: {
125
127
  readonly wizardState: CreateServerWizardState;
126
128
  }) {
127
129
  const { handleIconUpload, iconInputRef, isUploadingIcon, updateWizardField, wizardState } = props;
130
+ const fileUploadAvailability = useFileUploadAvailability();
128
131
 
129
132
  return (
130
133
  <div className="space-y-5">
@@ -175,17 +178,19 @@ function CreateServerForm(props: {
175
178
  accept="image/*"
176
179
  className="hidden"
177
180
  onChange={handleIconUpload}
181
+ disabled={!fileUploadAvailability.isUploadAvailable}
178
182
  />
179
183
  <button
180
184
  type="button"
181
185
  onClick={() => iconInputRef.current?.click()}
182
- disabled={isUploadingIcon}
186
+ disabled={isUploadingIcon || !fileUploadAvailability.isUploadAvailable}
183
187
  className={SECONDARY_BUTTON_CLASS_NAME}
184
188
  >
185
189
  {isUploadingIcon ? <Loader2 className="h-4 w-4 animate-spin" /> : <Upload className="h-4 w-4" />}
186
190
  Upload
187
191
  </button>
188
192
  </div>
193
+ {!fileUploadAvailability.isUploadAvailable && <FileUploadUnavailableNotice className="mt-3" />}
189
194
  {wizardState.iconUrl ? (
190
195
  <div className="mt-3 inline-flex rounded-lg border border-gray-200 bg-gray-50 p-3">
191
196
  {/* eslint-disable-next-line @next/next/no-img-element */}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useCallback, useMemo, useRef, useState, type ChangeEvent, type RefObject } from 'react';
4
4
  import { showAlert } from '../../../components/AsyncDialogs/asyncDialogs';
5
+ import { useFileUploadAvailability } from '../../../components/FileUploadAvailability/FileUploadAvailabilityContext';
5
6
  import { useDirtyModalGuard } from '../../../components/utils/useDirtyModalGuard';
6
7
  import { buildServerTablePrefix } from '../../../utils/buildServerTablePrefix';
7
8
  import type { ChatFeedbackMode } from '../../../utils/chatFeedbackMode';
@@ -358,6 +359,7 @@ function hasCreateServerWizardChanges(wizardState: CreateServerWizardState): boo
358
359
  */
359
360
  export function useCreateServerWizard(options: UseCreateServerWizardOptions): UseCreateServerWizardResult {
360
361
  const { onServerCreated } = options;
362
+ const fileUploadAvailability = useFileUploadAvailability();
361
363
  const [isDialogOpen, setIsDialogOpen] = useState(false);
362
364
  const [wizardState, setWizardState] = useState<CreateServerWizardState>(createInitialWizardState);
363
365
  const [isCreatingServer, setIsCreatingServer] = useState(false);
@@ -421,6 +423,16 @@ export function useCreateServerWizard(options: UseCreateServerWizardOptions): Us
421
423
  return;
422
424
  }
423
425
 
426
+ if (!fileUploadAvailability.isUploadAvailable) {
427
+ setWizardError({
428
+ message: fileUploadAvailability.message || 'File uploads are not available for this server.',
429
+ sqlDump: null,
430
+ sqlFilename: null,
431
+ });
432
+ event.target.value = '';
433
+ return;
434
+ }
435
+
424
436
  try {
425
437
  setIsUploadingIcon(true);
426
438
  setWizardError(null);
@@ -453,7 +465,7 @@ export function useCreateServerWizard(options: UseCreateServerWizardOptions): Us
453
465
  }
454
466
  }
455
467
  },
456
- [updateWizardField],
468
+ [fileUploadAvailability, updateWizardField],
457
469
  );
458
470
 
459
471
  const handleCreateServer = useCallback(async () => {
@@ -11,6 +11,8 @@ import { useAgentBackground } from '../../../components/AgentProfile/useAgentBac
11
11
  import { useChatEnterBehaviorPreferences } from '../../../components/ChatEnterBehavior/ChatEnterBehaviorPreferencesProvider';
12
12
  import { ChatErrorDialog } from '../../../components/ChatErrorDialog';
13
13
  import { useChatVisualMode } from '../../../components/ChatVisualMode/ChatVisualModeProvider';
14
+ import { FileUploadUnavailableNotice } from '../../../components/FileUploadAvailability/FileUploadUnavailableNotice';
15
+ import { useFileUploadAvailability } from '../../../components/FileUploadAvailability/FileUploadAvailabilityContext';
14
16
  import { usePrivateModePreferences } from '../../../components/PrivateModePreferences/PrivateModePreferencesProvider';
15
17
  import { useSelfLearningPreferences } from '../../../components/SelfLearningPreferences/SelfLearningPreferencesProvider';
16
18
  import { useServerLanguage } from '../../../components/ServerLanguage/ServerLanguageProvider';
@@ -110,6 +112,7 @@ export function AgentChatWrapper(props: AgentChatWrapperProps) {
110
112
 
111
113
  const shouldEnableFeedback = isChatFeedbackEnabled(feedbackMode);
112
114
  const { backgroundImage, brandColorHex, brandColorLightHex, brandColorDarkHex } = useAgentBackground(brandColor);
115
+ const fileUploadAvailability = useFileUploadAvailability();
113
116
  const allowFileAttachments = areFileAttachmentsEnabled ?? true;
114
117
 
115
118
  const chatBackgroundStyle: CSSProperties & Record<string, string> = {
@@ -427,7 +430,9 @@ export function AgentChatWrapper(props: AgentChatWrapperProps) {
427
430
  }}
428
431
  feedbackMode={toChatComponentFeedbackMode(feedbackMode)}
429
432
  onFeedback={shouldEnableFeedback ? handleFeedback : undefined}
430
- onFileUpload={allowFileAttachments ? handleFileUpload : undefined}
433
+ onFileUpload={
434
+ allowFileAttachments && fileUploadAvailability.isUploadAvailable ? handleFileUpload : undefined
435
+ }
431
436
  onError={handleError}
432
437
  defaultMessage={defaultMessage}
433
438
  autoExecuteMessage={effectiveAutoExecuteMessage}
@@ -453,7 +458,11 @@ export function AgentChatWrapper(props: AgentChatWrapperProps) {
453
458
  onReset={onStartNewChat}
454
459
  resetMode={onStartNewChat ? 'delegate' : undefined}
455
460
  teamAgentProfiles={teamAgentProfiles}
456
- />
461
+ >
462
+ {allowFileAttachments && !fileUploadAvailability.isUploadAvailable && (
463
+ <FileUploadUnavailableNotice className="mx-4 mt-4" />
464
+ )}
465
+ </AgentChat>
457
466
  );
458
467
 
459
468
  return (
@@ -4,7 +4,6 @@ import { usePromise } from '@common/hooks/usePromise';
4
4
  import { Chat } from '@promptbook-local/components';
5
5
  import { RemoteAgent } from '@promptbook-local/core';
6
6
  import { string_book, type ChatMessage } from '@promptbook-local/types';
7
- import { useRouter } from 'next/navigation';
8
7
  import { useCallback, useMemo, useState, type MouseEvent as ReactMouseEvent } from 'react';
9
8
  import { spaceTrim } from 'spacetrim';
10
9
  import { string_agent_url, string_color } from '../../../../../../src/types/typeAliases';
@@ -17,6 +16,8 @@ import { showAlert } from '../../../components/AsyncDialogs/asyncDialogs';
17
16
  import { useChatEnterBehaviorPreferences } from '../../../components/ChatEnterBehavior/ChatEnterBehaviorPreferencesProvider';
18
17
  import { useChatVisualMode } from '../../../components/ChatVisualMode/ChatVisualModeProvider';
19
18
  import { DeletedAgentBanner } from '../../../components/DeletedAgentBanner';
19
+ import { FileUploadUnavailableNotice } from '../../../components/FileUploadAvailability/FileUploadUnavailableNotice';
20
+ import { useFileUploadAvailability } from '../../../components/FileUploadAvailability/FileUploadAvailabilityContext';
20
21
  import { createMyChatsMobileMenuItem } from '../../../components/Header/createMyChatsMobileMenuItem';
21
22
  import { useHoistedMobileMenuItems } from '../../../components/Header/MobileMenuHoistingContext';
22
23
  import { usePrivateModePreferences } from '../../../components/PrivateModePreferences/PrivateModePreferencesProvider';
@@ -24,6 +25,7 @@ import { useServerLanguage } from '../../../components/ServerLanguage/ServerLang
24
25
  import { ChatThreadLoadingSkeleton } from '../../../components/Skeleton/ChatThreadLoadingSkeleton';
25
26
  import { usePromptbookTheme } from '../../../components/ThemeMode/usePromptbookTheme';
26
27
  import type { ServerLanguageCode } from '../../../languages/ServerLanguageRegistry';
28
+ import { buildFreshAgentChatHref } from '../../../utils/agentRouting/agentRouteHrefs';
27
29
  import { executeQuickActionButton } from '../../../utils/chat/executeQuickActionButton';
28
30
  import { resolveChatMessageValidationIssue } from '../../../utils/chat/validateChatMessageContent';
29
31
  import { createServerLanguageMoment } from '../../../utils/localization/createServerLanguageMoment';
@@ -277,13 +279,13 @@ export function AgentProfileChat({
277
279
  isHistoryEnabled = false,
278
280
  areFileAttachmentsEnabled = true,
279
281
  }: AgentProfileChatProps) {
280
- const router = useRouter();
281
282
  const [isCreatingAgent, setIsCreatingAgent] = useState(false);
282
283
  const [optimisticNavigationState, setOptimisticNavigationState] = useState<OptimisticChatNavigationState | null>(null);
283
284
  const { formatText } = useAgentNaming();
284
285
  const { language, t } = useServerLanguage();
285
286
  const { chatVisualMode } = useChatVisualMode();
286
287
  const { enterBehavior, resolveEnterBehavior } = useChatEnterBehaviorPreferences();
288
+ const fileUploadAvailability = useFileUploadAvailability();
287
289
  const { isPrivateModeEnabled } = usePrivateModePreferences();
288
290
  const { promptbookTheme } = usePromptbookTheme();
289
291
 
@@ -409,7 +411,7 @@ export function AgentProfileChat({
409
411
  try {
410
412
  const { permanentId } = await $createAgentFromBookAction(bookContent as string_book);
411
413
  if (permanentId) {
412
- router.push(`/agents/${permanentId}`);
414
+ window.location.assign(buildFreshAgentChatHref(permanentId));
413
415
  }
414
416
  } catch (error) {
415
417
  console.error('Failed to create agent:', error);
@@ -421,7 +423,7 @@ export function AgentProfileChat({
421
423
  setIsCreatingAgent(false);
422
424
  }
423
425
  },
424
- [formatText, router],
426
+ [formatText],
425
427
  );
426
428
  const handleFileUpload = useCallback(async (file: File) => chatFileUploadHandler(file), []);
427
429
  const fallbackInitialMessage = useMemo(() => {
@@ -485,6 +487,9 @@ export function AgentProfileChat({
485
487
  />
486
488
  )
487
489
  )}
490
+ {areFileAttachmentsEnabled && !fileUploadAvailability.isUploadAvailable && (
491
+ <FileUploadUnavailableNotice />
492
+ )}
488
493
  <div
489
494
  className={`relative w-full h-[calc(100dvh-300px)] min-h-[350px] md:min-h-[420px] md:h-[500px] agent-chat-route-surface ${
490
495
  isNavigatingToChat ? 'agent-chat-profile-transitioning' : ''
@@ -509,7 +514,11 @@ export function AgentProfileChat({
509
514
  onMessage={handleMessage}
510
515
  onActionButton={executeQuickActionButton}
511
516
  onCreateAgent={handleCreateAgent}
512
- onFileUpload={areFileAttachmentsEnabled ? handleFileUpload : undefined}
517
+ onFileUpload={
518
+ areFileAttachmentsEnabled && fileUploadAvailability.isUploadAvailable
519
+ ? handleFileUpload
520
+ : undefined
521
+ }
513
522
  isSaveButtonEnabled={false}
514
523
  isCopyButtonEnabled={false}
515
524
  className="h-full w-full rounded-[28px] bg-transparent"
@@ -3,6 +3,8 @@
3
3
  import { BookEditor } from '@promptbook-local/components';
4
4
  import type { string_book } from '@promptbook-local/types';
5
5
  import { bookEditorUploadHandler } from '../../../../utils/upload/createBookEditorUploadHandler';
6
+ import { FileUploadUnavailableNotice } from '../../../../components/FileUploadAvailability/FileUploadUnavailableNotice';
7
+ import { useFileUploadAvailability } from '../../../../components/FileUploadAvailability/FileUploadAvailabilityContext';
6
8
  import { SaveFailureNotice } from '../../../../components/SaveFailureNotice/SaveFailureNotice';
7
9
  import { usePromptbookTheme } from '../../../../components/ThemeMode/usePromptbookTheme';
8
10
  import { BookEditorHistoryPanel } from './BookEditorHistoryPanel';
@@ -24,6 +26,7 @@ type BookEditorWrapperProps = {
24
26
  */
25
27
  export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorWrapperProps) {
26
28
  const { promptbookTheme } = usePromptbookTheme();
29
+ const fileUploadAvailability = useFileUploadAvailability();
27
30
  const {
28
31
  agentSource,
29
32
  monacoModelPath,
@@ -48,6 +51,7 @@ export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorW
48
51
  onRetry={retrySaveNow}
49
52
  />
50
53
  )}
54
+ {!fileUploadAvailability.isUploadAvailable && <FileUploadUnavailableNotice className="mb-3" />}
51
55
 
52
56
  <div className="flex min-h-0 flex-1 gap-4">
53
57
  <div className="flex min-h-0 min-w-0 flex-1 gap-6">
@@ -59,7 +63,9 @@ export function BookEditorWrapper({ agentName, initialAgentSource }: BookEditorW
59
63
  value={agentSource}
60
64
  monacoModelPath={monacoModelPath}
61
65
  onChange={handleChange}
62
- onFileUpload={bookEditorUploadHandler}
66
+ onFileUpload={
67
+ fileUploadAvailability.isUploadAvailable ? bookEditorUploadHandler : undefined
68
+ }
63
69
  diagnostics={diagnostics}
64
70
  hoistedMenuItems={hoistedMenuItems}
65
71
  theme={promptbookTheme}
@@ -8,6 +8,8 @@ import { useAgentBackground } from '../../../../components/AgentProfile/useAgent
8
8
  import { useChatEnterBehaviorPreferences } from '../../../../components/ChatEnterBehavior/ChatEnterBehaviorPreferencesProvider';
9
9
  import { notifyError } from '../../../../components/Notifications/notifications';
10
10
  import { useChatVisualMode } from '../../../../components/ChatVisualMode/ChatVisualModeProvider';
11
+ import { FileUploadUnavailableNotice } from '../../../../components/FileUploadAvailability/FileUploadUnavailableNotice';
12
+ import { useFileUploadAvailability } from '../../../../components/FileUploadAvailability/FileUploadAvailabilityContext';
11
13
  import { useServerLanguage } from '../../../../components/ServerLanguage/ServerLanguageProvider';
12
14
  import { ChatThreadLoadingSkeleton } from '../../../../components/Skeleton/ChatThreadLoadingSkeleton';
13
15
  import { useSoundSystem } from '../../../../components/SoundSystemProvider/SoundSystemProvider';
@@ -145,6 +147,7 @@ export function CanonicalAgentChatSurface({
145
147
  const cancellableJob = useMemo(() => resolveCancellableJob(activeJobs), [activeJobs]);
146
148
  const { chatVisualMode } = useChatVisualMode();
147
149
  const { enterBehavior, resolveEnterBehavior } = useChatEnterBehaviorPreferences();
150
+ const fileUploadAvailability = useFileUploadAvailability();
148
151
  const { soundSystem } = useSoundSystem();
149
152
  const { promptbookTheme } = usePromptbookTheme();
150
153
  const effectConfigs = useMemo(() => createDefaultChatEffects(), []);
@@ -240,7 +243,11 @@ export function CanonicalAgentChatSurface({
240
243
  newChatButtonHref={isReadOnly ? undefined : newChatButtonHref}
241
244
  feedbackMode={toChatComponentFeedbackMode(feedbackMode)}
242
245
  onFeedback={!isReadOnly && feedbackEnabled ? state.onFeedback : undefined}
243
- onFileUpload={!isReadOnly && areFileAttachmentsEnabled ? handleFileUpload : undefined}
246
+ onFileUpload={
247
+ !isReadOnly && areFileAttachmentsEnabled && fileUploadAvailability.isUploadAvailable
248
+ ? handleFileUpload
249
+ : undefined
250
+ }
244
251
  participants={participants}
245
252
  chatLocale={language}
246
253
  timingTranslations={translations.timingTranslations}
@@ -261,6 +268,9 @@ export function CanonicalAgentChatSurface({
261
268
  saveFormatHandlers={saveFormatHandlers}
262
269
  theme={promptbookTheme}
263
270
  >
271
+ {!isReadOnly && areFileAttachmentsEnabled && !fileUploadAvailability.isUploadAvailable && (
272
+ <FileUploadUnavailableNotice className="mx-4 mt-4" />
273
+ )}
264
274
  {isReadOnly && frozenChatBannerLabel && (
265
275
  <div className="mx-4 mt-4 rounded-2xl border border-amber-200 bg-amber-50/95 px-4 py-3 text-sm font-medium text-amber-900 shadow-sm dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100">
266
276
  {translateText('chat.frozenBannerLabel', { source: frozenChatBannerLabel })}
@@ -4,8 +4,9 @@ import {
4
4
  resolveDefaultAgentAvatarVisualId,
5
5
  } from '@/src/constants/defaultAgentAvatarVisual';
6
6
  import { $provideAgentCollectionForServer } from '@/src/tools/$provideAgentCollectionForServer';
7
- import { $provideCdnForServer } from '@/src/tools/$provideCdnForServer';
7
+ import { $provideCdnForServer, resolveCdnPublicUrlForServer } from '@/src/tools/$provideCdnForServer';
8
8
  import { $provideExecutionToolsForServer } from '@/src/tools/$provideExecutionToolsForServer';
9
+ import { $provideServer } from '@/src/tools/$provideServer';
9
10
  import { $provideAgentReferenceResolver } from '@/src/utils/agentReferenceResolver/$provideAgentReferenceResolver';
10
11
  import { resolveServerAgentContext } from '@/src/utils/resolveServerAgentContext';
11
12
  import { computeHash, serializeError } from '@promptbook-local/utils';
@@ -71,6 +72,7 @@ async function renderGeneratedDefaultAvatar(
71
72
  agentProfile: Awaited<ReturnType<typeof resolveAgentProfileForDefaultAvatar>>,
72
73
  ) {
73
74
  const prompt = getAgentDefaultAvatarPrompt(agentProfile);
75
+ const providedServer = await $provideServer();
74
76
 
75
77
  // Use hash of the prompt as cache key - this ensures regeneration when prompt changes
76
78
  const promptHash = computeHash(prompt);
@@ -112,7 +114,9 @@ async function renderGeneratedDefaultAvatar(
112
114
 
113
115
  const imageBuffer = await imageResponse.arrayBuffer();
114
116
  const buffer = Buffer.from(imageBuffer);
115
- const cdn = $provideCdnForServer();
117
+ const cdn = $provideCdnForServer({
118
+ cdnPublicUrl: resolveCdnPublicUrlForServer(providedServer.publicUrl),
119
+ });
116
120
  const cdnKey = getGeneratedImageCdnKey({
117
121
  filename: internalFilename,
118
122
  pathPrefix: cdn.pathPrefix,
@@ -0,0 +1,18 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ /**
4
+ * Ensures the readiness route runs in the same runtime as the standalone server.
5
+ */
6
+ export const runtime = 'nodejs';
7
+
8
+ /**
9
+ * Prevents a cached response from hiding readiness failures during deployment handoff.
10
+ */
11
+ export const dynamic = 'force-dynamic';
12
+
13
+ /**
14
+ * Lightweight readiness endpoint used by standalone VPS pm2/nginx handoffs.
15
+ */
16
+ export async function GET() {
17
+ return NextResponse.json({ status: 'ok' });
18
+ }