nolo-cli 0.1.41 → 0.1.43

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 (136) hide show
  1. package/README.md +27 -12
  2. package/agent-runtime/agentThread.ts +222 -0
  3. package/agent-runtime/agentThreadAdmission.ts +74 -0
  4. package/agent-runtime/dialogWritePlan.ts +42 -0
  5. package/agent-runtime/hostAdapter.ts +1 -0
  6. package/agent-runtime/index.ts +43 -0
  7. package/agent-runtime/localLoop.ts +31 -2
  8. package/agent-runtime/localToolPolicy.ts +1 -0
  9. package/agent-runtime/localWorkspaceTools.ts +1258 -78
  10. package/agent-runtime/noloWorkspaceTools.ts +20 -1
  11. package/agent-runtime/runtimeToolPolicy.ts +1 -5
  12. package/agent-runtime/runtimeToolSurface.ts +1 -0
  13. package/agentAliases.ts +29 -9
  14. package/agentInternalCommandEntries.ts +11 -3
  15. package/agentMachineCommands.ts +1 -0
  16. package/agentRecordCommands.ts +2 -2
  17. package/agentRecordHelpers.ts +59 -6
  18. package/agentRunCommand.ts +84 -19
  19. package/ai/agent/buildSystemPrompt.ts +14 -0
  20. package/ai/agent/cliExecutor.ts +202 -43
  21. package/ai/agent/createAgentSchema.ts +2 -2
  22. package/ai/agent/liveAudioModel.ts +1 -1
  23. package/ai/agent/streamAgentChatTurn.ts +154 -20
  24. package/ai/agent/streamAgentChatTurnUtils.ts +63 -0
  25. package/ai/ai.locale.ts +70 -0
  26. package/ai/chat/inlineImageUrlsForCustomProvider.ts +7 -2
  27. package/ai/chat/sendOpenAICompletionsRequest.ts +25 -1
  28. package/ai/llm/kimi.ts +0 -1
  29. package/ai/llm/openrouterModels.ts +27 -2
  30. package/ai/memory/policy.ts +49 -0
  31. package/ai/memory/queryShared.ts +10 -4
  32. package/ai/memory/recentRelationshipRecap.ts +4 -0
  33. package/ai/memory/remember.ts +48 -104
  34. package/ai/memory/scope.ts +74 -0
  35. package/ai/memory/types.ts +6 -0
  36. package/ai/memory/understanding.ts +10 -18
  37. package/ai/policy/runtimePolicy.ts +95 -23
  38. package/ai/tools/agent/agentTools.ts +0 -21
  39. package/ai/tools/agent/startAgentDialogTool.ts +2 -1
  40. package/ai/tools/index.ts +2 -20
  41. package/ai/tools/prepareTools.ts +171 -1
  42. package/ai/tools/readXhsProfileTool.ts +80 -69
  43. package/ai/tools/table/createTableTool.ts +11 -0
  44. package/ai/tools/toolPacks.ts +19 -0
  45. package/ai/types.ts +3 -0
  46. package/app/favorite/deletedFavoriteProjection.ts +31 -0
  47. package/app/hooks/deleteDbKey.ts +24 -3
  48. package/app/i18n/translations/interface.locale.ts +48 -16
  49. package/app/types/appSummary.ts +4 -0
  50. package/app/utils/desktopAgentRuntimeTurnClient.ts +2 -0
  51. package/auth/adminPermissions.ts +64 -4
  52. package/auth/routes.ts +5 -0
  53. package/chat/chat.locale.ts +4 -0
  54. package/chat/dialog/actions/handleSendMessageAction.ts +7 -2
  55. package/chat/dialog/dialogAttachmentCleanup.ts +135 -0
  56. package/chat/dialog/dialogSlice.ts +117 -4
  57. package/chat/messages/activityCompletion.ts +97 -0
  58. package/chat/messages/imagePayloadPersistence.ts +22 -0
  59. package/chat/messages/messageContent.ts +20 -16
  60. package/chat/messages/messageSlice.ts +20 -1
  61. package/chat/messages/toolThunks.ts +32 -11
  62. package/chat/messages/types.ts +8 -1
  63. package/chat/messages/web/toolDisplayName.ts +686 -0
  64. package/cli/agentAliases.ts +29 -9
  65. package/cli/agentInternalCommandEntries.ts +11 -3
  66. package/cli/agentMachineCommands.ts +1 -0
  67. package/cli/agentRecordCommands.ts +2 -2
  68. package/cli/agentRecordHelpers.ts +59 -6
  69. package/cli/agentRunCommand.ts +84 -19
  70. package/cli/client/agentRun.ts +65 -253
  71. package/cli/client/localRuntimeAdapter.ts +179 -53
  72. package/cli/commandRegistry.ts +7 -4
  73. package/cli/dialogAttachmentCleanup.ts +151 -0
  74. package/cli/dialogCommands.ts +163 -61
  75. package/cli/dialogInternalCommandEntries.ts +1 -1
  76. package/cli/docCreateCommands.ts +345 -0
  77. package/cli/docInternalCommandEntries.ts +11 -0
  78. package/cli/docPageHelpers.ts +80 -0
  79. package/cli/docSkillHelpers.ts +67 -0
  80. package/cli/docSpaceHelpers.ts +46 -0
  81. package/cli/internalCommandEntries.ts +4 -0
  82. package/cli/machineCommands.ts +18 -34
  83. package/cli/memoryCommands.ts +253 -0
  84. package/cli/memoryInternalCommandEntries.ts +9 -0
  85. package/cli/qoderUsageProbe.ts +252 -0
  86. package/cli/scriptCommandEntries.ts +0 -3
  87. package/cli/secretScan.ts +31 -0
  88. package/cli/tableCommands.ts +248 -13
  89. package/cli/workflowInternalCommandEntries.ts +2 -3
  90. package/client/agentRun.test.ts +68 -84
  91. package/client/agentRun.ts +65 -253
  92. package/client/localRuntimeAdapter.test.ts +505 -74
  93. package/client/localRuntimeAdapter.ts +179 -53
  94. package/commandRegistry.ts +7 -4
  95. package/connector-experimental/capabilities.ts +10 -9
  96. package/create/space/sidebarVisibleTypes.ts +19 -0
  97. package/dialogAttachmentCleanup.ts +151 -0
  98. package/dialogCommands.ts +163 -61
  99. package/dialogInternalCommandEntries.ts +1 -1
  100. package/docCreateCommands.ts +345 -0
  101. package/docInternalCommandEntries.ts +11 -0
  102. package/docPageHelpers.ts +80 -0
  103. package/docSkillHelpers.ts +67 -0
  104. package/docSpaceHelpers.ts +46 -0
  105. package/integrations/google/models.ts +4 -4
  106. package/integrations/openai/generateOpenAIRequestBody.ts +0 -3
  107. package/integrations/xhs-reader/types.ts +36 -3
  108. package/integrations/xhs-reader/url.ts +24 -1
  109. package/internalCommandEntries.ts +4 -0
  110. package/machineCommands.ts +18 -34
  111. package/memoryCommands.ts +253 -0
  112. package/memoryInternalCommandEntries.ts +9 -0
  113. package/package.json +141 -114
  114. package/qoderUsageProbe.ts +252 -0
  115. package/render/contentIcon/types.ts +167 -0
  116. package/render/page/docSlice.ts +30 -3
  117. package/render/page/types.ts +2 -0
  118. package/render/table/createTableAction.ts +4 -0
  119. package/render/table/tableSlice.ts +61 -0
  120. package/render/table/types.ts +10 -0
  121. package/scriptCommandEntries.ts +0 -3
  122. package/secretScan.ts +31 -0
  123. package/server/handlers/agentRun/types.ts +24 -23
  124. package/tableCommands.ts +248 -13
  125. package/workflowInternalCommandEntries.ts +2 -3
  126. package/ai/tools/agent/taskRunTool.ts +0 -112
  127. package/cli/client/taskRunPrompt.ts +0 -27
  128. package/cli/taskRunCommand.ts +0 -265
  129. package/client/taskRunPrompt.ts +0 -27
  130. package/integrations/xhs-reader/analyze/profileAnalyzer.ts +0 -205
  131. package/integrations/xhs-reader/backends/playwrightProfileCollector.ts +0 -624
  132. package/integrations/xhs-reader/bridge/readXhsProfileWithBridge.ts +0 -227
  133. package/integrations/xhs-reader/client/xhsWebApiClient.ts +0 -229
  134. package/integrations/xhs-reader/normalize.ts +0 -390
  135. package/integrations/xhs-reader/orchestrator.ts +0 -178
  136. package/taskRunCommand.ts +0 -265
