nolo-cli 0.1.29 → 0.1.31

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 (329) hide show
  1. package/agent-runtime/agentRecordConfig.ts +0 -2
  2. package/agent-runtime/hostAdapter.ts +0 -1
  3. package/agent-runtime/hostRuntimeCapabilities.ts +7 -0
  4. package/agent-runtime/hostRuntimeConfig.ts +86 -0
  5. package/agent-runtime/index.ts +13 -0
  6. package/agent-runtime/localToolPolicy.ts +13 -6
  7. package/agent-runtime/localWorkspaceTools.ts +333 -30
  8. package/agent-runtime/platformChatProvider.ts +4 -1
  9. package/agent-runtime/runtimeToolPolicy.ts +32 -47
  10. package/agent-runtime/shellCommandPolicy.ts +117 -0
  11. package/agent-runtime/toolGate.ts +38 -0
  12. package/agentCreateCustomCommands.ts +9 -4
  13. package/agentDialogsCommands.ts +4 -3
  14. package/agentDoctorCommands.ts +3 -3
  15. package/agentListCommands.ts +3 -3
  16. package/agentMachineCommands.ts +6 -6
  17. package/agentPullCommand.ts +4 -12
  18. package/agentRecordCommands.ts +5 -3
  19. package/agentRecordHelpers.ts +42 -23
  20. package/agentRunCommand.ts +2 -6
  21. package/agentRunParsing.ts +18 -5
  22. package/agentRuntimeLocal.ts +10 -0
  23. package/agentUnpublishCommands.ts +3 -2
  24. package/ai/agent/agentRunDialogLineage.ts +56 -0
  25. package/ai/agent/agentSlice.ts +45 -10
  26. package/ai/agent/avatarUtils.ts +1 -1
  27. package/ai/agent/buildSystemPrompt.ts +1 -2
  28. package/ai/agent/cliExecutor.ts +330 -19
  29. package/ai/agent/createAgentSchema.ts +22 -0
  30. package/ai/agent/hooks/fetchPublicAgents.ts +14 -39
  31. package/ai/agent/hooks/usePublicAgents.ts +90 -192
  32. package/ai/agent/publicAgentCatalog.ts +359 -0
  33. package/ai/agent/publicAgentIdentity.ts +76 -0
  34. package/ai/agent/server/fetchPublicAgents.ts +21 -46
  35. package/ai/agent/serverOrigin.ts +7 -0
  36. package/ai/agent/streamAgentChatTurn.ts +23 -21
  37. package/ai/agent/streamAgentChatTurnUtils.ts +292 -6
  38. package/ai/agent/types.ts +2 -0
  39. package/ai/agent/web/agentDisplayUtils.ts +171 -0
  40. package/ai/agent/web/useAgentModelAvatarComponent.ts +32 -0
  41. package/ai/ai.locale.ts +8 -0
  42. package/ai/chat/sendOpenAICompletionsRequest.native.ts +15 -0
  43. package/ai/chat/sendOpenAICompletionsRequest.ts +93 -1
  44. package/ai/chat/sendOpenAIResponseRequest.ts +86 -9
  45. package/ai/chat/updateTotalUsage.ts +7 -0
  46. package/ai/llm/deepinfra.ts +30 -15
  47. package/ai/llm/mistral.ts +3 -4
  48. package/ai/llm/providers.ts +2 -0
  49. package/ai/llm/types.ts +15 -0
  50. package/ai/memory/capture.ts +3 -3
  51. package/ai/memory/consolidate.ts +2 -2
  52. package/ai/memory/delete.ts +4 -3
  53. package/ai/memory/query.ts +3 -3
  54. package/ai/memory/remember.ts +2 -2
  55. package/ai/memory/store.ts +3 -3
  56. package/ai/memory/storeShared.ts +3 -2
  57. package/ai/policy/commercialMultiplier.ts +16 -0
  58. package/ai/token/billingAdminAuditEvent.ts +68 -0
  59. package/ai/token/billingAdminAuditEventWriter.ts +41 -0
  60. package/ai/token/billingAnomaly.ts +90 -0
  61. package/ai/token/billingAnomalyLifecycle.ts +54 -0
  62. package/ai/token/billingAnomalyLifecycleWriter.ts +44 -0
  63. package/ai/token/billingAnomalyWriter.ts +41 -0
  64. package/ai/token/calculatePrice.ts +3 -2
  65. package/ai/token/deepinfraReconciliation.ts +153 -0
  66. package/ai/token/deepinfraRequestIdQuery.ts +92 -0
  67. package/ai/token/externalToolCost.ts +54 -41
  68. package/ai/token/missingUsageEstimate.ts +12 -3
  69. package/ai/token/normalizeUsage.ts +45 -5
  70. package/ai/token/openaiReconciliation.ts +188 -0
  71. package/ai/token/providerBillableEvent.ts +84 -0
  72. package/ai/token/providerBillableEventWriter.ts +36 -0
  73. package/ai/token/providerBillingAnomalyDrilldown.ts +308 -0
  74. package/ai/token/providerBillingDrilldown.ts +361 -0
  75. package/ai/token/providerBillingHealthReport.ts +366 -0
  76. package/ai/token/providerCall.ts +144 -0
  77. package/ai/token/providerCallSweeper.ts +168 -0
  78. package/ai/token/providerCallWriter.ts +33 -0
  79. package/ai/token/providerCredential.ts +89 -0
  80. package/ai/token/providerCredentialAnomaly.ts +154 -0
  81. package/ai/token/providerCredentialLifecycle.ts +59 -0
  82. package/ai/token/providerCredentialLifecycleWriter.ts +44 -0
  83. package/ai/token/providerCredentialRegistry.ts +83 -0
  84. package/ai/token/providerCredentialRegistryCommand.ts +97 -0
  85. package/ai/token/providerCredentialRegistryList.ts +124 -0
  86. package/ai/token/providerCredentialRegistryResolver.ts +150 -0
  87. package/ai/token/providerCredentialRegistryWriter.ts +41 -0
  88. package/ai/token/providerCredentialRotationCommand.ts +102 -0
  89. package/ai/token/providerCredentialRuntime.ts +56 -0
  90. package/ai/token/providerDispatchIntent.ts +67 -0
  91. package/ai/token/providerDispatchIntentWriter.ts +36 -0
  92. package/ai/token/providerGateway.ts +260 -0
  93. package/ai/token/providerReconciliation.ts +148 -0
  94. package/ai/token/providerReconciliationLocal.ts +97 -0
  95. package/ai/token/providerReconciliationRunner.ts +165 -0
  96. package/ai/token/providerReconciliationWriter.ts +41 -0
  97. package/ai/token/ratingResult.ts +202 -0
  98. package/ai/token/ratingResultWriter.ts +33 -0
  99. package/ai/token/serverDialogProjection.ts +5 -3
  100. package/ai/token/serverTokenWriter.ts +11 -3
  101. package/ai/token/types.ts +10 -0
  102. package/ai/tools/agent/callAgentTool.ts +3 -4
  103. package/ai/tools/agent/presets/appBuilderPreset.ts +5 -7
  104. package/ai/tools/agent/runStreamingAgentTool.ts +1 -2
  105. package/ai/tools/agent/streamParallelAgentsTool.ts +2 -3
  106. package/ai/tools/readXPostTool.ts +1 -5
  107. package/app/admin/adminPages.ts +9 -0
  108. package/app/i18n/i18n.config.ts +5 -0
  109. package/app/i18n/translations/pricing.locale.ts +397 -0
  110. package/app/utils/desktopAgentRuntimeStatusClient.ts +5 -4
  111. package/app/utils/desktopAgentRuntimeTurnClient.ts +4 -1
  112. package/app/utils/imageUtils.ts +64 -34
  113. package/auth/adminPermissions.ts +18 -0
  114. package/auth/authSlice.ts +26 -12
  115. package/auth/hooks/useFetchUsers.ts +3 -0
  116. package/auth/routes.ts +46 -0
  117. package/auth/server/accessActivity.ts +4 -3
  118. package/auth/server/adminPermissions.ts +43 -0
  119. package/auth/server/adminUsageReport.ts +375 -0
  120. package/auth/server/billingAnomalies.ts +235 -0
  121. package/auth/server/billingUsageReport.ts +41 -2
  122. package/auth/server/creatorEarningCreditConversion.ts +10 -2
  123. package/auth/server/deduct.ts +14 -7
  124. package/auth/server/delete.ts +10 -2
  125. package/auth/server/disable.ts +4 -3
  126. package/auth/server/emailAutomation.ts +7 -5
  127. package/auth/server/emailDbConfig.ts +4 -2
  128. package/auth/server/emailDelivery.ts +24 -20
  129. package/auth/server/emailDeliveryGuards.ts +10 -7
  130. package/auth/server/emailPreferences.ts +6 -4
  131. package/auth/server/emailReport.ts +6 -4
  132. package/auth/server/emailRuntimeConfig.ts +3 -3
  133. package/auth/server/emailScenarioNotifications.ts +9 -8
  134. package/auth/server/enable.ts +4 -3
  135. package/auth/server/getRechargeHistory.ts +3 -2
  136. package/auth/server/getUser.ts +3 -2
  137. package/auth/server/gptProAccess.ts +44 -0
  138. package/auth/server/inviteReward.ts +10 -7
  139. package/auth/server/listusers.ts +6 -4
  140. package/auth/server/login.ts +2 -2
  141. package/auth/server/productEntitlements.ts +7 -4
  142. package/auth/server/productPurchase.ts +10 -2
  143. package/auth/server/providerBillingDrilldown.ts +47 -0
  144. package/auth/server/providerBillingHealth.ts +71 -0
  145. package/auth/server/providerCredentials.ts +136 -0
  146. package/auth/server/recharge.ts +14 -8
  147. package/auth/server/revokeSessions.ts +4 -3
  148. package/auth/server/route.ts +77 -0
  149. package/auth/server/sendEmail.ts +5 -4
  150. package/auth/server/signup.ts +10 -3
  151. package/auth/server/token.ts +53 -55
  152. package/auth/server/tokenUsageBilling.ts +18 -7
  153. package/auth/server/usageManagementAccess.ts +43 -0
  154. package/auth/server/usageReport.ts +59 -0
  155. package/auth/types.ts +12 -0
  156. package/auth/utils.ts +24 -22
  157. package/authCommands.ts +36 -2
  158. package/buildPublish.ts +3 -1
  159. package/buildPublishArtifactAssembly.ts +62 -1
  160. package/buildPublishImportAnalysis.ts +21 -16
  161. package/chat/dialog/dialogLaunchScope.ts +32 -0
  162. package/chat/dialog/objectAssistantRegistry.ts +2 -2
  163. package/chat/dialog/useCreateDialog.ts +1 -1
  164. package/chat/hooks/useChatInput.ts +41 -10
  165. package/chat/messages/messageSlice.ts +5 -1
  166. package/chat/messages/pendingAttachmentParts.ts +28 -2
  167. package/chat/messages/sendFirstMessage.ts +76 -79
  168. package/chat/messages/types.ts +9 -0
  169. package/chat/messages/web/streamingMarkdownModel.ts +41 -0
  170. package/chat/messages/web/useStreamingReveal.ts +91 -0
  171. package/chat/web/messageInputAgentUi.ts +94 -6
  172. package/cliEnvHelpers.ts +68 -13
  173. package/cliRuntimeDoctorReport.ts +193 -0
  174. package/client/agentConfigResolver.test.ts +0 -2
  175. package/client/agentRun.test.ts +171 -10
  176. package/client/agentRun.ts +44 -637
  177. package/client/agentRunInput.ts +10 -0
  178. package/client/agentRunLocalExecutor.ts +179 -0
  179. package/client/agentRunServerTransport.ts +394 -0
  180. package/client/agentRunTaskRunRecorder.ts +176 -0
  181. package/client/localRuntimeAdapter.test.ts +236 -9
  182. package/client/localRuntimeAdapter.ts +221 -23
  183. package/client/localToolPolicy.test.ts +28 -4
  184. package/connector-experimental/daemonLauncher.ts +38 -4
  185. package/connectorRunArtifact.ts +34 -10
  186. package/connectorWebSocketTarget.ts +40 -15
  187. package/core/builtinAgents.ts +155 -11
  188. package/create/space/contentLabels.ts +1 -1
  189. package/create/space/contentTypeMeta.ts +3 -2
  190. package/create/space/sidebarVisibleTypes.ts +27 -0
  191. package/create/space/space.locale.ts +76 -4
  192. package/database/actions/agentReadResolution.ts +2 -8
  193. package/database/actions/read.ts +11 -9
  194. package/database/actions/readAndWait.ts +10 -5
  195. package/database/server/agentDelegation.ts +3 -2
  196. package/database/server/authorityStoreConformance.ts +209 -0
  197. package/database/server/authorityStoreTypes.ts +31 -0
  198. package/database/server/coreDataProxy.ts +1 -1
  199. package/database/server/dataHandlers.ts +13 -5
  200. package/database/server/db.ts +15 -34
  201. package/database/server/delete.ts +88 -71
  202. package/database/server/emailRepository.ts +11 -9
  203. package/database/server/fileService.ts +2 -1
  204. package/database/server/legacyServerDb.ts +58 -0
  205. package/database/server/levelAuthorityStore.ts +75 -0
  206. package/database/server/memoryAuthorityStore.ts +133 -0
  207. package/database/server/patch.ts +13 -12
  208. package/database/server/query.ts +3 -2
  209. package/database/server/read.ts +33 -30
  210. package/database/server/resourceAccess.ts +7 -5
  211. package/database/server/serverStoreFactory.ts +124 -0
  212. package/database/server/spaceMemberAuthority.ts +3 -2
  213. package/database/server/storeBatchCompat.ts +37 -0
  214. package/database/server/testAuthorityStore.ts +76 -0
  215. package/database/server/write.ts +29 -27
  216. package/dialogReadCommands.ts +2 -1
  217. package/dialogStatusCommands.ts +2 -1
  218. package/integrations/anthropic/anthropicModels.ts +12 -12
  219. package/integrations/deepseek/models.ts +67 -67
  220. package/integrations/google/models.ts +111 -53
  221. package/integrations/openai/models.ts +22 -22
  222. package/integrations/xai/models.ts +1 -1
  223. package/localRuntimeDb.ts +39 -2
  224. package/machineCommandPrompts.ts +2 -0
  225. package/machineCommands.ts +193 -62
  226. package/machineStatusCommands.ts +4 -12
  227. package/machineWsRunDispatch.ts +80 -15
  228. package/machineWsSession.ts +46 -2
  229. package/noloReadBridgePrompt.ts +16 -0
  230. package/package.json +114 -21
  231. package/pageCreateCommands.ts +2 -2
  232. package/pageDeleteCommands.ts +3 -3
  233. package/pageReadCommands.ts +3 -3
  234. package/pageUpdateCommands.ts +3 -3
  235. package/render/layout/mainLayoutViewMode.ts +25 -0
  236. package/render/page/server/createPage.ts +3 -1
  237. package/render/page/server/handleCreatePage.ts +2 -11
  238. package/runtimeDoctorCommands.ts +18 -69
  239. package/server/api/methods.ts +34 -12
  240. package/server/api/tableHandlers.ts +6 -3
  241. package/server/authenticateToken.ts +47 -2
  242. package/server/billingLedgerRoutes.ts +15 -0
  243. package/server/buildManifest.ts +54 -0
  244. package/server/coreReadiness.ts +34 -25
  245. package/server/coreRuntime.ts +15 -10
  246. package/server/handlers/activityStatsHandler.ts +5 -2
  247. package/server/handlers/agentEmailRegistrationE2EHandler.ts +25 -23
  248. package/server/handlers/agentRun/agentLookup.ts +1 -7
  249. package/server/handlers/agentRun/autonomousRuntime.ts +11 -10
  250. package/server/handlers/agentRun/builtinAgents.ts +12 -88
  251. package/server/handlers/agentRun/continueDialogContext.ts +15 -3
  252. package/server/handlers/agentRun/{inheritedDialogPrompt.ts → conversationReference.ts} +3 -3
  253. package/server/handlers/agentRun/dialogLookup.ts +4 -3
  254. package/server/handlers/agentRun/index.ts +864 -404
  255. package/server/handlers/agentRun/localRuntimeBootstrap.ts +17 -1
  256. package/server/handlers/agentRun/loop.ts +920 -508
  257. package/server/handlers/agentRun/machineConnector.ts +56 -8
  258. package/server/handlers/agentRun/machineRunAudit.ts +3 -2
  259. package/server/handlers/agentRun/modelUsageToolHandlers.ts +13 -10
  260. package/server/handlers/agentRun/requestContext.ts +12 -8
  261. package/server/handlers/agentRun/runtimeExecutionContext.ts +15 -3
  262. package/server/handlers/agentRun/runtimePreparation.ts +26 -24
  263. package/server/handlers/agentRun/runtimeRouting.ts +25 -40
  264. package/server/handlers/agentRun/runtimeSystemMessages.ts +65 -0
  265. package/server/handlers/agentRun/saveDialog.ts +97 -87
  266. package/server/handlers/agentRun/taskRunToolHandler.ts +9 -7
  267. package/server/handlers/agentRun/toolExecutor.ts +42 -78
  268. package/server/handlers/agentRun/types.ts +2 -1
  269. package/server/handlers/appDeployStatusStore.ts +17 -10
  270. package/server/handlers/appDomainHandler.ts +21 -15
  271. package/server/handlers/appLegacyInfraCleanup.ts +5 -2
  272. package/server/handlers/appSandboxHandler.ts +29 -8
  273. package/server/handlers/appSpaceAccess.ts +15 -8
  274. package/server/handlers/chatHandler.ts +22 -2
  275. package/server/handlers/chatUpstreamRetry.ts +225 -2
  276. package/server/handlers/connector/connectorConnectionRegistry.ts +55 -7
  277. package/server/handlers/connector/connectorWebSocket.ts +14 -10
  278. package/server/handlers/desktopAgentRuntimeAdapter.ts +4 -4
  279. package/server/handlers/desktopAgentRuntimeHostCapabilities.ts +25 -0
  280. package/server/handlers/desktopAgentRuntimeReadiness.ts +62 -0
  281. package/server/handlers/desktopAgentRuntimeStatusHandler.ts +38 -30
  282. package/server/handlers/desktopAgentRuntimeTurnHandler.ts +7 -3
  283. package/server/handlers/desktopAgentRuntimeTurnService.ts +39 -45
  284. package/server/handlers/desktopLlamaRuntimeHandler.ts +85 -51
  285. package/server/handlers/desktopUpdaterHandler.ts +132 -24
  286. package/server/handlers/favoriteHandlers.ts +8 -2
  287. package/server/handlers/getDialogBillingHandler.ts +4 -3
  288. package/server/handlers/getTransactionsHandler.ts +5 -2
  289. package/server/handlers/machines/machineBinding.ts +1 -0
  290. package/server/handlers/machines/machineHandlers.ts +0 -14
  291. package/server/handlers/machines/machineInstallAssets.ts +4 -2
  292. package/server/handlers/notificationEmitter.ts +5 -3
  293. package/server/handlers/notificationsHandler.ts +5 -2
  294. package/server/handlers/providerCallsRecentHandler.ts +234 -0
  295. package/server/handlers/providerReconciliationRunHandler.ts +180 -0
  296. package/server/handlers/providerRequestIdsHandler.ts +105 -0
  297. package/server/handlers/tableAccess.ts +6 -2
  298. package/server/handlers/tableToolHandlers.ts +28 -14
  299. package/server/handlers/userSecretsHandler.ts +17 -6
  300. package/server/handlers/versionHandler.ts +9 -4
  301. package/server/handlers/versionStore.ts +18 -10
  302. package/server/html/ssrPreload.ts +5 -3
  303. package/server/payments/crypto/cryptoUsdcBasePaymentProvider.ts +11 -8
  304. package/server/payments/crypto/cryptoUsdcBaseScanner.ts +12 -7
  305. package/server/payments/crypto/cryptoUsdtTronPaymentProvider.ts +11 -8
  306. package/server/payments/crypto/cryptoUsdtTronScanner.ts +9 -7
  307. package/server/payments/waffo/waffoPaymentProvider.ts +12 -9
  308. package/server/runtimeRole.ts +15 -0
  309. package/server/scheduler/scheduledTaskRuntime.ts +4 -2
  310. package/server/scheduler/schedulerService.ts +2 -2
  311. package/server/taskRunBridgeRoutes.ts +8 -5
  312. package/share/server/fetchCommunityShares.ts +1 -1
  313. package/share/server/helpers.ts +19 -17
  314. package/share/server/importShare.ts +9 -13
  315. package/share/server/listCommunityShares.ts +4 -4
  316. package/share/server/purchaseDoc.ts +8 -16
  317. package/share/server/readCommunityShareCover.ts +3 -2
  318. package/share/server/readSharedTable.ts +14 -13
  319. package/share/server/tableReplication.ts +42 -39
  320. package/spaceAttachCommands.ts +135 -0
  321. package/spaceCategoryCommands.ts +6 -6
  322. package/spaceCommands.ts +3 -3
  323. package/spaceDeleteCommands.ts +2 -2
  324. package/spaceInternalCommandEntries.ts +2 -0
  325. package/spaceReadCommands.ts +3 -3
  326. package/tableMetaCommands.ts +2 -2
  327. package/taskRunCommand.ts +11 -0
  328. package/ai/agent/runtimeServerBase.ts +0 -37
  329. package/server/handlers/desktopAgentRuntimeHostFacts.ts +0 -40
