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,325 @@
1
+ // 文件路径: packages/database/server/read.ts
2
+
3
+ import serverDb, { ensureServerDbOpen } from "./db";
4
+ import { DB_PREFIX, shareKey } from "database/keys";
5
+ import { parseToken, verifyToken } from "auth/token";
6
+ import { assertSessionTokenVersion } from "auth/sessionTokenVersion";
7
+ import { isTombstoneRecord } from "database/tombstones";
8
+ import { userActor } from "auth/actor";
9
+ import { authorizeActorRecordAccess } from "./actorAccess";
10
+
11
+ const isLevelNotFoundError = (error: any): boolean => {
12
+ const code = error?.code;
13
+ return (
14
+ error?.notFound === true ||
15
+ error?.name === "NotFoundError" ||
16
+ code === "LEVEL_NOT_FOUND" ||
17
+ code === "LEVEL_NOT_FOUND_ERROR"
18
+ );
19
+ };
20
+
21
+ const PROFILE_KEY_SUFFIX = "-profile";
22
+
23
+ const isPublicProfileKey = (dbKey: string): boolean =>
24
+ dbKey.endsWith(PROFILE_KEY_SUFFIX) && dbKey.length > PROFILE_KEY_SUFFIX.length;
25
+
26
+ const isPublicAgentKey = (dbKey: string): boolean =>
27
+ dbKey.startsWith("agent-pub-") || dbKey.startsWith("cybot-pub-");
28
+
29
+ const getUserIdFromProfileKey = (dbKey: string): string =>
30
+ dbKey.slice(0, -PROFILE_KEY_SUFFIX.length);
31
+
32
+ const getAuthorizedUserId = async (req: any): Promise<string | null> => {
33
+ const authHeader = req.headers?.get?.("authorization");
34
+ const token = typeof authHeader === "string" ? authHeader.split(" ")[1] : "";
35
+ if (!token) return null;
36
+
37
+ const parsed = parseToken(token);
38
+ const userId = parsed?.userId;
39
+ if (!userId || typeof userId !== "string") return null;
40
+
41
+ try {
42
+ const user = await serverDb.get(`${DB_PREFIX.USER}${userId}`);
43
+ if (!user?.publicKey || user?.isDisabled) return null;
44
+
45
+ const payload = verifyToken(token, user.publicKey);
46
+ assertSessionTokenVersion(payload, user);
47
+ if (!payload || payload.userId !== userId) return null;
48
+
49
+ const now = Date.now();
50
+ if (payload.exp != null) {
51
+ const expMs =
52
+ typeof payload.exp === "number"
53
+ ? payload.exp * 1000
54
+ : new Date(payload.exp).getTime();
55
+ if (Number.isFinite(expMs) && now > expMs) return null;
56
+ }
57
+ if (payload.nbf != null) {
58
+ const nbfMs =
59
+ typeof payload.nbf === "number"
60
+ ? payload.nbf * 1000
61
+ : new Date(payload.nbf).getTime();
62
+ if (Number.isFinite(nbfMs) && now < nbfMs) return null;
63
+ }
64
+
65
+ return userId;
66
+ } catch {
67
+ return null;
68
+ }
69
+ };
70
+
71
+ const purchaseRecordKey = (buyerId: string, shareToken: string) =>
72
+ `doc-purchase-${buyerId}-${shareToken}`;
73
+
74
+ const extractShareTokenFromDbKey = (dbKey: string): string | null =>
75
+ dbKey.startsWith("share-") ? dbKey.slice("share-".length) : null;
76
+
77
+ const isCommunityShare = (share: any): boolean =>
78
+ share?.meta?.visibility === "community";
79
+
80
+ const isShareOwner = (share: any, userId: string | null): boolean =>
81
+ Boolean(userId && share?.meta?.authorId === userId);
82
+
83
+ const isPaidShare = (share: any): boolean =>
84
+ typeof share?.meta?.price === "number" && share.meta.price > 0;
85
+
86
+ const redactSharePayload = (share: any) => ({
87
+ ...share,
88
+ data: {},
89
+ meta: {
90
+ ...(share?.meta ?? {}),
91
+ previewLocked: true,
92
+ requiresPurchase: true,
93
+ },
94
+ });
95
+
96
+ const hasPurchasedShare = async (
97
+ userId: string | null,
98
+ shareToken: string | null
99
+ ): Promise<boolean> => {
100
+ if (!userId || !shareToken) return false;
101
+ try {
102
+ await serverDb.get(purchaseRecordKey(userId, shareToken));
103
+ return true;
104
+ } catch {
105
+ return false;
106
+ }
107
+ };
108
+
109
+ const toPublicProfile = (profile: any, user: any) => ({
110
+ nickname:
111
+ typeof profile?.nickname === "string" && profile.nickname.trim()
112
+ ? profile.nickname
113
+ : typeof user?.username === "string"
114
+ ? user.username
115
+ : "",
116
+ username:
117
+ typeof user?.username === "string"
118
+ ? user.username
119
+ : typeof profile?.username === "string"
120
+ ? profile.username
121
+ : "",
122
+ avatar: typeof profile?.avatar === "string" ? profile.avatar : "",
123
+ });
124
+
125
+ const toPublicAgent = (agent: any) => {
126
+ const { apiKey, customProviderUrl, endpointKey, ...publicAgent } = agent ?? {};
127
+ return publicAgent;
128
+ };
129
+
130
+ const isAgentOwner = (agent: any, userId: string | null): boolean =>
131
+ Boolean(
132
+ userId &&
133
+ (agent?.ownerId === userId ||
134
+ agent?.userId === userId ||
135
+ agent?.tenantId === userId)
136
+ );
137
+ export const handleReadSingle = async (req: any) => {
138
+ const corsHeaders = {
139
+ "Access-Control-Allow-Origin": "*",
140
+ "Content-Type": "application/json",
141
+ };
142
+
143
+ if (!req.params?.id) {
144
+ return new Response(JSON.stringify({ error: "need id" }), {
145
+ status: 400,
146
+ headers: corsHeaders,
147
+ });
148
+ }
149
+
150
+ const dbKey = req.params.id;
151
+ const isShareDataKey = dbKey.startsWith("share-") || shareKey.isShareKey(dbKey);
152
+
153
+ try {
154
+ // ⭐ 确保 LevelDB 已打开,避免 LEVEL_DATABASE_NOT_OPEN
155
+ await ensureServerDbOpen();
156
+
157
+ const authedUserId = await getAuthorizedUserId(req);
158
+ const isPublicAgentDataKey = isPublicAgentKey(dbKey);
159
+ // NOTE: `space.visibility = "public"` is currently a product/discovery signal for
160
+ // logged-in flows, but it does not automatically make raw `/api/v1/db/read/:id`
161
+ // anonymous-readable. Today only public profiles and community shares bypass auth
162
+ // here. If we later want guest-readable public spaces / skill pages, that needs an
163
+ // explicit allowlist rule in this handler instead of assuming space visibility is enough.
164
+ if (!authedUserId && !isPublicProfileKey(dbKey) && !isShareDataKey && !isPublicAgentDataKey) {
165
+ return new Response(
166
+ JSON.stringify({
167
+ error: "Unauthorized",
168
+ message: "Authentication required",
169
+ }),
170
+ { status: 401, headers: corsHeaders }
171
+ );
172
+ }
173
+
174
+ if (!authedUserId && isPublicProfileKey(dbKey)) {
175
+ const inviterId = getUserIdFromProfileKey(dbKey);
176
+
177
+ let profile: any = null;
178
+ try {
179
+ profile = await serverDb.get(dbKey);
180
+ } catch (error: any) {
181
+ if (!isLevelNotFoundError(error)) throw error;
182
+ }
183
+
184
+ let user: any = null;
185
+ try {
186
+ user = await serverDb.get(`${DB_PREFIX.USER}${inviterId}`);
187
+ } catch (error: any) {
188
+ if (!isLevelNotFoundError(error)) throw error;
189
+ }
190
+
191
+ if (user?.isDisabled) {
192
+ return new Response(
193
+ JSON.stringify({
194
+ error: "Not Found",
195
+ message: `Resource with id ${dbKey} not found`,
196
+ }),
197
+ { status: 404, headers: corsHeaders }
198
+ );
199
+ }
200
+
201
+ if (!profile && !user) {
202
+ return new Response(
203
+ JSON.stringify({
204
+ error: "Not Found",
205
+ message: `Resource with id ${dbKey} not found`,
206
+ }),
207
+ { status: 404, headers: corsHeaders }
208
+ );
209
+ }
210
+
211
+ return new Response(JSON.stringify(toPublicProfile(profile, user)), {
212
+ status: 200,
213
+ headers: corsHeaders,
214
+ });
215
+ }
216
+
217
+ const result = await serverDb.get(dbKey);
218
+ if (
219
+ result === null ||
220
+ typeof result === "undefined" ||
221
+ isTombstoneRecord(result)
222
+ ) {
223
+ return new Response(
224
+ JSON.stringify({
225
+ error: "Not Found",
226
+ message: `Resource with id ${dbKey} not found`,
227
+ }),
228
+ { status: 404, headers: corsHeaders }
229
+ );
230
+ }
231
+
232
+ if (!authedUserId && isPublicAgentDataKey && result?.isPublic !== true) {
233
+ return new Response(
234
+ JSON.stringify({
235
+ error: "Not Found",
236
+ message: `Resource with id ${dbKey} not found`,
237
+ }),
238
+ { status: 404, headers: corsHeaders }
239
+ );
240
+ }
241
+
242
+ if (isShareDataKey) {
243
+ const owner = isShareOwner(result, authedUserId);
244
+ if (!isCommunityShare(result) && !owner) {
245
+ return new Response(
246
+ JSON.stringify({
247
+ error: "Not Found",
248
+ message: `Resource with id ${dbKey} not found`,
249
+ }),
250
+ { status: 404, headers: corsHeaders }
251
+ );
252
+ }
253
+
254
+ if (isPaidShare(result) && !owner) {
255
+ const shareToken = extractShareTokenFromDbKey(dbKey);
256
+ const purchased = await hasPurchasedShare(authedUserId, shareToken);
257
+ if (!purchased) {
258
+ return new Response(JSON.stringify(redactSharePayload(result)), {
259
+ status: 200,
260
+ headers: corsHeaders,
261
+ });
262
+ }
263
+ }
264
+ }
265
+
266
+ if (!authedUserId && isPublicAgentDataKey) {
267
+ return new Response(JSON.stringify(toPublicAgent(result)), {
268
+ status: 200,
269
+ headers: corsHeaders,
270
+ });
271
+ }
272
+
273
+ if (!authedUserId && isShareDataKey && isCommunityShare(result)) {
274
+ return new Response(JSON.stringify({ ...result }), {
275
+ status: 200,
276
+ headers: corsHeaders,
277
+ });
278
+ }
279
+
280
+ const access = await authorizeActorRecordAccess({
281
+ action: "read",
282
+ actor: userActor(authedUserId),
283
+ dbKey,
284
+ record: result,
285
+ });
286
+ if (!access.allowed) {
287
+ return new Response(
288
+ JSON.stringify({
289
+ error: "Forbidden",
290
+ message: "You do not have access to this resource",
291
+ }),
292
+ { status: 403, headers: corsHeaders }
293
+ );
294
+ }
295
+
296
+ if (isPublicAgentDataKey && !isAgentOwner(result, authedUserId)) {
297
+ return new Response(JSON.stringify(toPublicAgent(result)), {
298
+ status: 200,
299
+ headers: corsHeaders,
300
+ });
301
+ }
302
+ return new Response(JSON.stringify({ ...result }), {
303
+ status: 200,
304
+ headers: corsHeaders,
305
+ });
306
+ } catch (error: any) {
307
+ if (isLevelNotFoundError(error)) {
308
+ return new Response(
309
+ JSON.stringify({
310
+ error: "Not Found",
311
+ message: `Resource with id ${dbKey} not found`,
312
+ }),
313
+ { status: 404, headers: corsHeaders }
314
+ );
315
+ }
316
+ console.error("Database fetch error:", error);
317
+ return new Response(
318
+ JSON.stringify({
319
+ error: "Internal Server Error",
320
+ message: "Failed to fetch data",
321
+ }),
322
+ { status: 500, headers: corsHeaders }
323
+ );
324
+ }
325
+ };
@@ -0,0 +1,211 @@
1
+ import { isSystemAdmin } from "core/init";
2
+ import { shareKey } from "database/keys";
3
+ import serverDb from "./db";
4
+ import { resolveKeyOwnerId } from "./writeAuthority";
5
+
6
+ export type AccessAction = "read" | "write" | "delete" | "manage";
7
+
8
+ type AccessInput = {
9
+ action: AccessAction;
10
+ actionUserId?: string | null;
11
+ dbKey: string;
12
+ record?: any;
13
+ };
14
+
15
+ export type AccessResult = {
16
+ allowed: boolean;
17
+ reason?: string;
18
+ keyOwnerId?: string | null;
19
+ recordOwnerId?: string | null;
20
+ spaceId?: string | null;
21
+ };
22
+
23
+ const SPACE_PREFIX = "space-";
24
+ const SPACE_MEMBER_PREFIX = "space-member-";
25
+
26
+ const normalizeSpaceId = (spaceId?: unknown): string | null => {
27
+ if (typeof spaceId !== "string") return null;
28
+ const trimmed = spaceId.trim();
29
+ if (!trimmed) return null;
30
+ return trimmed.startsWith(SPACE_PREFIX)
31
+ ? trimmed.slice(SPACE_PREFIX.length)
32
+ : trimmed;
33
+ };
34
+
35
+ const isShareRecordKey = (dbKey: unknown): boolean => {
36
+ const value = String(dbKey || "");
37
+ return value.startsWith("share-") || shareKey.isShareKey(value);
38
+ };
39
+
40
+ const getRecordOwnerId = (record: any): string | null =>
41
+ (typeof record?.ownerId === "string" && record.ownerId) ||
42
+ (typeof record?.userId === "string" && record.userId) ||
43
+ (typeof record?.tenantId === "string" && record.tenantId) ||
44
+ (isShareRecordKey(record?.dbKey) && typeof record?.meta?.authorId === "string"
45
+ ? record.meta.authorId
46
+ : null) ||
47
+ null;
48
+
49
+ const getSpaceIdFromRecord = (record: any): string | null =>
50
+ normalizeSpaceId(record?.spaceId);
51
+
52
+ const getSpaceIdFromKey = (dbKey: string): string | null => {
53
+ if (!dbKey.startsWith(SPACE_PREFIX) || dbKey.startsWith(SPACE_MEMBER_PREFIX)) {
54
+ return null;
55
+ }
56
+ return normalizeSpaceId(dbKey);
57
+ };
58
+
59
+ const isAgentOwnedByUser = (agentKey: unknown, userId: string): boolean =>
60
+ typeof agentKey === "string" &&
61
+ Boolean(userId) &&
62
+ resolveKeyOwnerId(agentKey) === userId;
63
+
64
+ const isPublicAgentRecord = (dbKey: string, record: any): boolean =>
65
+ (dbKey.startsWith("agent-pub-") || dbKey.startsWith("cybot-pub-")) &&
66
+ record?.isPublic === true;
67
+
68
+ const isCommunityShareRecord = (dbKey: string, record: any): boolean =>
69
+ (dbKey.startsWith("share-") || shareKey.isShareKey(dbKey)) &&
70
+ record?.meta?.visibility === "community";
71
+
72
+ const hasSpaceMembership = async (
73
+ userId: string,
74
+ spaceId: string | null
75
+ ): Promise<boolean> => {
76
+ const normalized = normalizeSpaceId(spaceId);
77
+ if (!userId || !normalized) return false;
78
+
79
+ try {
80
+ const membership = await serverDb.get(
81
+ `${SPACE_MEMBER_PREFIX}${userId}-${normalized}`
82
+ );
83
+ if (membership) return true;
84
+ } catch {
85
+ // Fall through to the space record. Some older data only has members[].
86
+ }
87
+
88
+ try {
89
+ const space = await serverDb.get(`${SPACE_PREFIX}${normalized}`);
90
+ return (
91
+ space?.ownerId === userId ||
92
+ (Array.isArray(space?.members) && space.members.includes(userId))
93
+ );
94
+ } catch {
95
+ return false;
96
+ }
97
+ };
98
+
99
+ const getContentKeyCandidates = (dbKey: string, record: any): string[] => {
100
+ const candidates = [
101
+ dbKey,
102
+ typeof record?.dbKey === "string" ? record.dbKey : "",
103
+ typeof record?.contentKey === "string" ? record.contentKey : "",
104
+ typeof record?.key === "string" ? record.key : "",
105
+ ].filter(Boolean);
106
+
107
+ return [...new Set(candidates)];
108
+ };
109
+
110
+ const isRecordLinkedFromMemberSpace = async (
111
+ userId: string,
112
+ dbKey: string,
113
+ record: any
114
+ ): Promise<string | null> => {
115
+ if (!userId || !dbKey) return null;
116
+
117
+ const contentKeys = getContentKeyCandidates(dbKey, record);
118
+ const prefix = `${SPACE_MEMBER_PREFIX}${userId}-`;
119
+
120
+ try {
121
+ for await (const [, membership] of serverDb.iterator({
122
+ gte: prefix,
123
+ lte: prefix + "\uffff",
124
+ })) {
125
+ const spaceId = normalizeSpaceId(membership?.spaceId);
126
+ if (!spaceId) continue;
127
+
128
+ try {
129
+ const space = await serverDb.get(`${SPACE_PREFIX}${spaceId}`);
130
+ const contents = space?.contents;
131
+ if (!contents || typeof contents !== "object") continue;
132
+
133
+ if (contentKeys.some((contentKey) => Boolean(contents[contentKey]))) {
134
+ return spaceId;
135
+ }
136
+ } catch {
137
+ // Ignore stale membership pointers.
138
+ }
139
+ }
140
+ } catch {
141
+ return null;
142
+ }
143
+
144
+ return null;
145
+ };
146
+
147
+ export async function authorizeRecordAccess({
148
+ action,
149
+ actionUserId,
150
+ dbKey,
151
+ record,
152
+ }: AccessInput): Promise<AccessResult> {
153
+ if (!actionUserId) {
154
+ return { allowed: false, reason: "missing_actor" };
155
+ }
156
+
157
+ if (isSystemAdmin(actionUserId)) {
158
+ return { allowed: true };
159
+ }
160
+
161
+ if (action === "read") {
162
+ if (isPublicAgentRecord(dbKey, record)) return { allowed: true };
163
+ if (isCommunityShareRecord(dbKey, record)) return { allowed: true };
164
+ }
165
+
166
+ const keyOwnerId = resolveKeyOwnerId(dbKey);
167
+ const recordOwnerId = getRecordOwnerId({ ...record, dbKey });
168
+ const spaceId = getSpaceIdFromRecord(record) ?? getSpaceIdFromKey(dbKey);
169
+
170
+ if (keyOwnerId && recordOwnerId && keyOwnerId !== recordOwnerId) {
171
+ return {
172
+ allowed: false,
173
+ reason: "owner_mismatch",
174
+ keyOwnerId,
175
+ recordOwnerId,
176
+ spaceId,
177
+ };
178
+ }
179
+
180
+ if (keyOwnerId === actionUserId || recordOwnerId === actionUserId) {
181
+ return { allowed: true, keyOwnerId, recordOwnerId, spaceId };
182
+ }
183
+
184
+ if (
185
+ record?.ownerType === "agent" &&
186
+ isAgentOwnedByUser(recordOwnerId, actionUserId)
187
+ ) {
188
+ return { allowed: true, keyOwnerId, recordOwnerId, spaceId };
189
+ }
190
+
191
+ if (await hasSpaceMembership(actionUserId, spaceId)) {
192
+ return { allowed: true, keyOwnerId, recordOwnerId, spaceId };
193
+ }
194
+
195
+ const linkedSpaceId = await isRecordLinkedFromMemberSpace(
196
+ actionUserId,
197
+ dbKey,
198
+ record
199
+ );
200
+ if (linkedSpaceId) {
201
+ return { allowed: true, keyOwnerId, recordOwnerId, spaceId: linkedSpaceId };
202
+ }
203
+
204
+ return {
205
+ allowed: false,
206
+ reason: "not_owner_or_member",
207
+ keyOwnerId,
208
+ recordOwnerId,
209
+ spaceId,
210
+ };
211
+ }
@@ -0,0 +1,110 @@
1
+ import { API_ENDPOINTS } from "../config";
2
+ import { handleQuery } from "./query";
3
+ import { handleWrite } from "./write";
4
+ import { handlePatch } from "./patch";
5
+ import { maybeProxyDatabaseRequestToCore } from "./coreDataProxy";
6
+ import { createPageServer } from "render/page/server/createPage";
7
+ import { handleToken } from "auth/server/token";
8
+ import { logger } from "auth/server/shared";
9
+
10
+ export const databaseRequest = async (req, res, url) => {
11
+ const pathname = url.pathname;
12
+ const getIdFromPath = (prefix) => {
13
+ const start = pathname.indexOf(prefix) + prefix.length;
14
+ const end =
15
+ pathname.indexOf("/", start) !== -1
16
+ ? pathname.indexOf("/", start)
17
+ : undefined;
18
+ return pathname.slice(start, end);
19
+ };
20
+ if (pathname.startsWith(API_ENDPOINTS.DATABASE)) {
21
+ const operation = pathname
22
+ .substr(API_ENDPOINTS.DATABASE.length)
23
+ .split("/")[1];
24
+
25
+ switch (operation) {
26
+ case "write": {
27
+ const proxied = await maybeProxyDatabaseRequestToCore({
28
+ req,
29
+ pathname,
30
+ search: url.search,
31
+ coreBaseUrl: process.env.NOLO_SERVER_CORE_BASE_URL,
32
+ });
33
+ if (proxied) {
34
+ return proxied;
35
+ }
36
+ return handleWrite(req, res);
37
+ }
38
+ case "patch": {
39
+ const proxied = await maybeProxyDatabaseRequestToCore({
40
+ req,
41
+ pathname,
42
+ search: url.search,
43
+ coreBaseUrl: process.env.NOLO_SERVER_CORE_BASE_URL,
44
+ });
45
+ if (proxied) {
46
+ return proxied;
47
+ }
48
+ req.params = { id: getIdFromPath("/api/v1/db/patch/") };
49
+ return handlePatch(req, res);
50
+ }
51
+ case "query":
52
+ req.params = { userId: getIdFromPath("/api/v1/db/query/") };
53
+ return handleQuery(req, res);
54
+ case "page": {
55
+ // /api/v1/db/page/create
56
+ const subOperation = pathname.split("/")[5]; // page 后面的操作
57
+ if (subOperation === "create") {
58
+ try {
59
+ // 鉴权
60
+ const user = await handleToken(req, res);
61
+ const userId = user?.userId;
62
+ if (!userId) {
63
+ return res.status(401).json({
64
+ success: false,
65
+ message: "Unauthorized: userId not found",
66
+ });
67
+ }
68
+
69
+ const { title, content, slateData, spaceId, categoryId, tags } =
70
+ req.body || {};
71
+
72
+ const result = await createPageServer({
73
+ userId,
74
+ title,
75
+ content,
76
+ slateData,
77
+ spaceId,
78
+ categoryId,
79
+ tags,
80
+ });
81
+
82
+ logger.info({
83
+ event: "page_created",
84
+ userId,
85
+ dbKey: result.dbKey,
86
+ title: result.title,
87
+ });
88
+
89
+ return res.status(200).json(result);
90
+ } catch (error: any) {
91
+ logger.error({
92
+ event: "create_page_failed",
93
+ error: error.message,
94
+ });
95
+ return res.status(500).json({
96
+ success: false,
97
+ message: error.message || "Failed to create page",
98
+ });
99
+ }
100
+ }
101
+ return new Response("Unknown page operation", { status: 404 });
102
+ }
103
+ default:
104
+ return new Response(
105
+ JSON.stringify({ error: "Not Found", message: `Unknown database operation: ${operation}` }),
106
+ { status: 404, headers: { "Content-Type": "application/json" } }
107
+ );
108
+ }
109
+ }
110
+ };
@@ -0,0 +1,67 @@
1
+ import serverDb from "./db";
2
+
3
+ const SPACE_MEMBER_PREFIX = "space-member-";
4
+ const SPACE_PREFIX = "space-";
5
+
6
+ export const parseSpaceMemberKey = (
7
+ dbKey: string
8
+ ): { memberUserId: string; spaceId: string } | null => {
9
+ if (typeof dbKey !== "string" || !dbKey.startsWith(SPACE_MEMBER_PREFIX)) {
10
+ return null;
11
+ }
12
+
13
+ const rest = dbKey.slice(SPACE_MEMBER_PREFIX.length);
14
+ const lastDash = rest.lastIndexOf("-");
15
+ if (lastDash <= 0 || lastDash === rest.length - 1) {
16
+ return null;
17
+ }
18
+
19
+ return {
20
+ memberUserId: rest.slice(0, lastDash),
21
+ spaceId: rest.slice(lastDash + 1),
22
+ };
23
+ };
24
+
25
+ const loadSpaceRecord = async (spaceId: string): Promise<any | null> => {
26
+ try {
27
+ return await serverDb.get(`${SPACE_PREFIX}${spaceId}`);
28
+ } catch {
29
+ return null;
30
+ }
31
+ };
32
+
33
+ export const canWriteSpaceMemberRecord = async ({
34
+ dbKey,
35
+ actionUserId,
36
+ }: {
37
+ dbKey: string;
38
+ actionUserId?: string | null;
39
+ }): Promise<boolean> => {
40
+ const parsed = parseSpaceMemberKey(dbKey);
41
+ if (!parsed || !actionUserId) {
42
+ return false;
43
+ }
44
+
45
+ const space = await loadSpaceRecord(parsed.spaceId);
46
+ return Array.isArray(space?.members) && space.members.includes(actionUserId);
47
+ };
48
+
49
+ export const canDeleteSpaceMemberRecord = async ({
50
+ dbKey,
51
+ actionUserId,
52
+ }: {
53
+ dbKey: string;
54
+ actionUserId?: string | null;
55
+ }): Promise<boolean> => {
56
+ const parsed = parseSpaceMemberKey(dbKey);
57
+ if (!parsed || !actionUserId) {
58
+ return false;
59
+ }
60
+
61
+ if (parsed.memberUserId === actionUserId) {
62
+ return true;
63
+ }
64
+
65
+ const space = await loadSpaceRecord(parsed.spaceId);
66
+ return typeof space?.ownerId === "string" && space.ownerId === actionUserId;
67
+ };