@@ -73,9 +73,6 @@ import { getIsDesktopApp } from "../../app/utils/env";
73
73
  import { runDesktopAgentRuntimeTurn } from "../../app/utils/desktopAgentRuntimeTurnClient";
74
74
  import { resolveRuntimeToolSurfaceForAgent } from "../../agent-runtime/runtimeToolSurface";
75
75
 
76
- const BUILTIN_NOLO_AGENT_ID = "01NOLOAPPBLD000000019KCKT0";
77
- const BUILTIN_NOLO_AGENT_KEY = `agent-pub-${BUILTIN_NOLO_AGENT_ID}`;
78
-
79
76
  const buildParallelMessageMetadata = (
80
77
  agentConfig: Pick<Agent, "dbKey" | "name">,
81
78
  runtimeOptions?: AgentRuntimeOptions,
@@ -116,23 +113,141 @@ const filterMessagesForParallelBranch = (
116
113
  });
117
114
  };
118
115
 
119
- const isDesktopBuiltinNoloAgent = (
120
- requestedAgentKey: string,
116
+ const buildDesktopRuntimeToolMessagesForUi = ({
117
+ dialogId,
118
+ turnMessages,
119
+ }: {
120
+ dialogId: string;
121
+ turnMessages?: any[];
122
+ }) => {
123
+ if (!Array.isArray(turnMessages) || turnMessages.length === 0) return [];
124
+
125
+ const toolNamesByCallId = new Map<string, string>();
126
+ const activityByCallId = new Map<string, any>();
127
+ const projected: any[] = [];
128
+
129
+ for (const message of turnMessages) {
130
+ if (Array.isArray(message?.tool_calls)) {
131
+ for (const call of message.tool_calls) {
132
+ const callId = typeof call?.id === "string" ? call.id.trim() : "";
133
+ const toolName =
134
+ typeof call?.function?.name === "string"
135
+ ? call.function.name.trim()
136
+ : "";
137
+ if (callId && toolName) toolNamesByCallId.set(callId, toolName);
138
+
139
+ // Extract _activity from tool call arguments for UI projection
140
+ if (callId) {
141
+ try {
142
+ const args =
143
+ typeof call?.function?.arguments === "string"
144
+ ? JSON.parse(call.function.arguments)
145
+ : call?.function?.arguments;
146
+ const rawActivity =
147
+ args &&
148
+ typeof args === "object" &&
149
+ !Array.isArray(args)
150
+ ? args._activity
151
+ : undefined;
152
+ const activity =
153
+ rawActivity &&
154
+ typeof rawActivity === "object" &&
155
+ !Array.isArray(rawActivity)
156
+ ? rawActivity as Record<string, unknown>
157
+ : undefined;
158
+ const legacyTitle =
159
+ typeof activity?.title === "string" && activity.title.trim();
160
+ const action = activity?.action;
161
+ const actionTitle =
162
+ action &&
163
+ typeof action === "object" &&
164
+ !Array.isArray(action) &&
165
+ typeof (action as Record<string, unknown>).title === "string" &&
166
+ ((action as Record<string, unknown>).title as string).trim();
167
+ const plan = activity?.plan;
168
+ const hasPlan =
169
+ plan &&
170
+ typeof plan === "object" &&
171
+ !Array.isArray(plan) &&
172
+ Array.isArray((plan as Record<string, unknown>).phases) &&
173
+ ((plan as Record<string, unknown>).phases as unknown[]).some((phase) => {
174
+ if (!phase || typeof phase !== "object" || Array.isArray(phase)) return false;
175
+ const rawPhase = phase as Record<string, unknown>;
176
+ return typeof rawPhase.title === "string" && !!rawPhase.title.trim();
177
+ });
178
+ if (activity && (legacyTitle || actionTitle || hasPlan)) {
179
+ activityByCallId.set(callId, args._activity);
180
+ }
181
+ } catch {
182
+ // Ignore malformed tool call arguments
183
+ }
184
+ }
185
+ }
186
+ continue;
187
+ }
188
+
189
+ if (message?.role !== "tool") continue;
190
+ const toolCallId =
191
+ typeof message.tool_call_id === "string"
192
+ ? message.tool_call_id.trim()
193
+ : "";
194
+ const metadata =
195
+ message.tool_result_metadata &&
196
+ typeof message.tool_result_metadata === "object"
197
+ ? message.tool_result_metadata
198
+ : {};
199
+ const metadataToolName =
200
+ typeof metadata.toolName === "string" ? metadata.toolName.trim() : "";
201
+ const toolName =
202
+ metadataToolName ||
203
+ (toolCallId ? toolNamesByCallId.get(toolCallId) : "") ||
204
+ "tool";
205
+ const { key: dbKey, messageId } = createDialogMessageKeyAndId(dialogId);
206
+
207
+ // Resolve activity: prefer tool result metadata.activity, fall back to tool call _activity
208
+ const resultActivity =
209
+ metadata.activity && typeof metadata.activity === "object"
210
+ ? metadata.activity
211
+ : undefined;
212
+ const callActivity = toolCallId
213
+ ? activityByCallId.get(toolCallId)
214
+ : undefined;
215
+ const activity = resultActivity || callActivity;
216
+
217
+ // Merge activity into metadata so UI readers (ToolMessageGroup / extractActivityLines)
218
+ // that look at message.metadata.activity see it regardless of surface.
219
+ const mergedMetadata = activity
220
+ ? { ...metadata, activity }
221
+ : metadata;
222
+
223
+ projected.push({
224
+ id: messageId,
225
+ dialogId,
226
+ dbKey,
227
+ role: "tool",
228
+ content: message.content ?? "",
229
+ isStreaming: false,
230
+ toolName,
231
+ ...(toolCallId ? { toolCallId } : {}),
232
+ ...(Object.keys(mergedMetadata).length ? { metadata: mergedMetadata } : {}),
233
+ });
234
+ }
235
+
236
+ return projected;
237
+ };
238
+
239
+ const extractDesktopRuntimeToolCallsForUi = (turnMessages?: any[]) => {
240
+ if (!Array.isArray(turnMessages) || turnMessages.length === 0) return [];
241
+ return turnMessages.flatMap((message) =>
242
+ Array.isArray(message?.tool_calls) ? message.tool_calls : []
243
+ );
244
+ };
245
+
246
+ const shouldUseDesktopLocalRuntime = (
121
247
  agentConfig: Partial<Agent> | null | undefined,
122
248
  ) => {
123
249
  if (!getIsDesktopApp()) return false;
124
- const candidates = [
125
- requestedAgentKey,
126
- agentConfig?.dbKey,
127
- agentConfig?.agentKey,
128
- agentConfig?.id,
129
- ];
130
- return candidates.some((value) =>
131
- typeof value === "string" &&
132
- (value === BUILTIN_NOLO_AGENT_KEY ||
133
- value === BUILTIN_NOLO_AGENT_ID ||
134
- value.endsWith(`-${BUILTIN_NOLO_AGENT_ID}`))
135
- );
250
+ return agentConfig?.apiSource !== "cli";
136
251
  };
137
252
 
138
253
  const resolveWebAgentRuntimeToolSurface = (
@@ -261,6 +376,7 @@ const EMPTY_DYNAMIC_CONTEXTS = {
261
376
  historyContext: "",
262
377
  editingContext: null,
263
378
  appWorkingMemory: null,
379
+ memoryOverlay: null,
264
380
  dialogSummary: null,
265
381
  proactiveSummary: null,
266
382
  referenceKeys: [],
@@ -454,9 +570,11 @@ const canUseQuickChatEmptyDynamicContexts = (
454
570
  userInput: string | any[],
455
571
  runtimeOptions: AgentRuntimeOptions | undefined,
456
572
  dialogConfig: DialogConfig | null,
573
+ agentConfig?: Agent | null,
457
574
  ) => {
458
575
  if (!quickChatPerfStartedAt) return false;
459
576
  if (runtimeOptions) return false;
577
+ if (agentConfig?.tools?.includes("rememberMemory")) return false;
460
578
  if (!isSimpleTextInput(userInput)) return false;
461
579
  if (dialogConfig?.referenceKeys?.length) return false;
462
580
  return true;
@@ -478,6 +596,7 @@ const shouldDisableQuickChatToolsForDirectAnswer = (
478
596
  userInput,
479
597
  runtimeOptions,
480
598
  dialogConfig,
599
+ undefined,
481
600
  )) {
482
601
  return false;
483
602
  }
@@ -609,7 +728,8 @@ export const streamAgentChatTurnHandler = async (
609
728
  // runtimeBinding selects the bound machine. Without it, custom provider
610
729
  // requests stay on the normal OpenAI-compatible path, where
611
730
  // useServerProxy decides server proxy vs current-client direct fetch.
612
- if (agentConfig.apiSource === "cli" || boundMachineId) {
731
+ if (agentConfig.apiSource === "cli" || (boundMachineId && !getIsDesktopApp())) {
732
+ console.info("[streamAgentChatTurn] Triggered CLI/machine route. apiSource:", agentConfig.apiSource, "boundMachineId:", boundMachineId, "agentKey:", agentKey);
613
733
  const currentState = getState() as RootState;
614
734
  const w =
615
735
  typeof globalThis !== "undefined" && (globalThis as any).window
@@ -859,6 +979,7 @@ export const streamAgentChatTurnHandler = async (
859
979
  };
860
980
 
861
981
  const initialSessionId = await ensureCliSession();
982
+ console.info("[streamAgentChatTurn] Calling CLI turn stream. Session ID:", initialSessionId);
862
983
  let resp = await createCliChatTurnStream(
863
984
  {
864
985
  getState,
@@ -872,8 +993,10 @@ export const streamAgentChatTurnHandler = async (
872
993
  );
873
994
 
874
995
  if (!resp.ok && resp.status === 404) {
996
+ console.warn("[streamAgentChatTurn] CLI Session 404. Re-creating session...");
875
997
  cliSessionId = null;
876
998
  const renewedSessionId = await ensureCliSession();
999
+ console.info("[streamAgentChatTurn] Retrying CLI turn stream. Session ID:", renewedSessionId);
877
1000
  resp = await createCliChatTurnStream(
878
1001
  {
879
1002
  getState,
@@ -889,6 +1012,7 @@ export const streamAgentChatTurnHandler = async (
889
1012
 
890
1013
  if (!resp.ok) {
891
1014
  const err = await resp.json().catch(() => ({ error: resp.statusText }));
1015
+ console.error("[streamAgentChatTurn] Local CLI fetch failed. Status:", resp.status, "Error details:", err);
892
1016
  return rejectWithValue(err.error || "CLI 执行失败");
893
1017
  }
894
1018
 
@@ -1015,7 +1139,7 @@ export const streamAgentChatTurnHandler = async (
1015
1139
  dialogId,
1016
1140
  });
1017
1141
 
1018
- if (isDesktopBuiltinNoloAgent(agentKey, agentConfig)) {
1142
+ if (shouldUseDesktopLocalRuntime(agentConfig)) {
1019
1143
  const { key: msgKey, messageId } = createDialogMessageKeyAndId(dialogId);
1020
1144
  const desktopMessageMetadata = buildParallelMessageMetadata(
1021
1145
  agentConfig,
@@ -1044,6 +1168,14 @@ export const streamAgentChatTurnHandler = async (
1044
1168
  return rejectWithValue(localResult.error);
1045
1169
  }
1046
1170
 
1171
+ const desktopTurnMessages = (localResult.result as any).turnMessages;
1172
+ for (const toolMessage of buildDesktopRuntimeToolMessagesForUi({
1173
+ dialogId,
1174
+ turnMessages: desktopTurnMessages,
1175
+ })) {
1176
+ dispatch(messageStreaming(toolMessage));
1177
+ }
1178
+
1047
1179
  dispatch(messageStreaming({
1048
1180
  id: messageId,
1049
1181
  dialogId,
@@ -1066,7 +1198,7 @@ export const streamAgentChatTurnHandler = async (
1066
1198
  dialogId,
1067
1199
  dialogKey,
1068
1200
  reasoningBuffer: "",
1069
- toolCalls: [],
1201
+ toolCalls: extractDesktopRuntimeToolCallsForUi(desktopTurnMessages),
1070
1202
  messageMetadata: desktopMessageMetadata,
1071
1203
  })).unwrap();
1072
1204
  remoteTransientMessageFinalized = true;
@@ -1475,6 +1607,7 @@ export const streamAgentChatTurnHandler = async (
1475
1607
  userInput,
1476
1608
  runtimeOptions,
1477
1609
  currentDialog,
1610
+ agentConfigForCall,
1478
1611
  );
1479
1612
  logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-decision", {
1480
1613
  hasRuntimeOptions: !!runtimeOptions,
@@ -1747,6 +1880,7 @@ export const streamAgentChatTurnHandler = async (
1747
1880
  userInput,
1748
1881
  runtimeOptions,
1749
1882
  currentDialog,
1883
+ agentConfigForCall,
1750
1884
  );
1751
1885
  logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-decision", {
1752
1886
  hasRuntimeOptions: !!runtimeOptions,
@@ -58,6 +58,7 @@ import { buildRecentAppToolMemory } from "./appWorkingMemory";
58
58
  import type { AgentRuntimeOptions } from "./types";
59
59
  import { getModelConfig, getProviderByModelName, type Provider } from "../llm/providers";
60
60
  import type { ImageGenerationState } from "../../chat/messages/types";
61
+ import { resolveToolBaseUrl } from "../tools/toolApiClient";
61
62
 
62
63
  const BROWSER_UNAVAILABLE_CORE_TOOLS = new Set([
63
64
  "queryModelUsage",
@@ -418,11 +419,65 @@ export interface DynamicContexts {
418
419
  historyContext: string;
419
420
  editingContext: string | null;
420
421
  appWorkingMemory: string | null;
422
+ memoryOverlay: string | null;
421
423
  dialogSummary: string | null;
422
424
  proactiveSummary: string | null;
423
425
  referenceKeys: string[];
424
426
  }
425
427
 
428
+ export const fetchMemoryOverlayContext = async (
429
+ state: RootState,
430
+ agentConfig: Agent,
431
+ userInput: string | any[],
432
+ dialogConfig?: DialogConfig,
433
+ ): Promise<string | null> => {
434
+ const token = typeof (state as any)?.auth?.currentToken === "string"
435
+ ? (state as any).auth.currentToken
436
+ : null;
437
+ const currentServer = typeof (state as any)?.settings?.currentServer === "string"
438
+ ? (state as any).settings.currentServer
439
+ : null;
440
+ const agentKey = typeof agentConfig.dbKey === "string" && agentConfig.dbKey.trim()
441
+ ? agentConfig.dbKey.trim()
442
+ : "";
443
+ if (!token || !currentServer || !agentKey) return null;
444
+
445
+ const baseUrl = resolveToolBaseUrl(currentServer);
446
+ if (!baseUrl) return null;
447
+
448
+ const inputText = typeof userInput === "string"
449
+ ? userInput
450
+ : JSON.stringify(userInput ?? "");
451
+ const spaceId =
452
+ typeof (dialogConfig as any)?.spaceId === "string" && (dialogConfig as any).spaceId.trim()
453
+ ? (dialogConfig as any).spaceId.trim()
454
+ : typeof (state as any)?.space?.currentSpaceId === "string" && (state as any).space.currentSpaceId.trim()
455
+ ? (state as any).space.currentSpaceId.trim()
456
+ : undefined;
457
+
458
+ try {
459
+ const response = await fetch(`${baseUrl}/api/memory/query`, {
460
+ method: "POST",
461
+ headers: {
462
+ "Content-Type": "application/json",
463
+ Authorization: `Bearer ${token}`,
464
+ },
465
+ body: JSON.stringify({
466
+ agentKey,
467
+ userInput: inputText,
468
+ ...(spaceId ? { spaceId } : {}),
469
+ }),
470
+ });
471
+ if (!response.ok) return null;
472
+ const payload = await response.json().catch(() => null);
473
+ return typeof payload?.promptBlock === "string" && payload.promptBlock.trim()
474
+ ? payload.promptBlock.trim()
475
+ : null;
476
+ } catch {
477
+ return null;
478
+ }
479
+ };
480
+
426
481
 
427
482
  /**
428
483
  * 构建静态上下文(Loop 外调用一次)
@@ -644,12 +699,19 @@ export const buildDynamicContexts = async (
644
699
  currentDialogMessages,
645
700
  currentDialogToolRuns,
646
701
  );
702
+ const memoryOverlay = await fetchMemoryOverlayContext(
703
+ state,
704
+ agentConfig,
705
+ userInput,
706
+ dialogConfig,
707
+ );
647
708
 
648
709
  return {
649
710
  currentInputContext: formattedCurrentInputContext.trim() || null,
650
711
  historyContext: joinMapValues(historyMap),
651
712
  editingContext,
652
713
  appWorkingMemory,
714
+ memoryOverlay,
653
715
  dialogSummary,
654
716
  proactiveSummary: dialogConfig?.proactiveSummary || null,
655
717
  referenceKeys: dialogConfig?.referenceKeys || [],
@@ -672,6 +734,7 @@ export const mergeContexts = (
672
734
  historyContext: dynamicCtx.historyContext || undefined,
673
735
  editingContext: dynamicCtx.editingContext,
674
736
  appWorkingMemory: dynamicCtx.appWorkingMemory,
737
+ memoryOverlay: dynamicCtx.memoryOverlay,
675
738
  dialogSummary: dynamicCtx.dialogSummary,
676
739
  proactiveSummary: dynamicCtx.proactiveSummary,
677
740
  referenceKeys: dynamicCtx.referenceKeys,
package/ai/ai.locale.ts CHANGED
@@ -289,6 +289,41 @@ export default {
289
289
  max_tokens: "Maximum tokens for a single response. Suggestion: Do not set this unless you have a specific need (e.g., strict limit for very short replies).",
290
290
  reasoning_effort: "Reasoning effort. Higher values are slower but more rigorous."
291
291
  }
292
+ },
293
+ checkEnv: {
294
+ description: "Perform environment check and return structured results. check='context' shows platform, cwd and shells; check='build' verifies compile/build status.",
295
+ params: {
296
+ check: "The check item: 'context' returns current running environment, 'build' runs a build compile check.",
297
+ key: "Optional: probe a specific local tool (e.g. gemini, node, npm, bun, python, git, shell) without building."
298
+ }
299
+ },
300
+ deleteSpaces: {
301
+ description: "Find and delete spaces owned by the current user. Warning: first call returns confirmation preview, deletion only executes after confirmation.",
302
+ params: {
303
+ query: "Filter term for space search, can be space name or space ID (when combined with matchMode='spaceId').",
304
+ matchMode: "Match mode: prefix (name prefix), exact (full name match), contains (name contains), spaceId (match by ID). Default prefix.",
305
+ confirmedSpaceIds: "Optional list of space IDs to confirm for deletion. If omitted, all deletable spaces in the preview will be deleted."
306
+ }
307
+ },
308
+ listUserSpaces: {
309
+ description: "List all spaces that the current user belongs to, including space name, ID and role.",
310
+ params: {
311
+ brief: "If true, returns only brief summary to save context/tokens."
312
+ }
313
+ },
314
+ read: {
315
+ description: "Read content by its database key. Useful to query page contents, dialog history, or table records.",
316
+ params: {
317
+ dbKey: "The database key of the target record, e.g. PAGE-xxx, dialog-xxx, table-xxx.",
318
+ waitRemote: "If true, forces a remote synchronization read to fetch latest updates."
319
+ }
320
+ },
321
+ rememberMemory: {
322
+ description: "Write important user preferences or shared space agreements into episodic memory for long-term recall.",
323
+ params: {
324
+ content: "The memory content to remember, e.g., 'User prefers dark mode and uses TypeScript.'",
325
+ tags: "Optional tag array for classification and quick search."
326
+ }
292
327
  }
293
328
  },
294
329
  },
@@ -580,6 +615,41 @@ export default {
580
615
  max_tokens: "单次回答的最大 token 数。建议:除非有特殊需求(如强制控制回复极短),否则不要设置此项。",
581
616
  reasoning_effort: "推理强度,数值越高越慢但更严谨。"
582
617
  }
618
+ },
619
+ checkEnv: {
620
+ description: "执行环境检查并返回结构化结果。check='context' 可查看当前平台、工作目录与可用 shell;check='build' 可快速验证是否可编译。",
621
+ params: {
622
+ check: "检查项:context 返回当前运行环境;build 执行构建检查。",
623
+ key: "可选:探测某个本地工具,不执行 build。常用值如 gemini、node、npm、bun、python、git、shell。"
624
+ }
625
+ },
626
+ deleteSpaces: {
627
+ description: "按名称或 ID 查找并删除当前用户拥有的 Space。这是危险操作:第一次调用会返回待删除列表并等待用户确认;确认后才执行删除。",
628
+ params: {
629
+ query: "要删除的 Space 筛选词,例如 rn_owner_verify_0504,或配合 matchMode=spaceId 传入 space id。",
630
+ matchMode: "匹配方式。prefix=名称前缀,exact=名称完全匹配,contains=名称包含,spaceId=按 Space ID 匹配。默认 prefix。",
631
+ confirmedSpaceIds: "确认阶段要删除的 Space ID 列表。不传时,用户点击确认会删除预览列表中的全部可删除项。"
632
+ }
633
+ },
634
+ listUserSpaces: {
635
+ description: "列出当前用户拥有的或加入的所有 Space,返回 Space 名称、ID、成员角色等信息。",
636
+ params: {
637
+ brief: "是否返回精简信息。若为 true,只包含基本元信息,可节省上下文 Token。"
638
+ }
639
+ },
640
+ read: {
641
+ description: "根据数据库 Key 读取任意类型的数据记录(页面内容、对话历史、表格配置等)。",
642
+ params: {
643
+ dbKey: "要读取的记录 Key,例如 PAGE-xxx, dialog-xxx, table-xxx。",
644
+ waitRemote: "是否等待从远端同步最新数据后返回。默认 false。"
645
+ }
646
+ },
647
+ rememberMemory: {
648
+ description: "将值得长期保留的用户偏好或空间共识写入一条 episodic memory 记忆,供后续对话长期检索。",
649
+ params: {
650
+ content: "具体的记忆内容,例如:'用户更偏好使用 TypeScript 进行编码开发。'",
651
+ tags: "给这条记忆打上的标签数组,用于未来的归类 and 检索。"
652
+ }
583
653
  }
584
654
  },
585
655
  },
@@ -13,11 +13,16 @@ type InlineOptions = {
13
13
  const FILE_CONTENT_PATH = "/api/v1/db/file/content/";
14
14
 
15
15
  export const shouldInlineImageUrlsForAgent = (
16
- agentConfig: { apiSource?: string | null; provider?: string | null } | null | undefined,
16
+ agentConfig:
17
+ | { apiSource?: string | null; model?: string | null; provider?: string | null }
18
+ | null
19
+ | undefined,
17
20
  ) => {
18
21
  const apiSource = agentConfig?.apiSource?.toLowerCase();
19
22
  const provider = agentConfig?.provider?.toLowerCase();
20
- return apiSource === "custom" || provider === "custom";
23
+ const model = agentConfig?.model?.toLowerCase();
24
+ if (apiSource === "custom" || provider === "custom") return true;
25
+ return provider === "openrouter" && model === "minimax/minimax-m3";
21
26
  };
22
27
 
23
28
  const isInlineCandidate = (url: string) =>
@@ -118,6 +118,22 @@ function formatStreamErrorMessage(data: any): string {
118
118
  return rawMessage;
119
119
  }
120
120
 
121
+ function getChoiceFinishErrorMessage(data: any, choice: any): string | null {
122
+ if (choice?.error || choice?.message || choice?.code) {
123
+ const message = formatStreamErrorMessage(choice);
124
+ if (message && message !== "Unknown error") return message;
125
+ }
126
+ if (data?.error || data?.message || data?.code) {
127
+ const message = formatStreamErrorMessage(data);
128
+ if (message && message !== "Unknown error") return message;
129
+ }
130
+ const messageContent = choice?.message?.content;
131
+ if (typeof messageContent === "string" && messageContent.trim()) {
132
+ return messageContent.trim();
133
+ }
134
+ return null;
135
+ }
136
+
121
137
  /** 单次流式请求过程中的全部中间状态(显式 state) */
122
138
  type StreamState = {
123
139
  contentBuffer: MessageContentPart[];
@@ -854,11 +870,19 @@ export const sendOpenAICompletionsRequest = async ({
854
870
  // 无论是否 handoff,都 finalize 当前 assistant 消息(stub)
855
871
  streamState = await finalizeStream(streamState, finalizeCtx);
856
872
  } else if (finishReason !== "stop") {
873
+ const finishErrorMessage =
874
+ finishReason === "error"
875
+ ? getChoiceFinishErrorMessage(data, choice)
876
+ : null;
857
877
  streamState = {
858
878
  ...streamState,
859
879
  contentBuffer: appendTextChunk(
860
880
  streamState.contentBuffer,
861
- `\n[流结束原因: ${finishReason}]`
881
+ finishErrorMessage
882
+ ? `\n[API Error] Error: ${finishErrorMessage}`
883
+ : finishReason === "error"
884
+ ? "\n[API Error] Error: 模型响应以 error 结束,但上游未返回具体错误。请重试,或切换到其他支持图片输入的模型。"
885
+ : `\n[流结束原因: ${finishReason}]`
862
886
  ),
863
887
  };
864
888
  }
package/ai/llm/kimi.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export const FIREWORKS_KIMI_LATEST_MODEL = "accounts/fireworks/models/kimi-latest";
2
2
  export const FIREWORKS_KIMI_CURRENT_MODEL = "accounts/fireworks/models/kimi-k2p6";
3
3
  export const DEEPINFRA_KIMI_FALLBACK_MODEL = "moonshotai/Kimi-K2.6";
4
- export const OPENROUTER_KIMI_FALLBACK_MODEL = "moonshotai/kimi-k2.6";
5
4
  export const KIMI_PLATFORM_FALLBACK_STATUSES = [401, 402, 429, 500, 502, 503, 504];
6
5
 
7
6
  export const isFireworksKimiModel = (model?: string | null): boolean =>
@@ -1,6 +1,31 @@
1
- import { ModelDefinition } from '../types';
1
+ import type { Model } from "./types";
2
2
 
3
- export const OPENROUTER_MODELS: ModelDefinition[] = [
3
+ const OPENROUTER_PRICE_MULTIPLIER = 7;
4
+
5
+ export const OPENROUTER_MODELS: Model[] = [
6
+ {
7
+ name: "x-ai/grok-4.3",
8
+ displayName: "xAI: Grok 4.3",
9
+ hasVision: true,
10
+ contextWindow: 1_000_000,
11
+ supportsTool: true,
12
+ supportsReasoningEffort: true,
13
+ price: {
14
+ input: 1.25 * OPENROUTER_PRICE_MULTIPLIER,
15
+ output: 2.5 * OPENROUTER_PRICE_MULTIPLIER,
16
+ },
17
+ },
18
+ {
19
+ name: "minimax/minimax-m3",
20
+ displayName: "MiniMax: MiniMax M3",
21
+ hasVision: true,
22
+ contextWindow: 1_000_000,
23
+ supportsTool: true,
24
+ price: {
25
+ input: 0.3 * OPENROUTER_PRICE_MULTIPLIER,
26
+ output: 1.2 * OPENROUTER_PRICE_MULTIPLIER,
27
+ },
28
+ },
4
29
  ];
5
30
 
6
31
  export const openrouterModels = OPENROUTER_MODELS;
@@ -0,0 +1,49 @@
1
+ import type { MemorySubjectRef } from "./types";
2
+
3
+ export type MemoryOwnerFallback = "onSubjectMiss" | "always";
4
+
5
+ export type AgentMemoryPolicy = {
6
+ includeUserSubject: boolean;
7
+ ownerFallback: MemoryOwnerFallback;
8
+ allowDynamicGreetingMemory: boolean;
9
+ };
10
+
11
+ export function isPublicAgentKey(agentKey?: string | null): boolean {
12
+ return typeof agentKey === "string" && /^agent-pub-/i.test(agentKey.trim());
13
+ }
14
+
15
+ export function resolveAgentMemoryPolicy(input: {
16
+ agentKey?: string | null;
17
+ isPublicAgent?: boolean | null;
18
+ }): AgentMemoryPolicy {
19
+ const publicAgent = input.isPublicAgent === true || isPublicAgentKey(input.agentKey);
20
+ if (publicAgent) {
21
+ return {
22
+ includeUserSubject: false,
23
+ ownerFallback: "onSubjectMiss",
24
+ allowDynamicGreetingMemory: false,
25
+ };
26
+ }
27
+
28
+ return {
29
+ includeUserSubject: true,
30
+ ownerFallback: "always",
31
+ allowDynamicGreetingMemory: true,
32
+ };
33
+ }
34
+
35
+ export function buildMemorySubjectsForAgent(input: {
36
+ userId?: string | null;
37
+ spaceId?: string | null;
38
+ agentKey: string;
39
+ policy?: AgentMemoryPolicy;
40
+ }): MemorySubjectRef[] {
41
+ const policy = input.policy ?? resolveAgentMemoryPolicy({ agentKey: input.agentKey });
42
+ return [
43
+ { subjectType: "agent" as const, subjectId: input.agentKey },
44
+ policy.includeUserSubject && input.userId
45
+ ? { subjectType: "user" as const, subjectId: input.userId }
46
+ : null,
47
+ input.spaceId ? { subjectType: "space" as const, subjectId: input.spaceId } : null,
48
+ ].filter((value): value is MemorySubjectRef => !!value);
49
+ }
@@ -86,10 +86,12 @@ export const loadMemoryCandidatesFromDb = async (
86
86
  subjects: MemorySubjectRef[];
87
87
  kinds?: MemoryKind[];
88
88
  ownerLimit?: number;
89
+ ownerFallback?: "onSubjectMiss" | "always";
89
90
  }
90
91
  ): Promise<MemoryItem[]> => {
91
92
  const kinds = input.kinds ?? ["episodic", "semantic", "procedural"];
92
93
  const ownerLimit = input.ownerLimit ?? 12;
94
+ const ownerFallback = input.ownerFallback ?? "onSubjectMiss";
93
95
  const ownerKeySet = new Set(
94
96
  input.owners
95
97
  .filter((owner) => owner.ownerId)
@@ -110,10 +112,14 @@ export const loadMemoryCandidatesFromDb = async (
110
112
  )
111
113
  : [];
112
114
  const subjectHitCount = subjectResults.reduce((sum, items) => sum + items.length, 0);
113
- const usingOwnerFallback = subjects.length === 0 || subjectHitCount === 0;
114
- const results = usingOwnerFallback
115
+ const shouldLoadOwnerFallback =
116
+ subjects.length === 0 || subjectHitCount === 0 || ownerFallback === "always";
117
+ const results = shouldLoadOwnerFallback
115
118
  ? await Promise.all(
116
- input.owners.map((owner) => loadOwnerItemsFromDb(db, owner, ownerLimit))
119
+ [
120
+ ...subjectResults,
121
+ ...input.owners.map((owner) => loadOwnerItemsFromDb(db, owner, ownerLimit)),
122
+ ]
117
123
  )
118
124
  : subjectResults;
119
125
 
@@ -131,7 +137,7 @@ export const loadMemoryCandidatesFromDb = async (
131
137
  continue;
132
138
  }
133
139
  if (
134
- !usingOwnerFallback &&
140
+ !shouldLoadOwnerFallback &&
135
141
  subjects.length > 0 &&
136
142
  !subjects.some(
137
143
  (subject) =>
@@ -1,5 +1,6 @@
1
1
  import { createDialogKey, dialogMessageRange } from "../../database/keys";
2
2
  import { extractCustomId } from "../../core/prefix";
3
+ import { resolveAgentMemoryPolicy } from "./policy";
3
4
 
4
5
  interface RecentDialogLike {
5
6
  dbKey?: string;
@@ -43,6 +44,9 @@ export const shouldUseRecentRelationshipRecap = (input: {
43
44
  triggerType?: "user" | "api" | "localhost" | "scheduled_run";
44
45
  }): boolean => {
45
46
  if (!input.userId || !input.agentKey) return false;
47
+ if (!resolveAgentMemoryPolicy({ agentKey: input.agentKey }).allowDynamicGreetingMemory) {
48
+ return false;
49
+ }
46
50
  if ((input.cybotsCount ?? 0) !== 1) return false;
47
51
  if (input.inheritFromDialogKey) return false;
48
52
  if (input.skipGreeting) return false;