@@ -0,0 +1,56 @@
1
+ export type AgentRunDialogLineageInput = {
2
+ continueDialogId?: string | null;
3
+ inheritedFromDialogKey?: string | null;
4
+ parentDialogId?: string | null;
5
+ };
6
+
7
+ export type NormalizedAgentRunDialogLineage = {
8
+ continueDialogId?: string;
9
+ inheritedFromDialogKey?: string;
10
+ parentDialogId?: string;
11
+ hasAuthoritativeContinueDialog: boolean;
12
+ hasConflictingLineage: boolean;
13
+ };
14
+
15
+ function pickOptionalString(value: unknown) {
16
+ if (typeof value !== "string") return undefined;
17
+ const normalized = value.trim();
18
+ return normalized.length > 0 ? normalized : undefined;
19
+ }
20
+
21
+ export function hasAuthoritativeContinueDialogId(value: unknown) {
22
+ return pickOptionalString(value) !== undefined;
23
+ }
24
+
25
+ export function normalizeAgentRunDialogLineage(
26
+ input: AgentRunDialogLineageInput
27
+ ): NormalizedAgentRunDialogLineage {
28
+ const continueDialogId = pickOptionalString(input.continueDialogId);
29
+ const inheritedFromDialogKey = pickOptionalString(input.inheritedFromDialogKey);
30
+ const parentDialogId = pickOptionalString(input.parentDialogId);
31
+ const hasAuthoritativeContinueDialog = hasAuthoritativeContinueDialogId(continueDialogId);
32
+
33
+ return {
34
+ continueDialogId,
35
+ inheritedFromDialogKey: hasAuthoritativeContinueDialog ? undefined : inheritedFromDialogKey,
36
+ parentDialogId: hasAuthoritativeContinueDialog ? undefined : parentDialogId,
37
+ hasAuthoritativeContinueDialog,
38
+ hasConflictingLineage:
39
+ hasAuthoritativeContinueDialog && Boolean(inheritedFromDialogKey || parentDialogId),
40
+ };
41
+ }
42
+
43
+ export function buildAgentRunDialogLineageFields(
44
+ input: AgentRunDialogLineageInput
45
+ ): Pick<AgentRunDialogLineageInput, "continueDialogId" | "inheritedFromDialogKey" | "parentDialogId"> {
46
+ const normalized = normalizeAgentRunDialogLineage(input);
47
+ return {
48
+ ...(hasAuthoritativeContinueDialogId(normalized.continueDialogId)
49
+ ? { continueDialogId: normalized.continueDialogId }
50
+ : {}),
51
+ ...(normalized.inheritedFromDialogKey
52
+ ? { inheritedFromDialogKey: normalized.inheritedFromDialogKey }
53
+ : {}),
54
+ ...(normalized.parentDialogId ? { parentDialogId: normalized.parentDialogId } : {}),
55
+ };
56
+ }
@@ -79,6 +79,24 @@ const normalizeAgentReferences = (references: any[]): ReferenceItem[] => {
79
79
  }));
