nolo-cli 0.1.7 → 0.1.9

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 (247) hide show
  1. package/README.md +107 -5
  2. package/agentRuntimeCommands.ts +464 -0
  3. package/ai/agent/_executeModel.ts +118 -0
  4. package/ai/agent/agentSlice.ts +525 -0
  5. package/ai/agent/appWorkingMemory.ts +126 -0
  6. package/ai/agent/avatarUtils.ts +24 -0
  7. package/ai/agent/buildEditingContext.ts +373 -0
  8. package/ai/agent/buildSystemPrompt.ts +532 -0
  9. package/ai/agent/cleanAgentMessages.ts +140 -0
  10. package/ai/agent/cliChatClient.ts +119 -0
  11. package/ai/agent/cliExecutor.ts +733 -0
  12. package/ai/agent/cliPrompt.ts +10 -0
  13. package/ai/agent/contextCompiler.ts +107 -0
  14. package/ai/agent/contextLayerContract.ts +44 -0
  15. package/ai/agent/createAgentSchema.ts +234 -0
  16. package/ai/agent/executeToolCall.ts +58 -0
  17. package/ai/agent/fetchAgentContexts.ts +42 -0
  18. package/ai/agent/generatePrompt.ts +3 -0
  19. package/ai/agent/getFullChatContextKeys.ts +168 -0
  20. package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
  21. package/ai/agent/hooks/useAgentConfig.ts +61 -0
  22. package/ai/agent/hooks/useAgentDialog.ts +35 -0
  23. package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
  24. package/ai/agent/hooks/usePublicAgents.ts +473 -0
  25. package/ai/agent/machineRunPermissions.ts +95 -0
  26. package/ai/agent/persistMessageWithFixedId.ts +37 -0
  27. package/ai/agent/planSlice.ts +259 -0
  28. package/ai/agent/referenceUtils.ts +229 -0
  29. package/ai/agent/runAgentBackground.ts +238 -0
  30. package/ai/agent/runAgentClientLoop.ts +138 -0
  31. package/ai/agent/runtimeGuidance.ts +97 -0
  32. package/ai/agent/runtimeServerBase.ts +37 -0
  33. package/ai/agent/server/fetchPublicAgents.ts +128 -0
  34. package/ai/agent/startParallelAgentStreams.ts +424 -0
  35. package/ai/agent/startupProtocol.ts +53 -0
  36. package/ai/agent/streamAgentChatTurn.ts +1278 -0
  37. package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
  38. package/ai/agent/types.ts +71 -0
  39. package/ai/agent/utils/imageOutput.ts +33 -0
  40. package/ai/agent/utils/sortUtils.ts +250 -0
  41. package/ai/agent/web/referencePickerUtils.ts +146 -0
  42. package/ai/ai.locale.ts +1075 -0
  43. package/ai/chat/accumulateToolCallChunks.ts +95 -0
  44. package/ai/chat/fetchUtils.native.ts +276 -0
  45. package/ai/chat/fetchUtils.ts +153 -0
  46. package/ai/chat/parseApiError.ts +64 -0
  47. package/ai/chat/parseMultilineSSE.ts +95 -0
  48. package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
  49. package/ai/chat/sendOpenAICompletionsRequest.ts +703 -0
  50. package/ai/chat/sendOpenAIResponseRequest.ts +491 -0
  51. package/ai/chat/shouldUseServerProxy.ts +18 -0
  52. package/ai/chat/sseClient.native.ts +91 -0
  53. package/ai/chat/sseClient.ts +67 -0
  54. package/ai/chat/streamReader.native.ts +31 -0
  55. package/ai/chat/streamReader.ts +62 -0
  56. package/ai/chat/updateTotalUsage.ts +72 -0
  57. package/ai/context/buildReferenceContext.ts +437 -0
  58. package/ai/context/calculateContextUsage.ts +133 -0
  59. package/ai/context/retention.ts +165 -0
  60. package/ai/context/tokenUtils.ts +78 -0
  61. package/ai/index.ts +1 -0
  62. package/ai/llm/calculateGeminiImageTokens.ts +57 -0
  63. package/ai/llm/deepinfra.ts +28 -0
  64. package/ai/llm/fireworks.ts +50 -0
  65. package/ai/llm/generateRequestBody.ts +165 -0
  66. package/ai/llm/getModelContextWindow.ts +84 -0
  67. package/ai/llm/getNoloKey.ts +31 -0
  68. package/ai/llm/getPricing.ts +199 -0
  69. package/ai/llm/hooks/useModelPricing.ts +75 -0
  70. package/ai/llm/imagePricing.ts +40 -0
  71. package/ai/llm/isResponseAPIModel.ts +13 -0
  72. package/ai/llm/mimo.ts +71 -0
  73. package/ai/llm/mistral.ts +22 -0
  74. package/ai/llm/modelAvatar.ts +427 -0
  75. package/ai/llm/models.ts +45 -0
  76. package/ai/llm/openrouterModels.ts +269 -0
  77. package/ai/llm/providers.ts +306 -0
  78. package/ai/llm/reasoningModels.ts +28 -0
  79. package/ai/llm/types.ts +59 -0
  80. package/ai/llm/usageRequestOptions.ts +59 -0
  81. package/ai/memory/capture.ts +148 -0
  82. package/ai/memory/consolidate.ts +104 -0
  83. package/ai/memory/delete.ts +147 -0
  84. package/ai/memory/overlay.ts +84 -0
  85. package/ai/memory/query.ts +38 -0
  86. package/ai/memory/queryShared.ts +160 -0
  87. package/ai/memory/rank.ts +105 -0
  88. package/ai/memory/recentRelationshipRecap.ts +249 -0
  89. package/ai/memory/remember.ts +167 -0
  90. package/ai/memory/runtime.ts +76 -0
  91. package/ai/memory/store.ts +20 -0
  92. package/ai/memory/storeShared.ts +76 -0
  93. package/ai/memory/types.ts +46 -0
  94. package/ai/memory/understanding.ts +349 -0
  95. package/ai/memory/understandingGreeting.ts +264 -0
  96. package/ai/messages/type.ts +20 -0
  97. package/ai/policy/personalizationDialog.ts +333 -0
  98. package/ai/policy/runtimePolicy.ts +440 -0
  99. package/ai/policy/selfUpdateFields.ts +48 -0
  100. package/ai/policy/types.ts +64 -0
  101. package/ai/skills/referenceRuntime.ts +274 -0
  102. package/ai/skills/skillDiagnostics.ts +251 -0
  103. package/ai/skills/skillDocBuilder.ts +139 -0
  104. package/ai/skills/skillDocProtocol.ts +434 -0
  105. package/ai/skills/skillReferenceSummary.ts +63 -0
  106. package/ai/skills/skillSummaryMarker.ts +26 -0
  107. package/ai/token/calculatePrice.ts +544 -0
  108. package/ai/token/db.ts +98 -0
  109. package/ai/token/externalToolCost.ts +330 -0
  110. package/ai/token/hooks/useRecords.ts +65 -0
  111. package/ai/token/missingUsageEstimate.ts +42 -0
  112. package/ai/token/modelUsageQuery.ts +252 -0
  113. package/ai/token/normalizeUsage.ts +84 -0
  114. package/ai/token/openaiImageGenerationUsage.ts +56 -0
  115. package/ai/token/prepareTokenUsageData.ts +88 -0
  116. package/ai/token/query.ts +88 -0
  117. package/ai/token/queryUserTokens.ts +59 -0
  118. package/ai/token/resolveBillingTarget.ts +52 -0
  119. package/ai/token/saveTokenRecord.ts +53 -0
  120. package/ai/token/serverDialogProjection.ts +78 -0
  121. package/ai/token/serverTokenWriter.ts +143 -0
  122. package/ai/token/stats.ts +21 -0
  123. package/ai/token/tokenThunks.ts +24 -0
  124. package/ai/token/types.ts +93 -0
  125. package/ai/tools/agent/agentTools.ts +176 -0
  126. package/ai/tools/agent/agentUpdateShared.ts +311 -0
  127. package/ai/tools/agent/callAgentTool.ts +139 -0
  128. package/ai/tools/agent/createAgentTool.ts +512 -0
  129. package/ai/tools/agent/createDialogTool.ts +69 -0
  130. package/ai/tools/agent/createSkillAgentTool.ts +62 -0
  131. package/ai/tools/agent/parallelBudget.ts +221 -0
  132. package/ai/tools/agent/presets/appBuilderPreset.ts +145 -0
  133. package/ai/tools/agent/runLlmTool.ts +96 -0
  134. package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
  135. package/ai/tools/agent/skillAgentArgs.ts +106 -0
  136. package/ai/tools/agent/skillAgentPreset.ts +89 -0
  137. package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
  138. package/ai/tools/agent/updateAgentTool.ts +96 -0
  139. package/ai/tools/agent/updateSelfTool.ts +113 -0
  140. package/ai/tools/amazonProductScraperTool.ts +86 -0
  141. package/ai/tools/apifyActorClient.ts +45 -0
  142. package/ai/tools/appEditGuard.ts +372 -0
  143. package/ai/tools/appReadSnapshot.ts +153 -0
  144. package/ai/tools/appTools.ts +1549 -0
  145. package/ai/tools/applyEditTool.ts +256 -0
  146. package/ai/tools/applyLineEditsTool.ts +312 -0
  147. package/ai/tools/browserTools/click.ts +33 -0
  148. package/ai/tools/browserTools/closeSession.ts +29 -0
  149. package/ai/tools/browserTools/common.ts +27 -0
  150. package/ai/tools/browserTools/openSession.ts +48 -0
  151. package/ai/tools/browserTools/readContent.ts +38 -0
  152. package/ai/tools/browserTools/selectOption.ts +46 -0
  153. package/ai/tools/browserTools/typeText.ts +42 -0
  154. package/ai/tools/category/createCategoryTool.ts +66 -0
  155. package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
  156. package/ai/tools/category/updateContentCategoryTool.ts +75 -0
  157. package/ai/tools/cfBrowserTools.ts +319 -0
  158. package/ai/tools/cfSpeechToTextTool.ts +49 -0
  159. package/ai/tools/checkEnvTool.ts +65 -0
  160. package/ai/tools/cloudflareCrawlTool.ts +289 -0
  161. package/ai/tools/codeSearchTool.ts +111 -0
  162. package/ai/tools/codeTools.ts +101 -0
  163. package/ai/tools/createDocTool.ts +132 -0
  164. package/ai/tools/createPlanTool.ts +999 -0
  165. package/ai/tools/createSkillDocTool.ts +155 -0
  166. package/ai/tools/createWorkflowTool.ts +154 -0
  167. package/ai/tools/deepseekOcrTool.ts +34 -0
  168. package/ai/tools/delayTool.ts +31 -0
  169. package/ai/tools/deleteSpacesTool.ts +325 -0
  170. package/ai/tools/deleteSpacesToolModel.ts +159 -0
  171. package/ai/tools/devReloadUtils.ts +29 -0
  172. package/ai/tools/dialogMessageSearch.ts +137 -0
  173. package/ai/tools/doctorSkillTool.ts +72 -0
  174. package/ai/tools/ecommerceScraperTool.ts +86 -0
  175. package/ai/tools/emailTools.ts +549 -0
  176. package/ai/tools/evalSkillTool.ts +92 -0
  177. package/ai/tools/exaSearchTool.ts +64 -0
  178. package/ai/tools/execBashTool.ts +379 -0
  179. package/ai/tools/executeSqlTool.ts +192 -0
  180. package/ai/tools/fetchWebpageSupport.ts +309 -0
  181. package/ai/tools/fetchWebpageTool.ts +84 -0
  182. package/ai/tools/geminiImagePreviewTool.ts +361 -0
  183. package/ai/tools/generateDocxTool.ts +215 -0
  184. package/ai/tools/googleSearchScraperTool.ts +106 -0
  185. package/ai/tools/importDataTool.ts +133 -0
  186. package/ai/tools/importSkillTool.ts +162 -0
  187. package/ai/tools/index.ts +1858 -0
  188. package/ai/tools/listFilesTool.ts +82 -0
  189. package/ai/tools/listUserSpacesTool.ts +113 -0
  190. package/ai/tools/modelUsageTools.ts +142 -0
  191. package/ai/tools/olmOcrTool.ts +34 -0
  192. package/ai/tools/openaiImageTool.ts +218 -0
  193. package/ai/tools/paddleOcrTool.ts +34 -0
  194. package/ai/tools/prepareTools.ts +23 -0
  195. package/ai/tools/readDocTool.ts +84 -0
  196. package/ai/tools/readFileTool.ts +211 -0
  197. package/ai/tools/readTool.ts +163 -0
  198. package/ai/tools/readXPostTool.ts +233 -0
  199. package/ai/tools/rememberMemoryTool.ts +84 -0
  200. package/ai/tools/remotionVideoTool.ts +151 -0
  201. package/ai/tools/searchDialogMessagesTool.ts +222 -0
  202. package/ai/tools/searchRepoTool.ts +115 -0
  203. package/ai/tools/searchWorkspaceTool.ts +259 -0
  204. package/ai/tools/skillFollowup.ts +86 -0
  205. package/ai/tools/surfWeatherTool.ts +169 -0
  206. package/ai/tools/table/addTableRowTool.ts +217 -0
  207. package/ai/tools/table/createTableTool.ts +315 -0
  208. package/ai/tools/table/rowTools.ts +366 -0
  209. package/ai/tools/table/schemaTools.ts +244 -0
  210. package/ai/tools/table/shareTableTool.ts +148 -0
  211. package/ai/tools/table/toolShared.ts +129 -0
  212. package/ai/tools/toolApiClient.ts +198 -0
  213. package/ai/tools/toolNameAliases.ts +57 -0
  214. package/ai/tools/toolResultError.ts +42 -0
  215. package/ai/tools/toolRunSlice.ts +303 -0
  216. package/ai/tools/toolSchemaCompatibility.ts +53 -0
  217. package/ai/tools/toolVisibility.ts +4 -0
  218. package/ai/tools/types.ts +20 -0
  219. package/ai/tools/uiAskChoiceTool.ts +104 -0
  220. package/ai/tools/updateContentTitleTool.ts +84 -0
  221. package/ai/tools/updateDocTool.ts +105 -0
  222. package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
  223. package/ai/tools/whisperTool.ts +77 -0
  224. package/ai/tools/writeFileTool.ts +210 -0
  225. package/ai/tools/youtubeScraperTool.ts +116 -0
  226. package/ai/tools/ziweiChartTool.ts +678 -0
  227. package/ai/types.ts +55 -0
  228. package/ai/workflow/workflowExecutor.ts +323 -0
  229. package/ai/workflow/workflowSlice.ts +73 -0
  230. package/ai/workflow/workflowTypes.ts +106 -0
  231. package/client/agentRun.ts +198 -167
  232. package/client/compactDialog.ts +222 -0
  233. package/commandRegistry.ts +14 -0
  234. package/connector-experimental/capabilities.ts +73 -0
  235. package/connector-experimental/codexBinary.ts +41 -0
  236. package/connector-experimental/heartbeatLoop.ts +22 -0
  237. package/connector-experimental/index.ts +5 -0
  238. package/connector-experimental/machineInfo.ts +46 -0
  239. package/connector-experimental/protocol.ts +54 -0
  240. package/connectorWebSocketTarget.ts +29 -0
  241. package/defaultServer.ts +1 -0
  242. package/index.ts +158 -104
  243. package/machineCommands.ts +382 -0
  244. package/package.json +12 -2
  245. package/tui/readlineWorkspace.ts +50 -0
  246. package/tui/session.ts +40 -2
  247. package/updateCommands.ts +70 -5
