nolo-cli 0.1.13 → 0.1.14

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 (320) hide show
  1. package/README.md +9 -2
  2. package/agent-runtime/hostAdapter.ts +53 -0
  3. package/agent-runtime/index.ts +28 -0
  4. package/agent-runtime/localLoop.ts +62 -0
  5. package/agent-runtime/runtimeDecision.ts +70 -0
  6. package/agent-runtime/types.ts +87 -0
  7. package/agentRuntimeCommands.ts +139 -22
  8. package/agentRuntimeLocal.ts +7 -0
  9. package/ai/agent/_executeModel.ts +118 -0
  10. package/ai/agent/agentSlice.ts +544 -1
  11. package/ai/agent/appWorkingMemory.ts +126 -0
  12. package/ai/agent/avatarUtils.ts +24 -0
  13. package/ai/agent/buildEditingContext.ts +373 -0
  14. package/ai/agent/buildSystemPrompt.ts +532 -0
  15. package/ai/agent/cleanAgentMessages.ts +140 -0
  16. package/ai/agent/cliChatClient.ts +119 -0
  17. package/ai/agent/contextCompiler.ts +107 -0
  18. package/ai/agent/contextLayerContract.ts +44 -0
  19. package/ai/agent/createAgentSchema.ts +234 -0
  20. package/ai/agent/executeToolCall.ts +58 -0
  21. package/ai/agent/fetchAgentContexts.ts +42 -0
  22. package/ai/agent/generatePrompt.ts +3 -0
  23. package/ai/agent/getFullChatContextKeys.ts +168 -0
  24. package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
  25. package/ai/agent/hooks/useAgentConfig.ts +61 -0
  26. package/ai/agent/hooks/useAgentDialog.ts +35 -0
  27. package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
  28. package/ai/agent/hooks/usePublicAgents.ts +473 -0
  29. package/ai/agent/persistMessageWithFixedId.ts +37 -0
  30. package/ai/agent/planSlice.ts +259 -0
  31. package/ai/agent/referenceUtils.ts +229 -0
  32. package/ai/agent/runAgentBackground.ts +238 -0
  33. package/ai/agent/runAgentClientLoop.ts +138 -0
  34. package/ai/agent/runtimeGuidance.ts +97 -0
  35. package/ai/agent/runtimeServerBase.ts +37 -0
  36. package/ai/agent/server/fetchPublicAgents.ts +128 -0
  37. package/ai/agent/startParallelAgentStreams.ts +424 -0
  38. package/ai/agent/startupProtocol.ts +53 -0
  39. package/ai/agent/streamAgentChatTurn.ts +1299 -0
  40. package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
  41. package/ai/agent/types.ts +71 -0
  42. package/ai/agent/utils/imageOutput.ts +39 -0
  43. package/ai/agent/utils/publicImageAgentMode.ts +26 -0
  44. package/ai/agent/utils/sortUtils.ts +250 -0
  45. package/ai/agent/web/referencePickerUtils.ts +146 -0
  46. package/ai/ai.locale.ts +1083 -0
  47. package/ai/chat/accumulateToolCallChunks.ts +95 -0
  48. package/ai/chat/fetchUtils.native.ts +276 -0
  49. package/ai/chat/fetchUtils.ts +153 -0
  50. package/ai/chat/inlineImageUrlsForCustomProvider.ts +117 -0
  51. package/ai/chat/parseApiError.ts +64 -0
  52. package/ai/chat/parseMultilineSSE.ts +95 -0
  53. package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
  54. package/ai/chat/sendOpenAICompletionsRequest.ts +712 -0
  55. package/ai/chat/sendOpenAIResponseRequest.ts +512 -0
  56. package/ai/chat/shouldUseServerProxy.ts +18 -0
  57. package/ai/chat/sseClient.native.ts +91 -0
  58. package/ai/chat/sseClient.ts +67 -0
  59. package/ai/chat/streamReader.native.ts +31 -0
  60. package/ai/chat/streamReader.ts +62 -0
  61. package/ai/chat/updateTotalUsage.ts +72 -0
  62. package/ai/context/buildReferenceContext.ts +437 -0
  63. package/ai/context/calculateContextUsage.ts +133 -0
  64. package/ai/context/retention.ts +165 -0
  65. package/ai/context/tokenUtils.ts +78 -0
  66. package/ai/index.ts +1 -1
  67. package/ai/llm/agentCapabilities.ts +74 -0
  68. package/ai/llm/calculateGeminiImageTokens.ts +57 -0
  69. package/ai/llm/deepinfra.ts +28 -0
  70. package/ai/llm/fireworks.ts +68 -0
  71. package/ai/llm/generateRequestBody.ts +165 -0
  72. package/ai/llm/getModelContextWindow.ts +84 -0
  73. package/ai/llm/getNoloKey.ts +37 -0
  74. package/ai/llm/getPricing.ts +232 -0
  75. package/ai/llm/hooks/useModelPricing.ts +75 -0
  76. package/ai/llm/imagePricing.ts +66 -0
  77. package/ai/llm/isResponseAPIModel.ts +13 -0
  78. package/ai/llm/kimi.ts +18 -0
  79. package/ai/llm/mimo.ts +71 -0
  80. package/ai/llm/mistral.ts +22 -0
  81. package/ai/llm/modelAvatar.ts +427 -0
  82. package/ai/llm/models.ts +45 -0
  83. package/ai/llm/openrouterModels.ts +141 -0
  84. package/ai/llm/providers.ts +307 -0
  85. package/ai/llm/reasoningModels.ts +28 -0
  86. package/ai/llm/types.ts +59 -0
  87. package/ai/llm/usageRequestOptions.ts +59 -0
  88. package/ai/memory/capture.ts +148 -0
  89. package/ai/memory/consolidate.ts +104 -0
  90. package/ai/memory/delete.ts +147 -0
  91. package/ai/memory/overlay.ts +84 -0
  92. package/ai/memory/query.ts +38 -0
  93. package/ai/memory/queryShared.ts +160 -0
  94. package/ai/memory/rank.ts +105 -0
  95. package/ai/memory/recentRelationshipRecap.ts +247 -0
  96. package/ai/memory/remember.ts +167 -0
  97. package/ai/memory/runtime.ts +76 -0
  98. package/ai/memory/store.ts +20 -0
  99. package/ai/memory/storeShared.ts +76 -0
  100. package/ai/memory/types.ts +46 -0
  101. package/ai/memory/understanding.ts +349 -0
  102. package/ai/memory/understandingGreeting.ts +264 -0
  103. package/ai/messages/type.ts +20 -0
  104. package/ai/policy/personalizationDialog.ts +333 -0
  105. package/ai/policy/runtimePolicy.ts +440 -0
  106. package/ai/policy/selfUpdateFields.ts +48 -0
  107. package/ai/policy/types.ts +64 -0
  108. package/ai/skills/referenceRuntime.ts +274 -0
  109. package/ai/skills/skillDiagnostics.ts +251 -0
  110. package/ai/skills/skillDocBuilder.ts +139 -0
  111. package/ai/skills/skillDocProtocol.ts +434 -0
  112. package/ai/skills/skillReferenceSummary.ts +63 -0
  113. package/ai/skills/skillSummaryMarker.ts +26 -0
  114. package/ai/token/calculatePrice.ts +546 -0
  115. package/ai/token/db.ts +98 -0
  116. package/ai/token/externalToolCost.ts +321 -0
  117. package/ai/token/hooks/useRecords.ts +65 -0
  118. package/ai/token/missingUsageEstimate.ts +42 -0
  119. package/ai/token/modelUsageQuery.ts +252 -0
  120. package/ai/token/normalizeUsage.ts +84 -0
  121. package/ai/token/openaiImageGenerationUsage.ts +56 -0
  122. package/ai/token/prepareTokenUsageData.ts +88 -0
  123. package/ai/token/query.ts +88 -0
  124. package/ai/token/queryUserTokens.ts +59 -0
  125. package/ai/token/resolveBillingTarget.ts +52 -0
  126. package/ai/token/saveTokenRecord.ts +53 -0
  127. package/ai/token/serverDialogProjection.ts +78 -0
  128. package/ai/token/serverTokenWriter.ts +143 -0
  129. package/ai/token/stats.ts +21 -0
  130. package/ai/token/tokenThunks.ts +24 -0
  131. package/ai/token/types.ts +93 -0
  132. package/ai/tools/agent/agentTools.ts +176 -0
  133. package/ai/tools/agent/agentUpdateShared.ts +311 -0
  134. package/ai/tools/agent/callAgentTool.ts +139 -0
  135. package/ai/tools/agent/createAgentTool.ts +512 -0
  136. package/ai/tools/agent/createDialogTool.ts +69 -0
  137. package/ai/tools/agent/createSkillAgentTool.ts +62 -0
  138. package/ai/tools/agent/parallelBudget.ts +221 -0
  139. package/ai/tools/agent/presets/appBuilderPreset.ts +147 -0
  140. package/ai/tools/agent/runLlmTool.ts +96 -0
  141. package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
  142. package/ai/tools/agent/skillAgentArgs.ts +106 -0
  143. package/ai/tools/agent/skillAgentPreset.ts +89 -0
  144. package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
  145. package/ai/tools/agent/updateAgentTool.ts +96 -0
  146. package/ai/tools/agent/updateSelfTool.ts +113 -0
  147. package/ai/tools/amazonProductScraperTool.ts +86 -0
  148. package/ai/tools/apifyActorClient.ts +45 -0
  149. package/ai/tools/appEditGuard.ts +372 -0
  150. package/ai/tools/appReadSnapshot.ts +153 -0
  151. package/ai/tools/appTools.ts +1549 -0
  152. package/ai/tools/applyEditTool.ts +256 -0
  153. package/ai/tools/applyLineEditsTool.ts +312 -0
  154. package/ai/tools/browserTools/click.ts +33 -0
  155. package/ai/tools/browserTools/closeSession.ts +29 -0
  156. package/ai/tools/browserTools/common.ts +27 -0
  157. package/ai/tools/browserTools/openSession.ts +48 -0
  158. package/ai/tools/browserTools/readContent.ts +38 -0
  159. package/ai/tools/browserTools/selectOption.ts +46 -0
  160. package/ai/tools/browserTools/typeText.ts +42 -0
  161. package/ai/tools/category/createCategoryTool.ts +66 -0
  162. package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
  163. package/ai/tools/category/updateContentCategoryTool.ts +75 -0
  164. package/ai/tools/cfBrowserTools.ts +319 -0
  165. package/ai/tools/cfSpeechToTextTool.ts +49 -0
  166. package/ai/tools/checkEnvTool.ts +65 -0
  167. package/ai/tools/cloudflareCrawlTool.ts +289 -0
  168. package/ai/tools/codeSearchTool.ts +111 -0
  169. package/ai/tools/codeTools.ts +101 -0
  170. package/ai/tools/createDocTool.ts +132 -0
  171. package/ai/tools/createPlanTool.ts +999 -0
  172. package/ai/tools/createSkillDocTool.ts +155 -0
  173. package/ai/tools/createWorkflowTool.ts +154 -0
  174. package/ai/tools/deepseekOcrTool.ts +34 -0
  175. package/ai/tools/delayTool.ts +31 -0
  176. package/ai/tools/deleteSpacesTool.ts +325 -0
  177. package/ai/tools/deleteSpacesToolModel.ts +159 -0
  178. package/ai/tools/devReloadUtils.ts +29 -0
  179. package/ai/tools/dialogMessageSearch.ts +137 -0
  180. package/ai/tools/doctorSkillTool.ts +72 -0
  181. package/ai/tools/ecommerceScraperTool.ts +86 -0
  182. package/ai/tools/emailTools.ts +549 -0
  183. package/ai/tools/evalSkillTool.ts +92 -0
  184. package/ai/tools/exaSearchTool.ts +64 -0
  185. package/ai/tools/execBashTool.ts +379 -0
  186. package/ai/tools/executeSqlTool.ts +192 -0
  187. package/ai/tools/fetchWebpageSupport.ts +309 -0
  188. package/ai/tools/fetchWebpageTool.ts +84 -0
  189. package/ai/tools/geminiImagePreviewTool.ts +361 -0
  190. package/ai/tools/generateDocxTool.ts +215 -0
  191. package/ai/tools/googleSearchScraperTool.ts +106 -0
  192. package/ai/tools/importDataTool.ts +133 -0
  193. package/ai/tools/importSkillTool.ts +162 -0
  194. package/ai/tools/index.ts +1927 -0
  195. package/ai/tools/listFilesTool.ts +82 -0
  196. package/ai/tools/listUserSpacesTool.ts +113 -0
  197. package/ai/tools/modelUsageTools.ts +199 -0
  198. package/ai/tools/olmOcrTool.ts +34 -0
  199. package/ai/tools/openaiImageTool.ts +267 -0
  200. package/ai/tools/prepareTools.ts +23 -0
  201. package/ai/tools/readDocTool.ts +84 -0
  202. package/ai/tools/readFileTool.ts +211 -0
  203. package/ai/tools/readTool.ts +163 -0
  204. package/ai/tools/readXPostTool.ts +233 -0
  205. package/ai/tools/rememberMemoryTool.ts +84 -0
  206. package/ai/tools/remotionVideoTool.ts +151 -0
  207. package/ai/tools/searchDialogMessagesTool.ts +222 -0
  208. package/ai/tools/searchRepoTool.ts +115 -0
  209. package/ai/tools/searchWorkspaceTool.ts +259 -0
  210. package/ai/tools/skillFollowup.ts +86 -0
  211. package/ai/tools/surfWeatherTool.ts +169 -0
  212. package/ai/tools/table/addTableRowTool.ts +217 -0
  213. package/ai/tools/table/createTableTool.ts +315 -0
  214. package/ai/tools/table/rowTools.ts +366 -0
  215. package/ai/tools/table/schemaTools.ts +244 -0
  216. package/ai/tools/table/shareTableTool.ts +148 -0
  217. package/ai/tools/table/toolShared.ts +129 -0
  218. package/ai/tools/toolApiClient.ts +198 -0
  219. package/ai/tools/toolNameAliases.ts +57 -0
  220. package/ai/tools/toolResultError.ts +42 -0
  221. package/ai/tools/toolRunSlice.ts +303 -0
  222. package/ai/tools/toolSchemaCompatibility.ts +53 -0
  223. package/ai/tools/toolVisibility.ts +4 -0
  224. package/ai/tools/types.ts +20 -0
  225. package/ai/tools/uiAskChoiceTool.ts +104 -0
  226. package/ai/tools/updateContentTitleTool.ts +84 -0
  227. package/ai/tools/updateDocTool.ts +105 -0
  228. package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
  229. package/ai/tools/whisperTool.ts +77 -0
  230. package/ai/tools/writeFileTool.ts +210 -0
  231. package/ai/tools/youtubeScraperTool.ts +116 -0
  232. package/ai/tools/ziweiChartTool.ts +678 -0
  233. package/ai/types.ts +55 -0
  234. package/ai/workflow/workflowExecutor.ts +323 -0
  235. package/ai/workflow/workflowSlice.ts +73 -0
  236. package/ai/workflow/workflowTypes.ts +106 -0
  237. package/client/agentRun.test.ts +240 -0
  238. package/client/agentRun.ts +182 -19
  239. package/client/compactDialog.test.ts +238 -0
  240. package/client/localRuntimeAdapter.test.ts +135 -0
  241. package/client/localRuntimeAdapter.ts +244 -0
  242. package/client/profileConfig.test.ts +40 -0
  243. package/client/streamingOutput.test.ts +22 -0
  244. package/client/streamingOutput.ts +38 -0
  245. package/commandRegistry.ts +9 -2
  246. package/connector-experimental/index.ts +5 -0
  247. package/database/actions/cacheMergedUserData.ts +64 -0
  248. package/database/actions/common.ts +242 -0
  249. package/database/actions/deleteFile.ts +40 -0
  250. package/database/actions/fetchUserData.ts +16 -0
  251. package/database/actions/fileContent.ts +125 -0
  252. package/database/actions/patch.ts +155 -0
  253. package/database/actions/read.ts +337 -0
  254. package/database/actions/readAndWait.ts +224 -0
  255. package/database/actions/readRequestManager.ts +120 -0
  256. package/database/actions/remove.ts +94 -0
  257. package/database/actions/replication.ts +366 -0
  258. package/database/actions/upload.ts +174 -0
  259. package/database/actions/upsert.ts +56 -0
  260. package/database/actions/write.ts +126 -0
  261. package/database/client/db.native.ts +73 -0
  262. package/database/client/db.ts +51 -0
  263. package/database/client/fetchUserData.ts +61 -0
  264. package/database/client/handleError.ts +19 -0
  265. package/database/client/queryRequest.ts +21 -0
  266. package/database/config.ts +21 -0
  267. package/database/dbActionThunks.ts +1 -0
  268. package/database/dbSlice.ts +149 -0
  269. package/database/email.ts +42 -0
  270. package/database/fileRing.ts +51 -0
  271. package/database/fileSharding.ts +70 -0
  272. package/database/fileStorage.native.ts +92 -0
  273. package/database/fileStorage.ts +232 -0
  274. package/database/fileUrl.ts +34 -0
  275. package/database/hooks/useUserData.ts +489 -0
  276. package/database/index.ts +1 -0
  277. package/database/keys.ts +765 -0
  278. package/database/queryPrefixes.ts +14 -0
  279. package/database/requests.ts +443 -0
  280. package/database/runtimeServerContext.ts +35 -0
  281. package/database/server/MemoryDB.ts +76 -0
  282. package/database/server/actorAccess.ts +76 -0
  283. package/database/server/agentDelegation.ts +124 -0
  284. package/database/server/coreDataOwnership.ts +13 -0
  285. package/database/server/coreDataProxy.ts +76 -0
  286. package/database/server/cybotReadonly.ts +18 -0
  287. package/database/server/dataHandlers.ts +111 -0
  288. package/database/server/db.ts +118 -0
  289. package/database/server/dbPath.ts +20 -0
  290. package/database/server/delete.ts +499 -0
  291. package/database/server/emailRepository.ts +1480 -0
  292. package/database/server/ensureDbOpen.ts +12 -0
  293. package/database/server/fileRead.ts +337 -0
  294. package/database/server/fileService.ts +436 -0
  295. package/database/server/handleTransaction.ts +86 -0
  296. package/database/server/patch.ts +282 -0
  297. package/database/server/query.ts +138 -0
  298. package/database/server/read.ts +325 -0
  299. package/database/server/resourceAccess.ts +211 -0
  300. package/database/server/routes.ts +110 -0
  301. package/database/server/spaceMemberAuthority.ts +67 -0
  302. package/database/server/upload.ts +159 -0
  303. package/database/server/write.ts +494 -0
  304. package/database/server/writeAuthority.ts +133 -0
  305. package/database/sqliteDb.ts +46 -0
  306. package/database/table/deleteTable.ts +120 -0
  307. package/database/tenantPlacement.ts +57 -0
  308. package/database/tombstones.ts +52 -0
  309. package/database/userDataLoadDecision.ts +17 -0
  310. package/database/userDataMerge.ts +95 -0
  311. package/database/userPreferenceRegister.ts +108 -0
  312. package/database/utils/dbPath.ts +47 -0
  313. package/database/utils/ulid.native.ts +6 -0
  314. package/database/utils/ulid.ts +1 -0
  315. package/index.ts +25 -15
  316. package/localRuntimeDb.ts +28 -0
  317. package/package.json +16 -4
  318. package/runtimeModeArgs.ts +33 -0
  319. package/tui/readlineWorkspace.ts +1 -0
  320. package/tui/session.ts +22 -0