80
80
  };
81
81
 
82
+ const isLocalProviderUrl = (value: unknown) => {
83
+ const normalized = typeof value === "string" ? value.trim() : "";
84
+ if (!normalized) return false;
85
+ try {
86
+ const hostname = new URL(normalized).hostname.toLowerCase();
87
+ return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
88
+ } catch {
89
+ return false;
90
+ }
91
+ };
92
+
93
+ const shouldPersistMachineBinding = (args: {
94
+ apiSource?: string;
95
+ customProviderUrl?: unknown;
96
+ }) =>
97
+ args.apiSource === "cli" ||
98
+ (args.apiSource === "custom" && isLocalProviderUrl(args.customProviderUrl));
99
+
82
100
  /**
83
101
  * 创建场景:表单数据 -> 持久化数据(全量)
84
102
  */
@@ -104,7 +122,10 @@ const processAgentCreateForm = (formData: AgentFormData, userId: string) => {
104
122
  };
105
123
 
106
124
  delete result.machineId;
107
- if (formData.apiSource === "cli" && machineId) {
125
+ if (machineId && shouldPersistMachineBinding({
126
+ apiSource: formData.apiSource,
127
+ customProviderUrl: formData.customProviderUrl,
128
+ })) {
108
129
  const binding: AgentRuntimeBinding = {
109
130
  ...(result.runtimeBinding && typeof result.runtimeBinding === "object"
110
131
  ? result.runtimeBinding
@@ -165,14 +186,22 @@ const processAgentUpdateChanges = (
165
186
  }
166
187
  if ("machineId" in data) {
167
188
  const machineId = String((data as any).machineId ?? "").trim();
168
-
169
- // Determine effective apiSource: prefer data.apiSource, fall back to previousAgent.apiSource
189
+
190
+ // Determine effective runtime source: prefer form data, fall back to previous agent config
170
191
  const effectiveApiSource = data.apiSource ?? previousAgent?.apiSource;
171
-
192
+ const effectiveCustomProviderUrl =
193
+ data.customProviderUrl ?? previousAgent?.customProviderUrl;
194
+
172
195
  if (machineId) {
173
- // Persist runtimeBinding when effective apiSource is 'cli' or undefined
174
- // undefined = tool/legacy partial update without apiSource context → preserve backward compatibility
175
- if (effectiveApiSource === "cli" || effectiveApiSource === undefined) {
196
+ // Persist runtimeBinding for CLI agents and localhost custom providers.
197
+ // undefined apiSource keeps backward compatibility for legacy partial updates.
198
+ if (
199
+ effectiveApiSource === undefined ||
200
+ shouldPersistMachineBinding({
201
+ apiSource: effectiveApiSource,
202
+ customProviderUrl: effectiveCustomProviderUrl,
203
+ })
204
+ ) {
176
205
  const binding: AgentRuntimeBinding = {
177
206
  ...((changes.runtimeBinding && typeof changes.runtimeBinding === "object")
178
207
  ? changes.runtimeBinding
@@ -182,10 +211,16 @@ const processAgentUpdateChanges = (
182
211
  };
183
212
  changes.runtimeBinding = binding;
184
213
  }
185
- // If apiSource is explicitly non-cli and machineId is supplied, do not create binding
214
+ // If apiSource is explicitly incompatible with machine binding, do not create binding
186
215
  } else {
187
- // CLEAR branch: clear runtimeBinding when effectiveApiSource is 'cli' or undefined (backward-compatible)
188
- if (effectiveApiSource === "cli" || effectiveApiSource === undefined) {
216
+ // CLEAR branch: clear runtimeBinding when the effective runtime shape can be machine-bound.
217
+ if (
218
+ effectiveApiSource === undefined ||
219
+ shouldPersistMachineBinding({
220
+ apiSource: effectiveApiSource,
221
+ customProviderUrl: effectiveCustomProviderUrl,
222
+ })
223
+ ) {
189
224
  changes.runtimeBinding = null;
190
225
  }
191
226
  }
@@ -12,7 +12,7 @@ export function resolveAvatarUrl(
12
12
  avatarFileId: string | undefined | null,
13
13
  server: string | undefined | null
14
14
  ): string | null {
15
- if (!avatarFileId) return null;
15
+ if (!avatarFileId || avatarFileId === "briefcase") return null;
16
16
  if (avatarFileId.startsWith("http") || avatarFileId.startsWith("blob:")) {
17
17
  return avatarFileId;
18
18
  }
@@ -98,8 +98,7 @@ const AGENT_ORCHESTRATION_INSTRUCTIONS = `--- Agent 编排与协作 ---
98
98
  你所在的系统支持多个子 Agent 和工作流工具,请把自己视为"总协调者":
99
99
 
100
100
  1)子 Agent 协作
101
- - 如果目标 Agent 记录已经声明 delegation.serverBase / runtimeServerBase,工具会自动路由到对应 nolo server;你不需要重复填写 serverBase。
102
- - 如果用户明确给了另一个可访问的 server origin(例如 Windows 机器的 Cloudflare 域名),可以在工具参数里传 serverBase 覆盖自动路由;不要臆造地址,也不要把普通 localhost 当成远端机器。
101
+ - 如果用户明确给了另一个可访问的 server origin(例如 Windows 机器的 Cloudflare 域名),可以在工具参数里显式传 serverBase 把调用发到那台 server;不要臆造地址,也不要把普通 localhost 当成远端机器。
103
102
  - 当用户需要多视角分析或辩论时,你可以:
104
103
  - 先用 callAgent 依次询问多个 Agent 对同一问题的看法;
105
104
  - 在最后一条回复中,用你自己的话帮用户总结这些观点的异同,并给出综合结论。
@@ -14,8 +14,8 @@
14
14
  */
15
15
 
16
16
  import { exec, execSync, spawn } from "child_process";
17
- import { randomUUID } from "node:crypto";
18
- import { mkdtempSync, readFileSync, rmSync } from "node:fs";
17
+ import { createHash, randomUUID } from "node:crypto";
18
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
19
19
  import { join } from "node:path";
20
20
  import { tmpdir } from "node:os";
21
21
  import { resolveLaunchableCodexCommand } from "../../connector-experimental/codexBinary";
@@ -50,6 +50,25 @@ export interface CliExecuteResult {
50
50
  warnings?: string[];
51
51
  }
52
52
 
53
+ type AgyDebugArtifact = {
54
+ dir: string;
55
+ promptHash: string;
56
+ promptBytes: number;
57
+ writeResult: (args: {
58
+ startedAt: string;
59
+ finishedAt: string;
60
+ elapsedMs: number;
61
+ cwd: string;
62
+ timeoutMs: number;
63
+ args: string[];
64
+ stdout: string;
65
+ stderr: string;
66
+ exitCode: number | null;
67
+ outcome: "succeeded" | "timed_out" | "spawn_error" | "failed";
68
+ errorMessage?: string;
69
+ }) => void;
70
+ };
71
+
53
72
  type CliSessionMessage = {
54
73
  role: "user" | "assistant";
55
74
  content: string;
@@ -171,6 +190,8 @@ function supportsNativeSystemPrompt(provider: CliProvider): boolean {
171
190
  }
172
191
 
173
192
  function supportsModel(provider: CliProvider): boolean {
193
+ // Antigravity CLI does not expose a model selection flag.
194
+ // Codex is handled separately so explicit opt-in can still pass --model through.
174
195
  return provider !== "agy";
175
196
  }
176
197
 
@@ -178,6 +199,13 @@ function supportsReasoningEffort(provider: CliProvider): boolean {
178
199
  return provider === "claude" || provider === "copilot";
179
200
  }
180
201
 
202
+ function shouldPassCodexModel(options: CliExecuteOptions): boolean {
203
+ return (options.env?.NOLO_CODEX_CLI_ALLOW_MODEL ?? process.env.NOLO_CODEX_CLI_ALLOW_MODEL) === "1";
204
+ }
205
+ function shouldPassClaudeModel(options: CliExecuteOptions): boolean {
206
+ return (options.env?.NOLO_CLAUDE_CLI_ALLOW_MODEL ?? process.env.NOLO_CLAUDE_CLI_ALLOW_MODEL) === "1";
207
+ }
208
+
181
209
  function collectCliCapabilityWarnings(
182
210
  provider: CliProvider,
183
211
  options: CliExecuteOptions
@@ -189,7 +217,15 @@ function collectCliCapabilityWarnings(
189
217
  warnings.push(`${label} does not support reasoning_effort; ignored.`);
190
218
  }
191
219
 
192
- if (options.model !== undefined && !supportsModel(provider)) {
220
+ if (provider === "codex" && options.model !== undefined && !shouldPassCodexModel(options)) {
221
+ warnings.push(
222
+ "Codex CLI model selection is disabled by default because real Codex installs can reject --model with subscription/plan errors; set NOLO_CODEX_CLI_ALLOW_MODEL=1 to pass it through."
223
+ );
224
+ } else if (provider === "claude" && options.model !== undefined && !shouldPassClaudeModel(options)) {
225
+ warnings.push(
226
+ "Claude CLI model selection is disabled by default because current installs can reject --model; set NOLO_CLAUDE_CLI_ALLOW_MODEL=1 to pass it through."
227
+ );
228
+ } else if (options.model !== undefined && !supportsModel(provider)) {
193
229
  warnings.push(`${label} does not support model selection; ignored.`);
194
230
  }
195
231
 
@@ -218,16 +254,121 @@ function resolveCliExecution(
218
254
  options: CliExecuteOptions
219
255
  ): { prompt: string; options: CliExecuteOptions; warnings: string[] } {
220
256
  const warnings = collectCliCapabilityWarnings(provider, options);
221
- if (supportsNativeSystemPrompt(provider) || !options.systemPrompt?.trim()) {
222
- return { prompt, options, warnings };
257
+ const resolvedPrompt =
258
+ supportsNativeSystemPrompt(provider) || !options.systemPrompt?.trim()
259
+ ? prompt
260
+ : buildCliPrompt(options.systemPrompt, prompt);
261
+ const resolvedOptions =
262
+ supportsNativeSystemPrompt(provider) || !options.systemPrompt?.trim()
263
+ ? options
264
+ : {
265
+ ...options,
266
+ systemPrompt: undefined,
267
+ };
268
+ const agyPrompt = provider === "agy" ? compactAgyPrompt(resolvedPrompt) : null;
269
+ return {
270
+ prompt: agyPrompt?.prompt ?? resolvedPrompt,
271
+ options: resolvedOptions,
272
+ warnings: [...warnings, ...(agyPrompt?.warnings ?? [])],
273
+ };
274
+ }
275
+
276
+ const AGY_PRESERVED_PROMPT_BLOCK_HEADERS = [
277
+ "--- Nolo read-only CLI bridge ---",
278
+ "--- Nolo task-run tool bridge ---",
279
+ ] as const;
280
+ const AGY_OPTIONAL_TASKRUN_GUIDANCE_HEADERS = [
281
+ "## TaskRun tool guidance (2026-05-17)",
282
+ "## TaskRun tool strictness (2026-05-17)",
283
+ "## TaskRun delegation boundary (2026-05-17)",
284
+ ] as const;
285
+
286
+ function escapeRegExp(value: string) {
287
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
288
+ }
289
+
290
+ function extractPromptBlock(source: string, header: string) {
291
+ const pattern = new RegExp(
292
+ `(?:^|\\n)(${escapeRegExp(header)}\\n[\\s\\S]*?)(?=\\n{2,}--- [^\\n]+ ---|\\n{2,}User task:|\\n{2,}--- User task ---|$)`,
293
+ );
294
+ const match = source.match(pattern);
295
+ if (!match) return null;
296
+ return {
297
+ block: match[1].trim(),
298
+ remaining: source.replace(pattern, "\n\n"),
299
+ };
300
+ }
301
+
302
+ function stripMarkdownSection(source: string, header: string) {
303
+ const pattern = new RegExp(
304
+ `(?:^|\\n)${escapeRegExp(header)}\\n[\\s\\S]*?(?=\\n{2,}## [^\\n]+|\\n{2,}--- [^\\n]+ ---|\\n{2,}User task:|\\n{2,}--- User task ---|$)`,
305
+ );
306
+ return source.replace(pattern, "\n\n");
307
+ }
308
+
309
+ function compactAgyPrompt(prompt: string): { prompt: string; warnings: string[] } {
310
+ let next = prompt;
311
+ let changed = false;
312
+ const preservedBlocks: string[] = [];
313
+
314
+ const stripBlock = (pattern: RegExp) => {
315
+ const updated = next.replace(pattern, "\n\n");
316
+ if (updated !== next) {
317
+ next = updated;
318
+ changed = true;
319
+ }
320
+ };
321
+
322
+ for (const header of AGY_PRESERVED_PROMPT_BLOCK_HEADERS) {
323
+ const extracted = extractPromptBlock(next, header);
324
+ if (!extracted) continue;
325
+ next = extracted.remaining;
326
+ preservedBlocks.push(extracted.block);
327
+ changed = true;
328
+ }
329
+
330
+ // Antigravity intermittently stalls on long runtime metadata tails. The
331
+ // connector already enforces permissions and owns dialog context, so trim
332
+ // these blocks before handing the prompt to agy.
333
+ stripBlock(/(?:^|\n)Conversation Reference:\n[\s\S]*?(?=\n{2,}--- Machine permission policy ---|\n{2,}--- User task ---|\n{2,}User task:|$)/);
334
+ stripBlock(/(?:^|\n)--- Machine permission policy ---\n[\s\S]*?(?=\n{2,}--- User task ---|\n{2,}User task:|$)/);
335
+ if (!preservedBlocks.some((block) => block.startsWith("--- Nolo task-run tool bridge ---"))) {
336
+ for (const header of AGY_OPTIONAL_TASKRUN_GUIDANCE_HEADERS) {
337
+ const updated = stripMarkdownSection(next, header);
338
+ if (updated !== next) {
339
+ next = updated;
340
+ changed = true;
341
+ }
342
+ }
343
+ }
344
+
345
+ let normalized = next
346
+ .replace(/\n\n--- User task ---\n/g, "\n\nUser task:\n")
347
+ .replace(/\n{3,}/g, "\n\n")
348
+ .trim();
349
+ if (preservedBlocks.length > 0) {
350
+ const reinjected = preservedBlocks.join("\n\n");
351
+ if (/^User task:\n/.test(normalized)) {
352
+ normalized = `${reinjected}\n\n${normalized}`;
353
+ } else if (/\n\nUser task:\n/.test(normalized)) {
354
+ normalized = normalized.replace(/\n\nUser task:\n/, `\n\n${reinjected}\n\nUser task:\n`);
355
+ } else {
356
+ normalized = `${normalized}\n\n${reinjected}`.trim();
357
+ }
223
358
  }
359
+ if (normalized !== prompt) {
360
+ changed = true;
361
+ }
362
+
363
+ if (!changed) {
364
+ return { prompt, warnings: [] };
365
+ }
366
+
224
367
  return {
225
- prompt: buildCliPrompt(options.systemPrompt, prompt),
226
- options: {
227
- ...options,
228
- systemPrompt: undefined,
229
- },
230
- warnings,
368
+ prompt: normalized,
369
+ warnings: [
370
+ "Antigravity CLI prompt was compacted to avoid known timeout patterns in conversation-reference and machine-permission runtime blocks.",
371
+ ],
231
372
  };
232
373
  }
233
374
 
@@ -416,7 +557,9 @@ function executeCodex(
416
557
  "--cd",
417
558
  cwd,
418
559
  ];
419
- if (model) {
560
+ // Codex CLI accepts --model syntactically, but real desktop/subscription
561
+ // installs can fail with plan-gating errors. Keep default execution stable.
562
+ if (model && shouldPassCodexModel(options)) {
420
563
  args.push("--model");
421
564
  args.push(model);
422
565
  }
@@ -496,8 +639,8 @@ function executeClaude(
496
639
  } = options;
497
640
 
498
641
  return new Promise((resolve, reject) => {
499
- const args = ["-p", prompt];
500
- if (model) {
642
+ const args = ["--add-dir", cwd, "-p", prompt];
643
+ if (model && shouldPassClaudeModel(options)) {
501
644
  args.push("--model");
502
645
  args.push(model);
503
646
  }
@@ -564,9 +707,82 @@ function formatCliTimeout(timeoutMs: number) {
564
707
  return `${Math.max(1, Math.ceil(timeoutMs / 1000))}s`;
565
708
  }
566
709
 
567
- function executeAgy(
710
+ function shortHash(value: string) {
711
+ return createHash("sha256").update(value).digest("hex").slice(0, 12);
712
+ }
713
+
714
+ function resolveAgyDebugDir(env?: Record<string, string | undefined>) {
715
+ const raw = env?.NOLO_AGY_DEBUG_DIR ?? process.env.NOLO_AGY_DEBUG_DIR ?? "";
716
+ return raw.trim();
717
+ }
718
+
719
+ function createAgyDebugArtifact(args: {
720
+ prompt: string;
721
+ attempt: number;
722
+ timeoutMs: number;
723
+ cwd: string;
724
+ env?: Record<string, string | undefined>;
725
+ }): AgyDebugArtifact | null {
726
+ const debugRoot = resolveAgyDebugDir(args.env);
727
+ if (!debugRoot) return null;
728
+
729
+ const promptHash = shortHash(args.prompt);
730
+ const promptBytes = Buffer.byteLength(args.prompt, "utf8");
731
+ const dir = join(debugRoot, `${Date.now()}-agy-${promptHash}-attempt-${args.attempt + 1}`);
732
+ mkdirSync(dir, { recursive: true });
733
+ writeFileSync(join(dir, "prompt.txt"), args.prompt, "utf8");
734
+
735
+ return {
736
+ dir,
737
+ promptHash,
738
+ promptBytes,
739
+ writeResult: (result) => {
740
+ writeFileSync(join(dir, "stdout.txt"), result.stdout, "utf8");
741
+ writeFileSync(join(dir, "stderr.txt"), result.stderr, "utf8");
742
+ writeFileSync(
743
+ join(dir, "meta.json"),
744
+ `${JSON.stringify({
745
+ promptHash,
746
+ promptBytes,
747
+ attempt: args.attempt + 1,
748
+ startedAt: result.startedAt,
749
+ finishedAt: result.finishedAt,
750
+ elapsedMs: result.elapsedMs,
751
+ cwd: result.cwd,
752
+ timeoutMs: result.timeoutMs,
753
+ args: result.args,
754
+ exitCode: result.exitCode,
755
+ outcome: result.outcome,
756
+ errorMessage: result.errorMessage ?? null,
757
+ stdoutBytes: Buffer.byteLength(result.stdout, "utf8"),
758
+ stderrBytes: Buffer.byteLength(result.stderr, "utf8"),
759
+ }, null, 2)}\n`,
760
+ "utf8"
761
+ );
762
+ },
763
+ };
764
+ }
765
+
766
+ function isAgyTimeoutError(error: unknown) {
767
+ return error instanceof Error && /Antigravity CLI timed out after \d+ms/.test(error.message);
768
+ }
769
+
770
+ function resolveAgyTimeoutRetryCount(env?: Record<string, string | undefined>) {
771
+ const raw = env?.NOLO_AGY_TIMEOUT_RETRIES ?? process.env.NOLO_AGY_TIMEOUT_RETRIES ?? "2";
772
+ const parsed = Number.parseInt(raw, 10);
773
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 2;
774
+ }
775
+
776
+ const AGY_PROCESS_TIMEOUT_BUFFER_MS = 10_000;
777
+
778
+ function isAgyUpstreamTimeoutText(stdout: string) {
779
+ return /^Error:\s+timed out waiting for response\b/i.test(stdout.trim());
780
+ }
781
+
782
+ function executeAgyOnce(
568
783
  prompt: string,
569
- options: CliExecuteOptions
784
+ options: CliExecuteOptions,
785
+ attempt = 0
570
786
  ): Promise<CliExecuteResult> {
571
787
  const {
572
788
  timeout = 120_000,
@@ -576,6 +792,8 @@ function executeAgy(
576
792
 
577
793
  return new Promise((resolve, reject) => {
578
794
  const args = [
795
+ "--add-dir",
796
+ cwd,
579
797
  "--print",
580
798
  prompt,
581
799
  "--print-timeout",
@@ -586,6 +804,14 @@ function executeAgy(
586
804
  }
587
805
 
588
806
  const start = Date.now();
807
+ const startedAt = new Date(start).toISOString();
808
+ const debugArtifact = createAgyDebugArtifact({
809
+ prompt,
810
+ attempt,
811
+ timeoutMs: timeout,
812
+ cwd,
813
+ env: options.env,
814
+ });
589
815
  const proc = spawn("agy", args, {
590
816
  cwd,
591
817
  env: buildCliProcessEnv(options.env),
@@ -594,12 +820,40 @@ function executeAgy(
594
820
  let stdout = "";
595
821
  let stderr = "";
596
822
  let timer: ReturnType<typeof setTimeout> | undefined;
823
+ let timedOut = false;
824
+
825
+ const flushDebug = (result: {
826
+ exitCode: number | null;
827
+ outcome: "succeeded" | "timed_out" | "spawn_error" | "failed";
828
+ errorMessage?: string;
829
+ }) => {
830
+ debugArtifact?.writeResult({
831
+ startedAt,
832
+ finishedAt: new Date().toISOString(),
833
+ elapsedMs: Date.now() - start,
834
+ cwd,
835
+ timeoutMs: timeout,
836
+ args,
837
+ stdout,
838
+ stderr,
839
+ exitCode: result.exitCode,
840
+ outcome: result.outcome,
841
+ ...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
842
+ });
843
+ };
597
844
 
598
845
  if (timeout > 0) {
599
846
  timer = setTimeout(() => {
847
+ timedOut = true;
600
848
  proc.kill("SIGTERM");
601
- reject(new Error(`Antigravity CLI timed out after ${timeout}ms`));
602
- }, timeout + 1_000);
849
+ const error = new Error(`Antigravity CLI timed out after ${timeout}ms`);
850
+ flushDebug({
851
+ exitCode: null,
852
+ outcome: "timed_out",
853
+ errorMessage: error.message,
854
+ });
855
+ reject(error);
856
+ }, timeout + AGY_PROCESS_TIMEOUT_BUFFER_MS);
603
857
  }
604
858
 
605
859
  proc.stdout.on("data", (data: Buffer) => {
@@ -612,11 +866,32 @@ function executeAgy(
612
866
 
613
867
  proc.on("close", (code) => {
614
868
  if (timer) clearTimeout(timer);
869
+ if (timedOut) return;
615
870
  if (code !== 0 && code !== null) {
616
- reject(new Error(`Antigravity CLI exited with code ${code}\nstderr: ${stderr}`));
871
+ const error = new Error(`Antigravity CLI exited with code ${code}\nstderr: ${stderr}`);
872
+ flushDebug({
873
+ exitCode: code,
874
+ outcome: "failed",
875
+ errorMessage: error.message,
876
+ });
877
+ reject(error);
878
+ return;
879
+ }
880
+ if (isAgyUpstreamTimeoutText(stdout)) {
881
+ const error = new Error(`Antigravity CLI timed out after ${timeout}ms`);
882
+ flushDebug({
883
+ exitCode: code,
884
+ outcome: "timed_out",
885
+ errorMessage: error.message,
886
+ });
887
+ reject(error);
617
888
  return;
618
889
  }
619
890
 
891
+ flushDebug({
892
+ exitCode: code,
893
+ outcome: "succeeded",
894
+ });
620
895
  resolve({
621
896
  text: stdout.trim(),
622
897
  raw: stdout,
@@ -626,11 +901,47 @@ function executeAgy(
626
901
 
627
902
  proc.on("error", (err) => {
628
903
  if (timer) clearTimeout(timer);
904
+ flushDebug({
905
+ exitCode: null,
906
+ outcome: "spawn_error",
907
+ errorMessage: err instanceof Error ? err.message : String(err),
908
+ });
629
909
  reject(err);
630
910
  });
631
911
  });
632
912
  }
633
913
 
914
+ async function executeAgy(
915
+ prompt: string,
916
+ options: CliExecuteOptions
917
+ ): Promise<CliExecuteResult> {
918
+ const maxRetries = resolveAgyTimeoutRetryCount(options.env);
919
+ let attempt = 0;
920
+ let lastError: unknown;
921
+
922
+ while (attempt <= maxRetries) {
923
+ try {
924
+ const result = await executeAgyOnce(prompt, options, attempt);
925
+ if (attempt === 0) return result;
926
+ return {
927
+ ...result,
928
+ warnings: [
929
+ ...(result.warnings ?? []),
930
+ `Antigravity CLI succeeded after ${attempt} timeout retr${attempt === 1 ? "y" : "ies"}.`,
931
+ ],
932
+ };
933
+ } catch (error) {
934
+ lastError = error;
935
+ if (!isAgyTimeoutError(error) || attempt >= maxRetries) {
936
+ throw error;
937
+ }
938
+ attempt += 1;
939
+ }
940
+ }
941
+
942
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
943
+ }
944
+
634
945
  // ── 注册表(新增 CLI 工具时在这里加) ────────────────────────────────────────
635
946
 
636
947
  const EXECUTORS: Record<
@@ -35,6 +35,17 @@ const referenceItemSchema = z
35
35
  type: data.type === "page" ? "knowledge" : data.type,
36
36
  }));
37
37
 
38
+ const isLocalProviderUrl = (value?: string | null) => {
39
+ const normalized = typeof value === "string" ? value.trim() : "";
40
+ if (!normalized) return false;
41
+ try {
42
+ const hostname = new URL(normalized).hostname.toLowerCase();
43
+ return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
44
+ } catch {
45
+ return false;
46
+ }
47
+ };
48
+
38
49
  // --- 核心 schema ---
39
50
  export const getCreateAgentSchema = (t: TFunction) =>
40
51
  z
@@ -222,6 +233,17 @@ export const getCreateAgentSchema = (t: TFunction) =>
222
233
  message: t("validation.modelRequired"),
223
234
  path: ["model"],
224
235
  }
236
+ )
237
+ .refine(
238
+ (data) => {
239
+ if (!data.machineId?.trim()) return true;
240
+ if (data.apiSource === "cli") return true;
241
+ return data.apiSource === "custom" && isLocalProviderUrl(data.customProviderUrl);
242
+ },
243
+ {
244
+ message: t("validation.machineBindingRequiresCliOrLocalCustom"),
245
+ path: ["machineId"],
246
+ }
225
247
  );
226
248
 
227
249
  export type FormData = z.infer<ReturnType<typeof getCreateAgentSchema>>;