@@ -0,0 +1,264 @@
1
+ import { chooseMemoryOwners, loadMemoryCandidatesFromDb } from "./queryShared";
2
+ import type { MemoryFacet, MemoryItem } from "./types";
3
+
4
+ export interface UnderstandingGreetingResolution {
5
+ item: MemoryItem | null;
6
+ anchorItems: MemoryItem[];
7
+ followUpItem: MemoryItem | null;
8
+ }
9
+
10
+ const UNDERSTANDING_TAG = "understanding-memory";
11
+
12
+ const facetPriority: Record<MemoryFacet, number> = {
13
+ unfinished: 5,
14
+ tension: 4,
15
+ preference: 3,
16
+ style: 2,
17
+ goal: 1,
18
+ };
19
+
20
+ const anchorFacetPriority: Record<MemoryFacet, number> = {
21
+ preference: 3,
22
+ style: 2,
23
+ goal: 1,
24
+ tension: 0,
25
+ unfinished: 0,
26
+ };
27
+
28
+ const normalizeText = (value: string): string =>
29
+ value.trim().replace(/[。!?!?]+$/u, "").trim();
30
+
31
+ const stripPrefix = (text: string, prefix: string): string =>
32
+ text.startsWith(prefix) ? text.slice(prefix.length).trim() : text;
33
+
34
+ const toTimestamp = (item: MemoryItem): number => {
35
+ const parsed = Date.parse(item.lastActivatedAt || item.createdAt);
36
+ return Number.isFinite(parsed) ? parsed : 0;
37
+ };
38
+
39
+ const isUnderstandingItem = (item: MemoryItem): boolean =>
40
+ Array.isArray(item.tags) && item.tags.includes(UNDERSTANDING_TAG);
41
+
42
+ const sortByKindAndTime = (left: MemoryItem, right: MemoryItem): number => {
43
+ if (left.kind !== right.kind) return left.kind === "semantic" ? -1 : 1;
44
+ return toTimestamp(right) - toTimestamp(left);
45
+ };
46
+
47
+ const sameNormalizedContent = (left: string, right: string): boolean =>
48
+ normalizeText(left).toLowerCase() === normalizeText(right).toLowerCase();
49
+
50
+ const pickAnchorItems = (items: MemoryItem[]): MemoryItem[] => {
51
+ const ranked = [...items]
52
+ .filter((item) => (item.facet ? anchorFacetPriority[item.facet] > 0 : false))
53
+ .sort((left, right) => {
54
+ const leftFacet = left.facet ? anchorFacetPriority[left.facet] ?? 0 : 0;
55
+ const rightFacet = right.facet ? anchorFacetPriority[right.facet] ?? 0 : 0;
56
+ if (leftFacet !== rightFacet) return rightFacet - leftFacet;
57
+ if (left.kind !== right.kind) return left.kind === "semantic" ? -1 : 1;
58
+ if (left.content.length !== right.content.length) {
59
+ return left.content.length - right.content.length;
60
+ }
61
+ return toTimestamp(right) - toTimestamp(left);
62
+ });
63
+
64
+ const selected: MemoryItem[] = [];
65
+ for (const item of ranked) {
66
+ if (
67
+ selected.some((existing) =>
68
+ sameNormalizedContent(existing.content, item.content) ||
69
+ (existing.facet && item.facet && existing.facet === item.facet)
70
+ )
71
+ ) {
72
+ continue;
73
+ }
74
+ selected.push(item);
75
+ if (selected.length >= 2) break;
76
+ }
77
+ return selected;
78
+ };
79
+
80
+ const pickFollowUpItem = (items: MemoryItem[]): MemoryItem | null =>
81
+ [...items]
82
+ .filter((item) => item.facet === "unfinished" || item.facet === "tension")
83
+ .sort((left, right) => {
84
+ const leftFacet = left.facet ? facetPriority[left.facet] ?? 0 : 0;
85
+ const rightFacet = right.facet ? facetPriority[right.facet] ?? 0 : 0;
86
+ if (leftFacet !== rightFacet) return rightFacet - leftFacet;
87
+ return sortByKindAndTime(left, right);
88
+ })[0] ?? null;
89
+
90
+ export const resolveUnderstandingGreetingMemory = async (input: {
91
+ db: any;
92
+ userId?: string | null;
93
+ spaceId?: string | null;
94
+ agentKey: string;
95
+ }): Promise<UnderstandingGreetingResolution> => {
96
+ const owners = chooseMemoryOwners({
97
+ userId: input.userId,
98
+ spaceId: input.spaceId,
99
+ });
100
+ if (owners.length === 0) {
101
+ return { item: null, anchorItems: [], followUpItem: null };
102
+ }
103
+
104
+ const items = await loadMemoryCandidatesFromDb(input.db, {
105
+ owners,
106
+ subjects: [{ subjectType: "agent", subjectId: input.agentKey }],
107
+ kinds: ["semantic", "episodic"],
108
+ ownerLimit: 40,
109
+ });
110
+
111
+ const understandingItems = items.filter(isUnderstandingItem);
112
+ if (understandingItems.length === 0) {
113
+ return { item: null, anchorItems: [], followUpItem: null };
114
+ }
115
+
116
+ const ranked = [...understandingItems].sort((left, right) => {
117
+ const leftFacet = left.facet ? facetPriority[left.facet] ?? 0 : 0;
118
+ const rightFacet = right.facet ? facetPriority[right.facet] ?? 0 : 0;
119
+ if (leftFacet !== rightFacet) return rightFacet - leftFacet;
120
+ return sortByKindAndTime(left, right);
121
+ });
122
+
123
+ const anchorItems = pickAnchorItems(understandingItems);
124
+ const followUpItem = pickFollowUpItem(understandingItems);
125
+ const item = followUpItem ?? anchorItems[0] ?? ranked[0] ?? null;
126
+
127
+ return {
128
+ item,
129
+ anchorItems,
130
+ followUpItem,
131
+ };
132
+ };
133
+
134
+ const renderLeadClause = (item: MemoryItem): string => {
135
+ const content = normalizeText(item.content);
136
+ switch (item.facet) {
137
+ case "unfinished":
138
+ return `我记得你上次还没定下来:${stripPrefix(content, "还没决定")}`;
139
+ case "tension":
140
+ return `我记得你上次还在权衡${stripPrefix(content, "在权衡")}`;
141
+ case "style":
142
+ case "preference":
143
+ return `我记得你上次${content}`;
144
+ case "goal":
145
+ return `我记得你上次想推进的是${content}`;
146
+ default:
147
+ return `我记得你上次提过${content}`;
148
+ }
149
+ };
150
+
151
+ const renderAnchorFragment = (item: MemoryItem): string => {
152
+ const content = normalizeText(item.content);
153
+ switch (item.facet) {
154
+ case "preference":
155
+ if (content.startsWith("更在意")) {
156
+ return `更在意的是${stripPrefix(content, "更在意")}`;
157
+ }
158
+ if (content.startsWith("更关心")) {
159
+ return `更关心的是${stripPrefix(content, "更关心")}`;
160
+ }
161
+ if (content.startsWith("更怕")) {
162
+ return `更怕${stripPrefix(content, "更怕")}`;
163
+ }
164
+ if (content.startsWith("不想")) {
165
+ return `不想${stripPrefix(content, "不想")}`;
166
+ }
167
+ if (content.startsWith("不希望")) {
168
+ return `不希望${stripPrefix(content, "不希望")}`;
169
+ }
170
+ return content;
171
+ case "style":
172
+ if (content.startsWith("不喜欢")) {
173
+ return `不太喜欢${stripPrefix(content, "不喜欢")}`;
174
+ }
175
+ if (content.startsWith("更喜欢")) {
176
+ return `更喜欢${stripPrefix(content, "更喜欢")}`;
177
+ }
178
+ return content;
179
+ case "goal":
180
+ if (content.startsWith("想先")) {
181
+ return `想先${stripPrefix(content, "想先")}`;
182
+ }
183
+ return `想推进的是${content}`;
184
+ default:
185
+ return content;
186
+ }
187
+ };
188
+
189
+ const renderAnchorSentence = (items: MemoryItem[]): string | null => {
190
+ if (items.length === 0) return null;
191
+ const fragments = items
192
+ .map(renderAnchorFragment)
193
+ .map((fragment) => fragment.trim())
194
+ .filter(Boolean);
195
+ if (fragments.length === 0) return null;
196
+ if (fragments.length === 1) {
197
+ return `我记得你上次${fragments[0]}。`;
198
+ }
199
+ return `我记得你上次${fragments[0]},也${fragments[1]}。`;
200
+ };
201
+
202
+ const splitTradeoff = (value: string): [string, string] | null => {
203
+ const normalized = normalizeText(value)
204
+ .replace(/^在权衡/u, "")
205
+ .replace(/^还没决定/u, "")
206
+ .replace(/^还不确定/u, "")
207
+ .replace(/^还没想好/u, "")
208
+ .trim();
209
+ if (!normalized.includes("还是")) return null;
210
+
211
+ const [left, right] = normalized.split(/\s*还是/u, 2);
212
+ const normalizedLeft = normalizeText((left ?? "").replace(/[,,::]+$/u, "").trim());
213
+ const normalizedRight = normalizeText((right ?? "").trim());
214
+ if (!normalizedLeft || !normalizedRight) return null;
215
+ return [normalizedLeft, normalizedRight];
216
+ };
217
+
218
+ const renderFollowUpLine = (item: MemoryItem | null): string => {
219
+ if (!item) {
220
+ return "如果你想,我们可以接着上次那个点;如果今天是新问题,也直接说。";
221
+ }
222
+
223
+ const tradeoff = splitTradeoff(item.content);
224
+ if (tradeoff) {
225
+ return `如果你愿意,我们可以接着看:${tradeoff[0]},还是${tradeoff[1]}。如果今天是新问题,也直接说。`;
226
+ }
227
+
228
+ const content = normalizeText(item.content);
229
+ switch (item.facet) {
230
+ case "unfinished":
231
+ return `如果你愿意,我们可以接着把${stripPrefix(content, "还没决定")}定下来;如果今天是新问题,也直接说。`;
232
+ case "tension":
233
+ return `如果你愿意,我们可以接着看${stripPrefix(content, "在权衡")};如果今天是新问题,也直接说。`;
234
+ case "goal":
235
+ return `如果你愿意,我们可以继续推进${stripPrefix(content, "想先")};如果今天是新问题,也直接说。`;
236
+ default:
237
+ return "如果你想,我们可以接着上次那个点;如果今天是新问题,也直接说。";
238
+ }
239
+ };
240
+
241
+ export const mergeGreetingWithUnderstandingMemory = (input: {
242
+ greetingText?: string;
243
+ resolution?: UnderstandingGreetingResolution | null;
244
+ item?: MemoryItem | null;
245
+ }): string | null => {
246
+ const greetingText = typeof input.greetingText === "string"
247
+ ? input.greetingText.trim()
248
+ : "";
249
+ const resolution = input.resolution ?? null;
250
+ const item = input.item ?? resolution?.item ?? null;
251
+ if (!greetingText && !item) return null;
252
+ if (!item) return greetingText || null;
253
+
254
+ const anchorItems = resolution?.anchorItems ?? [];
255
+ const followUpItem = resolution?.followUpItem ?? item;
256
+ const leadLine = renderAnchorSentence(anchorItems) ?? `${renderLeadClause(item)}。`;
257
+ const suffix = renderFollowUpLine(followUpItem);
258
+ const memoryBlock = `欢迎回来。${leadLine}\n${suffix}`;
259
+
260
+ if (!greetingText) {
261
+ return memoryBlock;
262
+ }
263
+ return `${greetingText}\n\n${memoryBlock}`;
264
+ };
@@ -0,0 +1,20 @@
1
+ import { ClaudeContent } from "integrations/anthropic/type";
2
+
3
+ export interface InputMessage {
4
+ content:
5
+ | Array<{
6
+ text?: string;
7
+ type: string;
8
+ image_url?: {
9
+ url: string;
10
+ };
11
+ }>
12
+ | string;
13
+ role: string;
14
+ }
15
+
16
+ export interface OutputMessage {
17
+ role: string;
18
+ content: string | ClaudeContent;
19
+ images: string[];
20
+ }
@@ -0,0 +1,333 @@
1
+ import { noloAgentId } from "core/init";
2
+ import { createDialog, initDialog } from "chat/dialog/dialogSlice";
3
+ import { buildDialogUrl } from "chat/dialog/dialogUrl";
4
+ import { prepareAndPersistMessage } from "chat/messages/messageSlice";
5
+ import {
6
+ uiAskChoiceFunc,
7
+ uiAskChoiceFunctionSchema,
8
+ } from "ai/tools/uiAskChoiceTool";
9
+ import type { AgentRuntimeOptions } from "ai/agent/types";
10
+
11
+ export type PersonalizationDialogSource = "signup" | "home";
12
+
13
+ /**
14
+ * 当前阶段,这个入口只负责“对话式编辑 User Overlay Profile”。
15
+ * 未来它可以演进成引导用户创建属于自己的一个或多个 AI,
16
+ * 但那会把偏好采集和 agent 创建耦合在一起,所以现在明确不做。
17
+ *
18
+ * 这里刻意继续复用 nolo,而不是单独拆一个 personalization-agent、
19
+ * 也不是把它建模成 skill / first-class mode:
20
+ * - 用户感知上,这仍然是“nolo 帮我做个性化设置”,一致感比内部抽象更重要;
21
+ * - 当前只有这一条特殊入口,单独引入 agent/skill/mode 只会把同一套约束换个名字再实现一遍;
22
+ * - 真正变化的是这个 dialog 的目标、允许的工具和策略上下文,所以先用 dialog category 承载。
23
+ *
24
+ * 以后如果出现第二、第三个长期存在的“特殊流程对话”,再考虑把这类 category 升级成统一抽象。
25
+ */
26
+ export const PERSONALIZATION_DIALOG_CATEGORY = "user-overlay-profile";
27
+ export const PERSONALIZATION_DIALOG_EXTRA_TOOLS = [
28
+ "ui_ask_choice",
29
+ "updateUserPreferenceProfile",
30
+ ] as const;
31
+
32
+ type SupportedCopyLocale = "en" | "zh-CN" | "zh-TW" | "ja";
33
+
34
+ const resolveCopyLocale = (language?: string | null): SupportedCopyLocale => {
35
+ const normalized = (language || "").toLowerCase();
36
+
37
+ if (normalized.startsWith("zh-tw") || normalized.startsWith("zh-hk")) {
38
+ return "zh-TW";
39
+ }
40
+
41
+ if (normalized.startsWith("zh")) {
42
+ return "zh-CN";
43
+ }
44
+
45
+ if (normalized.startsWith("ja")) {
46
+ return "ja";
47
+ }
48
+
49
+ return "en";
50
+ };
51
+
52
+ export const buildPersonalizationDialogTitle = (
53
+ language?: string | null,
54
+ source: PersonalizationDialogSource = "home"
55
+ ): string => {
56
+ const locale = resolveCopyLocale(language);
57
+
58
+ const titles = {
59
+ en:
60
+ source === "signup" ? "Set Up Your AI Preferences" : "Adjust AI Preferences",
61
+ "zh-CN": source === "signup" ? "开始设置你的 AI 偏好" : "调整 AI 偏好",
62
+ "zh-TW": source === "signup" ? "開始設定你的 AI 偏好" : "調整 AI 偏好",
63
+ ja:
64
+ source === "signup" ? "AI の好みを設定する" : "AI の好みを調整する",
65
+ } as const;
66
+
67
+ return titles[locale];
68
+ };
69
+
70
+ export const buildPersonalizationStarterPrompt = (
71
+ language?: string | null,
72
+ source: PersonalizationDialogSource = "home"
73
+ ): string => {
74
+ const locale = resolveCopyLocale(language);
75
+
76
+ if (locale === "zh-CN") {
77
+ return source === "signup"
78
+ ? "我刚完成注册。请你作为我的个性化 AI 偏好助手,用最多 3 个简短问题帮我确认这三件事:1. 我偏好的交流语气;2. 值得沉淀的结果应该如何处理;3. 回答问题时是否应该读取当前空间,以及读取到什么程度。请保持简洁、像真正的对话,不要一次把所有选项都堆给我。在我确认之前,不要创建文档,不要修改或创建任何 AI。最后请把建议整理成明确设置项,方便我确认或调整。"
79
+ : "我想通过对话调整我的 AI 偏好。请你作为个性化 AI 偏好助手,用最多 3 个简短问题帮我重新确认:1. 我偏好的交流语气;2. 值得沉淀的结果应该如何处理;3. 回答问题时是否应该读取当前空间,以及读取到什么程度。请保持简洁、像真正的对话。在我确认之前,不要创建文档,不要修改或创建任何 AI。最后请把建议整理成明确设置项,方便我确认或调整。";
80
+ }
81
+
82
+ if (locale === "zh-TW") {
83
+ return source === "signup"
84
+ ? "我剛完成註冊。請你作為我的個人化 AI 偏好助手,用最多 3 個簡短問題幫我確認這三件事:1. 我偏好的交流語氣;2. 值得沉澱的結果應該如何處理;3. 回答問題時是否應該讀取目前空間,以及讀取到什麼程度。請保持簡潔、像真正的對話,不要一次把所有選項都丟給我。在我確認之前,不要建立文件,不要修改或建立任何 AI。最後請把建議整理成明確設定項,方便我確認或調整。"
85
+ : "我想透過對話調整我的 AI 偏好。請你作為個人化 AI 偏好助手,用最多 3 個簡短問題幫我重新確認:1. 我偏好的交流語氣;2. 值得沉澱的結果應該如何處理;3. 回答問題時是否應該讀取目前空間,以及讀取到什麼程度。請保持簡潔、像真正的對話。在我確認之前,不要建立文件,不要修改或建立任何 AI。最後請把建議整理成明確設定項,方便我確認或調整。";
86
+ }
87
+
88
+ if (locale === "ja") {
89
+ return source === "signup"
90
+ ? "登録したばかりです。あなたは私の AI 設定アシスタントとして、最大 3 つの短い質問で次の 3 点を確認してください。1. 好みの話し方 2. 価値のある結果をどのように知識化するか 3. 回答時に現在のスペースを読むべきか、どの程度読むか。長い説明ではなく自然な対話で進めてください。私が確認する前に、ドキュメントを作成したり、AI を作成・更新したりしないでください。最後に、確認しやすい設定項目として整理してください。"
91
+ : "会話しながら AI の好みを調整したいです。あなたは設定アシスタントとして、最大 3 つの短い質問で次の 3 点を再確認してください。1. 好みの話し方 2. 価値のある結果をどのように知識化するか 3. 回答時に現在のスペースを読むべきか、どの程度読むか。自然な対話で簡潔に進めてください。私が確認する前に、ドキュメントを作成したり、AI を作成・更新したりしないでください。最後に、確認しやすい設定項目として整理してください。";
92
+ }
93
+
94
+ return source === "signup"
95
+ ? "I just signed up. Act as my AI personalization assistant and use at most three short questions to confirm three things: 1. the tone I prefer, 2. how reusable results should be captured, and 3. whether you should read the current space when answering, and how aggressively. Keep it concise and conversational. Do not create documents, and do not create or modify any AI before I confirm. End by summarizing the recommended settings so I can confirm or adjust them."
96
+ : "I want to adjust my AI preferences through conversation. Act as my AI personalization assistant and use at most three short questions to reconfirm three things: 1. the tone I prefer, 2. how reusable results should be captured, and 3. whether you should read the current space when answering, and how aggressively. Keep it concise and conversational. Do not create documents, and do not create or modify any AI before I confirm. End by summarizing the recommended settings so I can confirm or adjust them.";
97
+ };
98
+
99
+ export const buildPersonalizationRuntimeOptions = (
100
+ runtimeOptions?: AgentRuntimeOptions
101
+ ): AgentRuntimeOptions => ({
102
+ ...runtimeOptions,
103
+ extraTools: Array.from(
104
+ new Set([
105
+ ...(runtimeOptions?.extraTools ?? []),
106
+ ...PERSONALIZATION_DIALOG_EXTRA_TOOLS,
107
+ ])
108
+ ),
109
+ });
110
+
111
+ export const buildPersonalizationDialogPolicyContext = (): string =>
112
+ [
113
+ "当前对话是“用户个性化设置”模式,不是普通闲聊。",
114
+ "你的目标是用简短对话帮助用户确认 tone、knowledge_capture、space_context 这三项偏好。",
115
+ "如果用户先介绍自己、工作方式或长期沟通偏好,请把这些可复用信息整理成简洁的 globalPrompt 草案,并在用户确认后通过 updateUserPreferenceProfile 保存。",
116
+ "优先一次只问一个问题;当存在清晰互斥选项时,优先调用 ui_ask_choice。",
117
+ "收集到足够信息后,调用 updateUserPreferenceProfile 保存结果,然后用自然语言总结已保存的设置。",
118
+ "保存完成后,要提醒用户:以后也可以在设置里修改 globalPrompt 和这些偏好,或者再次打开这个入口继续调整。",
119
+ "个性化设置完成后,可顺手引导用户尝试 1 到 2 个相关功能,例如首页快捷对话、创建笔记、创建 AI,但不要一次推荐太多。",
120
+ "除非用户明确要求,否则不要创建文档,不要创建或修改任何 agent。",
121
+ ].join("\n");
122
+
123
+ const buildPersonalizationOpeningChoice = (
124
+ language?: string | null,
125
+ source: PersonalizationDialogSource = "home"
126
+ ) => {
127
+ const locale = resolveCopyLocale(language);
128
+
129
+ if (locale === "zh-CN") {
130
+ return {
131
+ question:
132
+ source === "signup"
133
+ ? [
134
+ "你好,我会帮你完成 **AI 偏好设置确认**。",
135
+ "",
136
+ "你可以直接快速设置,也可以先做个自我介绍,我会顺手帮你整理成全局提示词。",
137
+ "",
138
+ "你想怎么开始?",
139
+ ].join("\n")
140
+ : [
141
+ "我们来调整一下你的 **AI 偏好设置**。",
142
+ "",
143
+ "你可以直接快速设置,也可以先做个自我介绍,我会顺手帮你整理成全局提示词。",
144
+ "",
145
+ "你想怎么开始?",
146
+ ].join("\n"),
147
+ choices: [
148
+ {
149
+ id: "quick_setup",
150
+ label: "直接快速设置",
151
+ userMessage:
152
+ "直接开始快速设置吧。请用最多三个简短问题帮我确定语气、知识沉淀和空间读取偏好。",
153
+ },
154
+ {
155
+ id: "intro_first",
156
+ label: "先做自我介绍",
157
+ userMessage:
158
+ "我想先做个自我介绍。请根据我的介绍帮我整理一段适合写进全局提示词的内容,在我确认后保存,然后继续完成语气、知识沉淀和空间读取设置。",
159
+ },
160
+ {
161
+ id: "show_capabilities",
162
+ label: "先看看你能做什么",
163
+ userMessage:
164
+ "先用很短的话告诉我 nolo 在这里还能帮我做什么,然后继续带我完成个性化设置。",
165
+ },
166
+ ],
167
+ };
168
+ }
169
+
170
+ if (locale === "zh-TW") {
171
+ return {
172
+ question:
173
+ source === "signup"
174
+ ? [
175
+ "你好,我會幫你完成 **AI 偏好設定確認**。",
176
+ "",
177
+ "你可以直接快速設定,也可以先做個自我介紹,我會順手幫你整理成全域提示詞。",
178
+ "",
179
+ "你想怎麼開始?",
180
+ ].join("\n")
181
+ : [
182
+ "我們來調整一下你的 **AI 偏好設定**。",
183
+ "",
184
+ "你可以直接快速設定,也可以先做個自我介紹,我會順手幫你整理成全域提示詞。",
185
+ "",
186
+ "你想怎麼開始?",
187
+ ].join("\n"),
188
+ choices: [
189
+ {
190
+ id: "quick_setup",
191
+ label: "直接快速設定",
192
+ userMessage:
193
+ "直接開始快速設定吧。請用最多三個簡短問題幫我確定語氣、知識沉澱與空間讀取偏好。",
194
+ },
195
+ {
196
+ id: "intro_first",
197
+ label: "先做自我介紹",
198
+ userMessage:
199
+ "我想先做個自我介紹。請根據我的介紹幫我整理一段適合寫進全域提示詞的內容,在我確認後保存,然後繼續完成語氣、知識沉澱與空間讀取設定。",
200
+ },
201
+ {
202
+ id: "show_capabilities",
203
+ label: "先看看你能做什麼",
204
+ userMessage:
205
+ "先用很短的話告訴我 nolo 在這裡還能幫我做什麼,然後繼續帶我完成個人化設定。",
206
+ },
207
+ ],
208
+ };
209
+ }
210
+
211
+ if (locale === "ja") {
212
+ return {
213
+ question:
214
+ source === "signup"
215
+ ? "**AI の好み設定** を進めます。\n\nすぐに設定を始めることもできますし、先に自己紹介してもらえれば、その内容を global prompt にまとめられます。\n\nどう始めますか?"
216
+ : "**AI の好み設定** を調整しましょう。\n\nすぐに設定を始めることもできますし、先に自己紹介してもらえれば、その内容を global prompt にまとめられます。\n\nどう始めますか?",
217
+ choices: [
218
+ {
219
+ id: "quick_setup",
220
+ label: "すぐに設定する",
221
+ userMessage:
222
+ "すぐに設定を始めたいです。最大3つの短い質問で、話し方、知識化、スペース読取の好みを確認してください。",
223
+ },
224
+ {
225
+ id: "intro_first",
226
+ label: "先に自己紹介する",
227
+ userMessage:
228
+ "先に自己紹介したいです。私の紹介をもとに global prompt に入れる短い文を作って、確認後に保存し、そのあと残りの設定も進めてください。",
229
+ },
230
+ {
231
+ id: "show_capabilities",
232
+ label: "何ができるか先に見る",
233
+ userMessage:
234
+ "先に nolo がここで何をしてくれるのかを短く教えてください。そのあと個人設定を続けてください。",
235
+ },
236
+ ],
237
+ };
238
+ }
239
+
240
+ return {
241
+ question:
242
+ source === "signup"
243
+ ? "Let's set up your **AI preferences**.\n\nWe can either start with a quick setup, or you can introduce yourself first and I'll turn that into a reusable global prompt.\n\nHow do you want to begin?"
244
+ : "Let's adjust your **AI preferences**.\n\nWe can either start with a quick setup, or you can introduce yourself first and I'll turn that into a reusable global prompt.\n\nHow do you want to begin?",
245
+ choices: [
246
+ {
247
+ id: "quick_setup",
248
+ label: "Start quick setup",
249
+ userMessage:
250
+ "Start the quick setup. Ask me at most three short questions to confirm my tone, knowledge capture, and space-reading preferences.",
251
+ },
252
+ {
253
+ id: "intro_first",
254
+ label: "Let me introduce myself first",
255
+ userMessage:
256
+ "I want to introduce myself first. Please turn my introduction into a concise global prompt draft, save it after I confirm, and then continue the rest of the personalization setup.",
257
+ },
258
+ {
259
+ id: "show_capabilities",
260
+ label: "Show what nolo can do first",
261
+ userMessage:
262
+ "First, briefly show me what nolo can help me do here, then continue the personalization setup.",
263
+ },
264
+ ],
265
+ };
266
+ };
267
+
268
+ export interface StartPersonalizationDialogParams {
269
+ dispatch: any;
270
+ navigate: (to: string, options?: { state?: Record<string, unknown> }) => void;
271
+ language?: string | null;
272
+ source?: PersonalizationDialogSource;
273
+ }
274
+
275
+ export const startPersonalizationDialog = async ({
276
+ dispatch,
277
+ navigate,
278
+ language,
279
+ source = "home",
280
+ }: StartPersonalizationDialogParams): Promise<string> => {
281
+ const result = await dispatch(
282
+ createDialog({
283
+ cybots: [noloAgentId],
284
+ skipGreeting: true,
285
+ title: buildPersonalizationDialogTitle(language, source),
286
+ category: PERSONALIZATION_DIALOG_CATEGORY,
287
+ })
288
+ ).unwrap();
289
+
290
+ const dialogKey = (result as { dbKey?: string } | undefined)?.dbKey ?? "";
291
+ const dialogSpaceId =
292
+ (result as { spaceId?: string | null } | undefined)?.spaceId ?? null;
293
+
294
+ if (!dialogKey) {
295
+ throw new Error("Personalization dialog key is missing.");
296
+ }
297
+
298
+ await dispatch(initDialog(dialogKey)).unwrap();
299
+ const openingChoice = buildPersonalizationOpeningChoice(language, source);
300
+ const toolResult = await uiAskChoiceFunc(
301
+ {
302
+ question: openingChoice.question,
303
+ choices: openingChoice.choices,
304
+ blocking: true,
305
+ },
306
+ { dispatch }
307
+ );
308
+
309
+ await dispatch(
310
+ prepareAndPersistMessage({
311
+ message: {
312
+ role: "tool",
313
+ toolName: uiAskChoiceFunctionSchema.name,
314
+ cybotKey: noloAgentId,
315
+ content: toolResult.rawData,
316
+ displayData: toolResult.displayData,
317
+ },
318
+ dialogConfig: {
319
+ id: dialogKey.split("-").at(-1),
320
+ dbKey: dialogKey,
321
+ },
322
+ })
323
+ );
324
+
325
+ navigate(buildDialogUrl(dialogKey, dialogSpaceId), {
326
+ state: {
327
+ isNew: true,
328
+ personalizationSource: source,
329
+ },
330
+ });
331
+
332
+ return dialogKey;
333
+ };