@@ -0,0 +1,233 @@
1
+ import { createXReadFailure } from "../../integrations/x-reader/types";
2
+ import type { XPost, XReadResult } from "../../integrations/x-reader/types";
3
+ import { getRequestConfig, ToolApiError } from "./toolApiClient";
4
+
5
+ export const readXPostFunctionSchema = {
6
+ name: "read_x_post",
7
+ description:
8
+ "读取 X/Twitter status 链接的可见帖子正文、作者和结构化数据。适合用户给出 x.com/twitter.com 帖子链接并要求查看、总结、解释或抽取信息的场景。默认通过桌面本地 Chrome/CDP bridge 读取,不要求用户粘贴 cookie 或 token。",
9
+ parameters: {
10
+ type: "object",
11
+ properties: {
12
+ url: {
13
+ type: "string",
14
+ description:
15
+ "要读取的 X/Twitter status URL,例如 https://x.com/user/status/123。",
16
+ },
17
+ keepOpen: {
18
+ type: "boolean",
19
+ description: "调试时是否保留临时 Chrome bridge。默认 false。",
20
+ default: false,
21
+ },
22
+ profileDir: {
23
+ type: "string",
24
+ description:
25
+ "桌面端本地 Chrome 专用 profile 目录。用于让用户在本机登录一次 X 后复用本地账号状态;不要传入用户日常 Chrome profile。",
26
+ },
27
+ headless: {
28
+ type: "boolean",
29
+ description:
30
+ "是否用 headless 模式启动本地 Chrome。需要用户首次登录本地 X profile 时设为 false 并配合 keepOpen。",
31
+ default: true,
32
+ },
33
+ },
34
+ required: ["url"],
35
+ },
36
+ };
37
+
38
+ type ReadXPostToolContext = {
39
+ reader?: (
40
+ url: string,
41
+ args: { keepOpen?: boolean; profileDir?: string; headless?: boolean },
42
+ ) => Promise<XReadResult<XPost>>;
43
+ };
44
+
45
+ async function callLocalReadXPostApi(
46
+ thunkApi: any,
47
+ body: object,
48
+ ): Promise<XReadResult<XPost>> {
49
+ const { currentServer, token } = getRequestConfig(thunkApi);
50
+ const browserOrigin = (globalThis as any).window?.location?.origin;
51
+ const baseUrl = typeof browserOrigin === "string" && browserOrigin
52
+ ? browserOrigin
53
+ : currentServer.replace(/\/+$/, "");
54
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
55
+ if (token) headers.Authorization = `Bearer ${token}`;
56
+
57
+ const response = await fetch(`${baseUrl}/api/read-x-post`, {
58
+ method: "POST",
59
+ headers,
60
+ body: JSON.stringify(body),
61
+ });
62
+ const text = await response.text();
63
+ let data: any = null;
64
+ try {
65
+ data = text ? JSON.parse(text) : null;
66
+ } catch {
67
+ data = null;
68
+ }
69
+ if (!response.ok) {
70
+ throw new ToolApiError(
71
+ data?.error?.message ?? `read_x_post API 请求失败,状态码: ${response.status}`,
72
+ {
73
+ status: response.status,
74
+ code: data?.error?.code,
75
+ details: data,
76
+ },
77
+ );
78
+ }
79
+ return data as XReadResult<XPost>;
80
+ }
81
+
82
+ async function callDesktopReadXPostApi(
83
+ thunkApi: any,
84
+ body: object,
85
+ ): Promise<XReadResult<XPost>> {
86
+ const { token } = getRequestConfig(thunkApi);
87
+ const port = Number(process.env.NOLO_DESKTOP_SERVER_PORT ?? 3233);
88
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
89
+ if (token) headers.Authorization = `Bearer ${token}`;
90
+
91
+ const response = await fetch(`http://127.0.0.1:${port}/api/read-x-post`, {
92
+ method: "POST",
93
+ headers,
94
+ body: JSON.stringify(body),
95
+ });
96
+ const text = await response.text();
97
+ let data: any = null;
98
+ try {
99
+ data = text ? JSON.parse(text) : null;
100
+ } catch {
101
+ data = null;
102
+ }
103
+ if (!response.ok) {
104
+ throw new ToolApiError(
105
+ data?.error?.message ?? `desktop read_x_post API 请求失败,状态码: ${response.status}`,
106
+ {
107
+ status: response.status,
108
+ code: data?.error?.code,
109
+ details: data,
110
+ },
111
+ );
112
+ }
113
+ return data as XReadResult<XPost>;
114
+ }
115
+
116
+ async function readWithDefaultBridge(
117
+ url: string,
118
+ args: { keepOpen?: boolean; profileDir?: string; headless?: boolean },
119
+ thunkApi?: any,
120
+ ): Promise<XReadResult<XPost>> {
121
+ if (process.env.PLATFORM === "web") {
122
+ if (thunkApi?.getState) {
123
+ return callLocalReadXPostApi(thunkApi, { url, ...args });
124
+ }
125
+
126
+ if (typeof window !== "undefined" && (window as any).__NOLO_DESKTOP__) {
127
+ try {
128
+ const res = await fetch("/api/read-x-post", {
129
+ method: "POST",
130
+ headers: { "Content-Type": "application/json" },
131
+ body: JSON.stringify({ url, ...args }),
132
+ });
133
+ const data = await res.json().catch(() => null);
134
+ if (res.ok && data) {
135
+ return data as XReadResult<XPost>;
136
+ }
137
+ return createXReadFailure({
138
+ code: "network_error",
139
+ message: `desktop read_x_post endpoint failed: HTTP ${res.status}`,
140
+ nextStep: "请确认桌面端本地服务仍在运行,然后重试。",
141
+ backend: "desktop_local_browser",
142
+ });
143
+ } catch (error) {
144
+ return createXReadFailure({
145
+ code: "network_error",
146
+ message:
147
+ error instanceof Error
148
+ ? error.message
149
+ : "desktop read_x_post endpoint request failed",
150
+ nextStep: "请确认桌面端本地服务仍在运行,然后重试。",
151
+ backend: "desktop_local_browser",
152
+ });
153
+ }
154
+ }
155
+
156
+ return createXReadFailure({
157
+ code: "not_connected",
158
+ message: "read_x_post 需要通过服务器或桌面本地 bridge 执行,不能在普通浏览器 bundle 内直接启动 Chrome/CDP。",
159
+ nextStep: "请使用服务器 agent run 或桌面端本地 bridge 路径执行该工具。",
160
+ backend: "desktop_local_browser",
161
+ });
162
+ }
163
+
164
+ if (process.env.NOLO_DESKTOP === "1" && thunkApi?.getState) {
165
+ return callDesktopReadXPostApi(thunkApi, { url, ...args });
166
+ }
167
+
168
+ const importBridge = new Function("specifier", "return import(specifier)") as <
169
+ T = any,
170
+ >(
171
+ specifier: string,
172
+ ) => Promise<T>;
173
+ const bridgeModuleUrl = new URL(
174
+ "../../integrations/x-reader/bridge/readXPostWithBridge.ts",
175
+ import.meta.url,
176
+ ).href;
177
+ const { readXPostWithBridge } = await importBridge<{
178
+ readXPostWithBridge: (
179
+ url: string,
180
+ args: { keepOpen?: boolean; profileDir?: string; headless?: boolean },
181
+ ) => Promise<XReadResult<XPost>>;
182
+ }>(bridgeModuleUrl);
183
+ return readXPostWithBridge(url, args);
184
+ }
185
+
186
+ function assertXStatusUrl(url: string) {
187
+ if (!/^https?:\/\/(x|twitter)\.com\/[^/]+\/status\/\d+/i.test(url)) {
188
+ throw new Error("read_x_post 需要一个有效的 X/Twitter status URL。");
189
+ }
190
+ }
191
+
192
+ function formatDisplay(result: XReadResult<XPost>) {
193
+ if (!result.ok) {
194
+ return [
195
+ `读取 X 帖子失败:${result.message}`,
196
+ `失败代码:${result.code}`,
197
+ result.nextStep ? `下一步:${result.nextStep}` : "",
198
+ `后端:${result.backend}`,
199
+ ]
200
+ .filter(Boolean)
201
+ .join("\n");
202
+ }
203
+
204
+ const post = result.data;
205
+ return [
206
+ `已读取 X 帖子:@${post.author.handle}${post.author.displayName ? `(${post.author.displayName})` : ""}`,
207
+ `URL: ${post.url}`,
208
+ `后端:${result.backend}`,
209
+ "",
210
+ post.text,
211
+ ].join("\n");
212
+ }
213
+
214
+ export async function readXPostFunc(
215
+ args: { url: string; keepOpen?: boolean; profileDir?: string; headless?: boolean },
216
+ thunkApi: any,
217
+ context: ReadXPostToolContext = {},
218
+ ): Promise<{ rawData: XReadResult<XPost>; displayData: string }> {
219
+ const url = String(args?.url ?? "").trim();
220
+ assertXStatusUrl(url);
221
+
222
+ const keepOpen = Boolean(args?.keepOpen);
223
+ const profileDir = String(args?.profileDir ?? "").trim() || undefined;
224
+ const headless = args?.headless;
225
+ const rawData =
226
+ (await context.reader?.(url, { keepOpen, profileDir, headless })) ??
227
+ (await readWithDefaultBridge(url, { keepOpen, profileDir, headless }, thunkApi));
228
+
229
+ return {
230
+ rawData,
231
+ displayData: formatDisplay(rawData),
232
+ };
233
+ }
@@ -0,0 +1,84 @@
1
+ import { selectCurrentSpaceId } from "create/space/spaceSlice";
2
+ import { callToolApi } from "./toolApiClient";
3
+ import type { RememberMemoryScope } from "ai/memory/remember";
4
+ import type { MemoryKind } from "ai/memory/types";
5
+
6
+ export interface RememberMemoryToolArgs {
7
+ content: string;
8
+ scope?: RememberMemoryScope;
9
+ kind?: MemoryKind;
10
+ }
11
+
12
+ export const rememberMemoryFunctionSchema = {
13
+ name: "rememberMemory",
14
+ description: [
15
+ "当你判断某条用户偏好、纠正、决策习惯或当前 Space 共识值得被长期记住时,调用本工具写入一条 memory。",
16
+ "默认先记原始事件,不要把一次性临时要求、当前任务进度或明显短期信息写进去。",
17
+ "只有重复出现的可执行流程/排障步骤才传 kind=procedural;一般偏好和事实保持默认 episodic。",
18
+ "只有当当前 dialog 明确绑定了一个 Space,且这条内容确实属于共享协作共识时,才应该传 scope=space。",
19
+ "优先写成一句简洁、未来仍可理解的话;如无必要,不要频繁调用。",
20
+ ].join("\n"),
21
+ parameters: {
22
+ type: "object",
23
+ properties: {
24
+ content: {
25
+ type: "string",
26
+ description:
27
+ "要记住的内容。请写成一句未来仍然可理解的简洁描述,例如“这个用户在复杂问题里更喜欢先看结论”。",
28
+ },
29
+ scope: {
30
+ type: "string",
31
+ enum: ["auto", "user", "space"],
32
+ description:
33
+ "记忆范围。默认 auto:优先记到当前用户;若没有用户上下文再退到当前 space。只有当前 dialog 明确绑定了 space,且你明确想写共享协作记忆时才传 space;否则保持 auto。",
34
+ },
35
+ kind: {
36
+ type: "string",
37
+ enum: ["episodic", "semantic", "procedural"],
38
+ description:
39
+ "记忆类型。默认 episodic。只有重复出现的可执行流程、排障步骤或稳定 runbook 才使用 procedural。",
40
+ },
41
+ },
42
+ required: ["content"],
43
+ } as const,
44
+ };
45
+
46
+ export async function rememberMemoryFunc(
47
+ args: RememberMemoryToolArgs,
48
+ thunkApi: any
49
+ ): Promise<{ rawData: unknown; displayData: string }> {
50
+ const state = thunkApi.getState();
51
+ const spaceId = selectCurrentSpaceId(state) || undefined;
52
+ const content = String(args.content ?? "").trim();
53
+ const scope = args.scope ?? "auto";
54
+ const kind = args.kind ?? "episodic";
55
+
56
+ if (!content) {
57
+ throw new Error("rememberMemory 需要非空 content。");
58
+ }
59
+
60
+ const result = await callToolApi<{
61
+ success: boolean;
62
+ content: string;
63
+ requestedScope: RememberMemoryScope;
64
+ resolvedScopes: Array<{ ownerType: string; ownerId: string }>;
65
+ }>(
66
+ thunkApi,
67
+ "/api/memory/remember",
68
+ {
69
+ content,
70
+ scope,
71
+ kind,
72
+ spaceId,
73
+ },
74
+ { withAuth: true }
75
+ );
76
+
77
+ const scopeLabel =
78
+ result.resolvedScopes?.[0]?.ownerType === "space" ? "当前空间" : "当前用户";
79
+
80
+ return {
81
+ rawData: result,
82
+ displayData: `已记住这条${scopeLabel}记忆。`,
83
+ };
84
+ }
@@ -0,0 +1,151 @@
1
+ import { ContentType } from "app/types";
2
+ import { addContentAction } from "create/space/content/addContentAction";
3
+ import { selectCurrentSpaceId } from "create/space/spaceSlice";
4
+ import { buildDatabaseFileContentUrl } from "database/fileUrl";
5
+ import { fileKey } from "database/keys";
6
+ import { selectCurrentServer } from "app/settings/settingSlice";
7
+ import { selectUserId } from "auth/authSlice";
8
+ import { callToolApi } from "./toolApiClient";
9
+
10
+ type RemotionRenderVideoArgs = {
11
+ template?: "mobile-product" | "landscape-product" | "nolo-mobile-product" | "nolo-landscape-product";
12
+ brand?: string;
13
+ hook?: string;
14
+ headline?: string;
15
+ subline?: string;
16
+ prompt?: string;
17
+ cta?: string;
18
+ outputName?: string;
19
+ };
20
+
21
+ export const remotionRenderVideoFunctionSchema = {
22
+ name: "remotionRenderVideo",
23
+ description: [
24
+ "使用平台内 Remotion 模板渲染产品视频,并保存为可复用 MP4 文件。",
25
+ "",
26
+ "适用场景:",
27
+ "- 用户要生成手机传播视频、产品介绍视频、欢迎页短片、Agent/AI 工作流演示视频。",
28
+ "- 需要把文案、标题、输入框示例等参数化后产出 MP4。",
29
+ "",
30
+ "当前模板:",
31
+ "- mobile-product: 9:16 竖版,适合手机传播。",
32
+ "- landscape-product: 16:9 横版,适合官网/演示页。",
33
+ "- 兼容旧别名 nolo-mobile-product / nolo-landscape-product。",
34
+ ].join("\n"),
35
+ parameters: {
36
+ type: "object",
37
+ properties: {
38
+ template: {
39
+ type: "string",
40
+ enum: ["mobile-product", "landscape-product", "nolo-mobile-product", "nolo-landscape-product"],
41
+ description: "视频模板。默认 mobile-product。",
42
+ },
43
+ brand: {
44
+ type: "string",
45
+ description: "品牌名,例如 Nolo.Chat、你的产品名、店铺名或活动名。",
46
+ },
47
+ hook: {
48
+ type: "string",
49
+ description: "竖版视频首屏钩子标题,例如“把一句想法,推进成结果”。",
50
+ },
51
+ headline: {
52
+ type: "string",
53
+ description: "横版视频主标题;竖版缺少 hook 时也会作为 hook 使用。",
54
+ },
55
+ subline: {
56
+ type: "string",
57
+ description: "横版视频副标题。",
58
+ },
59
+ prompt: {
60
+ type: "string",
61
+ description: "手机输入框里逐字打出的示例需求。",
62
+ },
63
+ cta: {
64
+ type: "string",
65
+ description: "结尾行动号召,例如“开始体验”。",
66
+ },
67
+ outputName: {
68
+ type: "string",
69
+ description: "输出文件名,建议以 .mp4 结尾。",
70
+ },
71
+ },
72
+ },
73
+ };
74
+
75
+ export const remotionRenderVideoFunc = async (
76
+ args: RemotionRenderVideoArgs,
77
+ thunkApi: any
78
+ ): Promise<{ rawData: any; displayData: string; llmContext?: string }> => {
79
+ const result: any = await callToolApi(
80
+ thunkApi,
81
+ "/api/remotion/render",
82
+ {
83
+ template: args.template || "mobile-product",
84
+ brand: args.brand,
85
+ hook: args.hook,
86
+ headline: args.headline,
87
+ subline: args.subline,
88
+ prompt: args.prompt,
89
+ cta: args.cta,
90
+ outputName: args.outputName,
91
+ },
92
+ { withAuth: true }
93
+ );
94
+
95
+ const state = thunkApi.getState();
96
+ const userId = selectUserId(state);
97
+ const spaceId = selectCurrentSpaceId(state);
98
+ const currentServer = selectCurrentServer(state);
99
+ const fileId = result?.fileId;
100
+ const metadata = result?.metadata || {};
101
+ const fileDbKey = userId && fileId ? fileKey.single(userId, fileId) : "";
102
+ const url = buildDatabaseFileContentUrl(currentServer, fileDbKey || fileId);
103
+
104
+ if (spaceId && fileDbKey) {
105
+ try {
106
+ await addContentAction(
107
+ {
108
+ spaceId,
109
+ contentKey: fileDbKey,
110
+ title: metadata.originalName || "Remotion video.mp4",
111
+ type: ContentType.FILE,
112
+ fileCategory: "video",
113
+ } as any,
114
+ { dispatch: thunkApi.dispatch, getState: thunkApi.getState }
115
+ );
116
+ } catch (error) {
117
+ console.warn("[remotionRenderVideoFunc] Failed to add video to space:", error);
118
+ }
119
+ }
120
+
121
+ const displayLines = [
122
+ result?.text || "已使用 Remotion 生成视频。",
123
+ fileId ? `- fileId: ${fileId}` : "",
124
+ fileDbKey ? `- fileDbKey: ${fileDbKey}` : "",
125
+ url ? `- url: ${url}` : "",
126
+ metadata?.size ? `- size: ${metadata.size} bytes` : "",
127
+ result?.template ? `- template: ${result.template}` : "",
128
+ ].filter(Boolean);
129
+
130
+ const llmContext = [
131
+ "The Remotion video tool produced a reusable video file.",
132
+ "Reuse these exact references when mentioning or embedding the video:",
133
+ fileId ? `- fileId: ${fileId}` : "",
134
+ fileDbKey ? `- fileDbKey: ${fileDbKey}` : "",
135
+ url ? `- url: ${url}` : "",
136
+ metadata?.originalName ? `- name: ${metadata.originalName}` : "",
137
+ result?.template ? `- template: ${result.template}` : "",
138
+ ]
139
+ .filter(Boolean)
140
+ .join("\n");
141
+
142
+ return {
143
+ rawData: {
144
+ ...result,
145
+ fileDbKey,
146
+ url,
147
+ },
148
+ displayData: displayLines.join("\n"),
149
+ llmContext,
150
+ };
151
+ };
@@ -0,0 +1,222 @@
1
+ import { extractCustomId } from "core/prefix";
2
+ import { selectRuntimeSnapshot } from "app/stateViews/runtime";
3
+ import {
4
+ buildDialogMessageSearchResults,
5
+ clampDialogSearchNumber,
6
+ formatDialogMessageSearchDisplay,
7
+ normalizeDialogSearchText,
8
+ type DialogMessageSearchRecord,
9
+ } from "./dialogMessageSearch";
10
+
11
+ type SearchDialogMessagesArgs = {
12
+ dialogKey: string;
13
+ query: string;
14
+ limit?: number;
15
+ scanLimit?: number;
16
+ contextMessages?: number;
17
+ role?: "user" | "assistant" | "tool" | "system";
18
+ includeTools?: boolean;
19
+ };
20
+
21
+ const MAX_LIMIT = 10;
22
+ const MAX_CONTEXT_MESSAGES = 3;
23
+ const MAX_CONTENT_CHARS = 1800;
24
+ const CONTEXT_CONTENT_CHARS = 600;
25
+ const SERVER_SCAN_LIMIT = 500;
26
+
27
+ const getMessageSortValue = (message: DialogMessageSearchRecord) => {
28
+ const createdAt = message.createdAt;
29
+ if (typeof createdAt === "number") return createdAt;
30
+ if (typeof createdAt === "string") {
31
+ const parsed = Date.parse(createdAt);
32
+ if (Number.isFinite(parsed)) return parsed;
33
+ }
34
+ const id = normalizeDialogSearchText(message.id);
35
+ return id ? id : normalizeDialogSearchText(message.dbKey);
36
+ };
37
+
38
+ async function collectDialogMessages(db: any, dialogId: string): Promise<DialogMessageSearchRecord[]> {
39
+ if (!db?.iterator) return [];
40
+ const prefix = `dialog-${dialogId}-msg-`;
41
+ let iterator = db.iterator({
42
+ gte: prefix,
43
+ lte: `${prefix}\uffff`,
44
+ });
45
+ if (iterator && typeof iterator.then === "function") {
46
+ iterator = await iterator;
47
+ }
48
+
49
+ const messages: DialogMessageSearchRecord[] = [];
50
+ for await (const [key, value] of iterator) {
51
+ if (!value || typeof value !== "object") continue;
52
+ messages.push({
53
+ ...(value as DialogMessageSearchRecord),
54
+ dbKey: (value as DialogMessageSearchRecord).dbKey || String(key),
55
+ });
56
+ }
57
+
58
+ return messages.sort((a, b) => {
59
+ const left = getMessageSortValue(a);
60
+ const right = getMessageSortValue(b);
61
+ if (typeof left === "number" && typeof right === "number") return left - right;
62
+ return String(left).localeCompare(String(right));
63
+ });
64
+ }
65
+
66
+ async function fetchDialogMessagesFromServer(args: {
67
+ serverBase: string;
68
+ token?: string;
69
+ dialogId: string;
70
+ limit: number;
71
+ }): Promise<DialogMessageSearchRecord[]> {
72
+ const serverBase = args.serverBase.replace(/\/+$/, "");
73
+ if (!serverBase || !args.token) return [];
74
+
75
+ const response = await fetch(`${serverBase}/rpc/getConvMsgs`, {
76
+ method: "POST",
77
+ headers: {
78
+ Authorization: `Bearer ${args.token}`,
79
+ "Content-Type": "application/json",
80
+ },
81
+ body: JSON.stringify({ dialogId: args.dialogId, limit: args.limit }),
82
+ });
83
+ if (!response.ok) return [];
84
+
85
+ const newestFirst = await response.json();
86
+ if (!Array.isArray(newestFirst)) return [];
87
+ return [...newestFirst].reverse().map((message) => ({
88
+ ...(message as DialogMessageSearchRecord),
89
+ dbKey: (message as DialogMessageSearchRecord).dbKey,
90
+ }));
91
+ }
92
+
93
+ async function collectBestAvailableDialogMessages(
94
+ db: any,
95
+ dialogId: string,
96
+ thunkApi: any,
97
+ scanLimit: number,
98
+ ): Promise<DialogMessageSearchRecord[]> {
99
+ const localMessages = await collectDialogMessages(db, dialogId);
100
+ const state = thunkApi?.getState?.();
101
+ const runtime = state ? selectRuntimeSnapshot(state) : null;
102
+ const serverBases = Array.from(new Set([
103
+ runtime?.localRuntimeOrigin,
104
+ runtime?.currentServer,
105
+ ...(Array.isArray(runtime?.syncServers) ? runtime.syncServers : []),
106
+ ].filter((base): base is string => typeof base === "string" && base.trim().length > 0)));
107
+
108
+ let bestMessages = localMessages;
109
+ for (const serverBase of serverBases) {
110
+ const serverMessages = await fetchDialogMessagesFromServer({
111
+ serverBase,
112
+ token: runtime?.currentToken,
113
+ dialogId,
114
+ limit: scanLimit,
115
+ }).catch(() => []);
116
+ if (serverMessages.length > bestMessages.length) {
117
+ bestMessages = serverMessages;
118
+ }
119
+ }
120
+
121
+ return bestMessages;
122
+ }
123
+
124
+ export const searchDialogMessagesFunctionSchema = {
125
+ name: "searchDialogMessages",
126
+ description: [
127
+ "Search original messages inside a specific dialog by exact or fuzzy text.",
128
+ "Use this when a user asks for an exact old message, original wording, who said what, why a decision was made, early-history detail, failed attempts, files or tool evidence, or comparison with prior work from an attached conversation.",
129
+ "Prefer this over answering from a lossy dialog summary when the user needs evidence, provenance, or a specific prior detail.",
130
+ "Returns matching message ids, roles, clipped original content, and nearby context without loading the full dialog into the model context.",
131
+ ].join(" "),
132
+ parameters: {
133
+ type: "object",
134
+ properties: {
135
+ dialogKey: {
136
+ type: "string",
137
+ description: "Dialog dbKey, for example dialog-userId-01ABC...",
138
+ },
139
+ query: {
140
+ type: "string",
141
+ description: "Text to search for in original message content.",
142
+ },
143
+ limit: {
144
+ type: "number",
145
+ description: `Maximum matches to return. Capped at ${MAX_LIMIT}.`,
146
+ },
147
+ scanLimit: {
148
+ type: "number",
149
+ description: `Maximum messages to scan when fetching from a server. Capped at ${SERVER_SCAN_LIMIT}.`,
150
+ },
151
+ contextMessages: {
152
+ type: "number",
153
+ description: `Number of neighboring messages before/after each match. Capped at ${MAX_CONTEXT_MESSAGES}.`,
154
+ },
155
+ role: {
156
+ type: "string",
157
+ enum: ["user", "assistant", "tool", "system"],
158
+ description: "Optional role filter.",
159
+ },
160
+ includeTools: {
161
+ type: "boolean",
162
+ description: "Whether tool messages may match. Defaults to true.",
163
+ },
164
+ },
165
+ required: ["dialogKey", "query"],
166
+ },
167
+ };
168
+
169
+ export async function searchDialogMessagesFunc(
170
+ args: SearchDialogMessagesArgs,
171
+ _thunkApi: any,
172
+ context?: { db?: any }
173
+ ) {
174
+ const dialogKey = normalizeDialogSearchText(args?.dialogKey);
175
+ const query = normalizeDialogSearchText(args?.query);
176
+ if (!dialogKey.startsWith("dialog-")) {
177
+ throw new Error("searchDialogMessages requires a dialog-* dbKey.");
178
+ }
179
+ if (!query) {
180
+ throw new Error("searchDialogMessages requires a non-empty query.");
181
+ }
182
+
183
+ const db = context?.db ?? _thunkApi?.extra?.db;
184
+ if (!db) {
185
+ throw new Error("searchDialogMessages cannot access the local message database.");
186
+ }
187
+
188
+ const dialogId = extractCustomId(dialogKey) || dialogKey.split("-").at(-1) || "";
189
+ const scanLimit = clampDialogSearchNumber(
190
+ args.scanLimit,
191
+ SERVER_SCAN_LIMIT,
192
+ 1,
193
+ SERVER_SCAN_LIMIT,
194
+ );
195
+ const messages = await collectBestAvailableDialogMessages(db, dialogId, _thunkApi, scanLimit);
196
+ const limit = clampDialogSearchNumber(args.limit, 5, 1, MAX_LIMIT);
197
+ const contextMessages = clampDialogSearchNumber(args.contextMessages, 1, 0, MAX_CONTEXT_MESSAGES);
198
+
199
+ const results = buildDialogMessageSearchResults({
200
+ messages,
201
+ query,
202
+ limit,
203
+ contextMessages,
204
+ role: args.role,
205
+ includeTools: args.includeTools,
206
+ contentClipChars: MAX_CONTENT_CHARS,
207
+ contextClipChars: CONTEXT_CONTENT_CHARS,
208
+ });
209
+
210
+ const displayData = formatDialogMessageSearchDisplay({ dialogKey, query, results });
211
+
212
+ return {
213
+ rawData: {
214
+ success: true,
215
+ dialogKey,
216
+ query,
217
+ scannedMessages: messages.length,
218
+ matches: results,
219
+ },
220
+ displayData,
221
+ };
222
+ }