nolo-cli 0.1.23 → 0.1.26

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 (76) hide show
  1. package/README.md +25 -3
  2. package/agent-runtime/hybridRecordStore.ts +30 -9
  3. package/agent-runtime/localLoop.ts +12 -4
  4. package/agent-runtime/localToolPolicy.ts +22 -10
  5. package/agent-runtime/localWorkspaceTools.ts +140 -374
  6. package/agent-runtime/runtimeToolPolicy.ts +43 -2
  7. package/agent-runtime/types.ts +5 -0
  8. package/agentAliases.ts +1 -1
  9. package/agentCommandSupport.ts +77 -0
  10. package/agentInternalCommandEntries.ts +34 -0
  11. package/agentListCommands.ts +123 -0
  12. package/agentListHelpers.ts +187 -0
  13. package/agentMachineCommands.ts +402 -0
  14. package/agentRecordCommands.ts +141 -0
  15. package/agentRecordHelpers.ts +395 -0
  16. package/agentRunCommand.ts +1 -2
  17. package/agentRuntimeCommands.ts +7 -701
  18. package/ai/agent/_executeModel.ts +29 -3
  19. package/ai/agent/buildSystemPrompt.ts +17 -0
  20. package/ai/agent/cliChatClient.ts +38 -4
  21. package/ai/agent/cliExecutor.ts +304 -25
  22. package/ai/agent/createAgentSchema.ts +2 -2
  23. package/ai/agent/guidedCreation/assistant.ts +82 -0
  24. package/ai/agent/guidedCreation/capabilities.ts +84 -0
  25. package/ai/agent/guidedCreation/draft.ts +62 -0
  26. package/ai/agent/guidedCreation/types.ts +38 -0
  27. package/ai/agent/machineRunPermissions.ts +21 -3
  28. package/ai/agent/runtimeGuidance.ts +1 -1
  29. package/ai/agent/server/fetchPublicAgents.ts +6 -1
  30. package/ai/agent/streamAgentChatTurn.ts +492 -19
  31. package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
  32. package/ai/ai.locale.ts +14 -2
  33. package/ai/chat/sendOpenAICompletionsRequest.ts +28 -3
  34. package/ai/chat/sendOpenAIResponseRequest.ts +3 -2
  35. package/ai/llm/getPricing.ts +14 -0
  36. package/ai/llm/kimi.ts +3 -0
  37. package/ai/llm/modelAvatar.ts +1 -0
  38. package/ai/llm/providers.ts +1 -1
  39. package/ai/taskRun/taskRunProtocol.ts +24 -10
  40. package/ai/token/modelUsageQuery.ts +17 -0
  41. package/ai/tools/agent/agentTools.ts +21 -0
  42. package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
  43. package/ai/tools/checkEnvTool.ts +52 -20
  44. package/ai/tools/convertMarxistsBookTool.ts +103 -0
  45. package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
  46. package/ai/tools/index.ts +24 -14
  47. package/ai/tools/marxistsOfflineBook.ts +253 -0
  48. package/ai/tools/modelUsageTools.ts +4 -2
  49. package/ai/tools/readTool.ts +31 -0
  50. package/ai/tools/toolApiClient.ts +35 -1
  51. package/ai/tools/toolNameAliases.ts +13 -0
  52. package/cliCommandAdapters.ts +28 -0
  53. package/cliCommandDispatch.ts +66 -0
  54. package/cliCommandFactories.ts +34 -0
  55. package/cliCommandTypes.ts +49 -0
  56. package/cliEnvHelpers.ts +44 -0
  57. package/client/agentRun.test.ts +123 -19
  58. package/client/agentRun.ts +40 -1
  59. package/client/hybridRecordStore.test.ts +42 -0
  60. package/client/hybridRecordStore.ts +11 -0
  61. package/client/localRuntimeAdapter.test.ts +189 -21
  62. package/client/localRuntimeAdapter.ts +84 -8
  63. package/client/localRuntimeDryRun.test.ts +5 -2
  64. package/client/taskRunPrompt.ts +1 -0
  65. package/commandRegistry.ts +11 -61
  66. package/connector-experimental/capabilities.ts +1 -0
  67. package/database/server/coreDataProxy.ts +43 -26
  68. package/index.ts +54 -191
  69. package/internalCommandEntries.ts +12 -0
  70. package/machineCommands.ts +301 -32
  71. package/offlineMarxistsAgentCommand.ts +314 -0
  72. package/package.json +20 -2
  73. package/runtimeDoctorCommands.ts +118 -0
  74. package/scriptCommandEntries.ts +71 -0
  75. package/systemInternalCommandEntries.ts +71 -0
  76. package/workflowInternalCommandEntries.ts +11 -0
@@ -26,7 +26,7 @@ import {
26
26
  getFullChatContextKeys,
27
27
  deduplicateContextKeys,
28
28
  } from "ai/agent/getFullChatContextKeys";
29
- import type { Agent } from "app/types";
29
+ import type { Agent, DialogConfig } from "app/types";
30
30
  import { isResponseAPIModel } from "ai/llm/isResponseAPIModel";
31
31
  import { getModelContextWindow } from "ai/llm/getModelContextWindow";
32
32
  import { resolveAgentImageInputSupport } from "ai/llm/agentCapabilities";
@@ -56,7 +56,11 @@ import {
56
56
  import { buildCliPrompt } from "./cliPrompt";
57
57
  import { createCliChatTurnStream } from "./cliChatClient";
58
58
  import { getCliChatSession, startCliChatSession } from "./cliChatClient";
59
- import { messageStreaming, prepareAndPersistUserMessage } from "chat/messages/messageSlice";
59
+ import {
60
+ messageStreamEnd,
61
+ messageStreaming,
62
+ prepareAndPersistUserMessage,
63
+ } from "chat/messages/messageSlice";
60
64
  import { selectCurrentToken, selectUserId } from "auth/authSlice";
61
65
  import { persistMessageWithFixedId } from "./persistMessageWithFixedId";
62
66
  import { updateTotalUsage } from "../chat/updateTotalUsage";
@@ -65,6 +69,11 @@ import {
65
69
  extractAgentRuntimeServerBase,
66
70
  normalizeServerOrigin,
67
71
  } from "./runtimeServerBase";
72
+ import { getIsDesktopApp } from "app/utils/env";
73
+ import { runDesktopAgentRuntimeTurn } from "app/utils/desktopAgentRuntimeTurnClient";
74
+
75
+ const BUILTIN_NOLO_AGENT_ID = "01NOLOAPPBLD000000019KCKT0";
76
+ const BUILTIN_NOLO_AGENT_KEY = `agent-pub-${BUILTIN_NOLO_AGENT_ID}`;
68
77
 
69
78
  const buildParallelMessageMetadata = (
70
79
  agentConfig: Pick<Agent, "dbKey" | "name">,
@@ -106,6 +115,25 @@ const filterMessagesForParallelBranch = (
106
115
  });
107
116
  };
108
117
 
118
+ const isDesktopBuiltinNoloAgent = (
119
+ requestedAgentKey: string,
120
+ agentConfig: Partial<Agent> | null | undefined,
121
+ ) => {
122
+ if (!getIsDesktopApp()) return false;
123
+ const candidates = [
124
+ requestedAgentKey,
125
+ agentConfig?.dbKey,
126
+ agentConfig?.agentKey,
127
+ agentConfig?.id,
128
+ ];
129
+ return candidates.some((value) =>
130
+ typeof value === "string" &&
131
+ (value === BUILTIN_NOLO_AGENT_KEY ||
132
+ value === BUILTIN_NOLO_AGENT_ID ||
133
+ value.endsWith(`-${BUILTIN_NOLO_AGENT_ID}`))
134
+ );
135
+ };
136
+
109
137
  const formatMachineAgentRunError = async (response: Response): Promise<string> => {
110
138
  const errorText = await response.text();
111
139
  let payload: any = null;
@@ -149,20 +177,171 @@ export interface StreamAgentChatTurnArgs {
149
177
  quickChatPerfStartedAt?: number;
150
178
  }
151
179
 
180
+ const QUICK_CHAT_AGENT_CONFIG_READ_TIMEOUT_MS = 10_000;
181
+ const QUICK_CHAT_DYNAMIC_CONTEXT_TIMEOUT_MS = 5_000;
182
+ const EMPTY_DYNAMIC_CONTEXTS = {
183
+ currentInputContext: null,
184
+ historyContext: "",
185
+ editingContext: null,
186
+ appWorkingMemory: null,
187
+ dialogSummary: null,
188
+ proactiveSummary: null,
189
+ referenceKeys: [],
190
+ };
191
+
192
+ function appendCliCapabilityWarnings(content: string, warnings: string[]): string {
193
+ if (!warnings.length) return content;
194
+ const warningBlock = `\n\n[CLI 能力提示]\n${warnings.map((warning) => `- ${warning}`).join("\n")}`;
195
+ return `${content}${warningBlock}`;
196
+ }
197
+
152
198
  const logQuickChatPerfStage = (
153
199
  startedAt: number | undefined,
154
200
  stage: string,
155
201
  details: Record<string, unknown> = {}
156
202
  ) => {
157
203
  if (!startedAt) return;
204
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
158
205
  console.info("[QuickChatPerf]", {
159
206
  stage,
160
- elapsedMs: Date.now() - startedAt,
161
- ...(typeof performance !== "undefined" ? { atMs: performance.now() } : {}),
207
+ elapsedMs: now - startedAt,
208
+ ...(typeof performance !== "undefined" ? { atMs: now } : {}),
162
209
  ...details,
163
210
  });
164
211
  };
165
212
 
213
+ const readAgentConfigForTurn = async (
214
+ dispatch: any,
215
+ agentKey: string,
216
+ quickChatPerfStartedAt?: number,
217
+ ): Promise<Agent> => {
218
+ const readPromise = dispatch(read({ dbKey: agentKey })).unwrap() as Promise<Agent>;
219
+ if (!quickChatPerfStartedAt) return readPromise;
220
+
221
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
222
+ try {
223
+ return await Promise.race([
224
+ readPromise,
225
+ new Promise<Agent>((_, reject) => {
226
+ timeoutId = setTimeout(() => {
227
+ logQuickChatPerfStage(
228
+ quickChatPerfStartedAt,
229
+ "quick-chat-agent-config-read-timeout",
230
+ {
231
+ agentKey,
232
+ timeoutMs: QUICK_CHAT_AGENT_CONFIG_READ_TIMEOUT_MS,
233
+ },
234
+ );
235
+ reject(
236
+ new Error("读取 Agent 配置超时,未能启动模型回复。"),
237
+ );
238
+ }, QUICK_CHAT_AGENT_CONFIG_READ_TIMEOUT_MS);
239
+ }),
240
+ ]);
241
+ } finally {
242
+ if (timeoutId) clearTimeout(timeoutId);
243
+ }
244
+ };
245
+
246
+ const buildDynamicContextsForTurn = async (
247
+ state: RootState,
248
+ dispatch: any,
249
+ agentConfig: Agent,
250
+ userInput: string | any[],
251
+ runtimeOptions: AgentRuntimeOptions | undefined,
252
+ mergedContentCache: Map<string, any>,
253
+ dialogKey: string | undefined,
254
+ quickChatPerfStartedAt?: number,
255
+ ) => {
256
+ if (!quickChatPerfStartedAt) {
257
+ return buildDynamicContexts(
258
+ state,
259
+ dispatch,
260
+ agentConfig,
261
+ userInput,
262
+ runtimeOptions,
263
+ mergedContentCache,
264
+ dialogKey,
265
+ );
266
+ }
267
+
268
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
269
+ try {
270
+ const timeoutPromise = new Promise<typeof EMPTY_DYNAMIC_CONTEXTS>((resolve) => {
271
+ timeoutId = setTimeout(() => {
272
+ logQuickChatPerfStage(
273
+ quickChatPerfStartedAt,
274
+ "quick-chat-dynamic-context-timeout",
275
+ {
276
+ agentKey: agentConfig.dbKey,
277
+ dialogKey,
278
+ timeoutMs: QUICK_CHAT_DYNAMIC_CONTEXT_TIMEOUT_MS,
279
+ },
280
+ );
281
+ resolve(EMPTY_DYNAMIC_CONTEXTS);
282
+ }, QUICK_CHAT_DYNAMIC_CONTEXT_TIMEOUT_MS);
283
+ });
284
+
285
+ logQuickChatPerfStage(
286
+ quickChatPerfStartedAt,
287
+ "stream-agent-dynamic-context-starting",
288
+ {
289
+ responseApi: isResponseAPIModel(agentConfig),
290
+ },
291
+ );
292
+ const contextPromise = buildDynamicContexts(
293
+ state,
294
+ dispatch,
295
+ agentConfig,
296
+ userInput,
297
+ runtimeOptions,
298
+ mergedContentCache,
299
+ dialogKey,
300
+ );
301
+
302
+ return await Promise.race([
303
+ contextPromise,
304
+ timeoutPromise,
305
+ ]);
306
+ } finally {
307
+ if (timeoutId) clearTimeout(timeoutId);
308
+ }
309
+ };
310
+
311
+ const finalizeQuickChatAgentTurnFailure = async (
312
+ dispatch: any,
313
+ dialogKey: string,
314
+ agentKey: string,
315
+ error: unknown,
316
+ ) => {
317
+ const dialogId = extractCustomId(dialogKey);
318
+ const { key: msgKey, messageId } = createDialogMessageKeyAndId(dialogId);
319
+ const reason =
320
+ error instanceof Error && error.message.trim()
321
+ ? error.message.trim()
322
+ : "未能启动模型回复,请重试。";
323
+
324
+ await dispatch(
325
+ messageStreamEnd({
326
+ finalContentBuffer: [
327
+ {
328
+ type: "text",
329
+ text: `[错误: ${reason}]`,
330
+ },
331
+ ],
332
+ totalUsage: null,
333
+ msgKey,
334
+ agentConfig: {
335
+ dbKey: agentKey,
336
+ },
337
+ dialogId,
338
+ dialogKey,
339
+ messageId,
340
+ reasoningBuffer: "",
341
+ })
342
+ ).unwrap?.();
343
+ };
344
+
166
345
  const normalizeAgentRunUserInput = (userInput: string | any[]) => {
167
346
  if (typeof userInput === "string") {
168
347
  return userInput;
@@ -181,9 +360,68 @@ const normalizeAgentRunUserInput = (userInput: string | any[]) => {
181
360
  });
182
361
  };
183
362
 
363
+ const isSimpleTextInput = (userInput: string | any[]) => {
364
+ if (typeof userInput === "string") return true;
365
+ if (!Array.isArray(userInput)) return false;
366
+ return userInput.every(
367
+ (part) =>
368
+ part &&
369
+ typeof part === "object" &&
370
+ part.type === "text" &&
371
+ typeof part.text === "string",
372
+ );
373
+ };
374
+
375
+ const canUseQuickChatEmptyDynamicContexts = (
376
+ quickChatPerfStartedAt: number | undefined,
377
+ userInput: string | any[],
378
+ runtimeOptions: AgentRuntimeOptions | undefined,
379
+ dialogConfig: DialogConfig | null,
380
+ ) => {
381
+ if (!quickChatPerfStartedAt) return false;
382
+ if (runtimeOptions) return false;
383
+ if (!isSimpleTextInput(userInput)) return false;
384
+ if (dialogConfig?.referenceKeys?.length) return false;
385
+ return true;
386
+ };
387
+
388
+ const QUICK_CHAT_DIRECT_ANSWER_PATTERN =
389
+ /(只回复|只输出|只回答|直接回复|直接回答|不要解释|无需解释|不用解释|简短回答|一句话)/i;
390
+ const QUICK_CHAT_TOOL_INTENT_PATTERN =
391
+ /(调用|转交|agent|助手|应用|网页|页面|图表|图片|生成图|画图|删除|清理|空间|商品|链接|https?:\/\/|www\.|@)/i;
392
+
393
+ const shouldDisableQuickChatToolsForDirectAnswer = (
394
+ quickChatPerfStartedAt: number | undefined,
395
+ userInput: string | any[],
396
+ runtimeOptions: AgentRuntimeOptions | undefined,
397
+ dialogConfig: DialogConfig | null,
398
+ ) => {
399
+ if (!canUseQuickChatEmptyDynamicContexts(
400
+ quickChatPerfStartedAt,
401
+ userInput,
402
+ runtimeOptions,
403
+ dialogConfig,
404
+ )) {
405
+ return false;
406
+ }
407
+ const text = extractAgentRunUserText(userInput);
408
+ if (!text || text.length > 500) return false;
409
+ if (!QUICK_CHAT_DIRECT_ANSWER_PATTERN.test(text)) return false;
410
+ return !QUICK_CHAT_TOOL_INTENT_PATTERN.test(text);
411
+ };
412
+
413
+ const classifyQuickChatAccessError = (accessError: string) => {
414
+ if (accessError.includes("获取用户余额")) return "balance-loading";
415
+ if (accessError.includes("余额")) return "balance";
416
+ if (accessError.includes("白名单")) return "whitelist";
417
+ if (accessError.includes("定价")) return "pricing";
418
+ return "unknown";
419
+ };
420
+
184
421
  const isUsableAgentConfig = (value: unknown): value is Agent =>
185
422
  !!value &&
186
423
  typeof value === "object" &&
424
+ (value as any).__ssrPreviewOnly !== true &&
187
425
  typeof (value as Agent).dbKey === "string" &&
188
426
  !!(value as Agent).dbKey &&
189
427
  typeof (value as Agent).model === "string" &&
@@ -244,6 +482,7 @@ export const streamAgentChatTurnHandler = async (
244
482
  let runtimeDialogKey: string | null = explicitDialogKey ?? null;
245
483
  let remoteTransientMessageId: string | null = null;
246
484
  let remoteTransientMessageFinalized = false;
485
+ let modelRequestStarted = false;
247
486
 
248
487
  try {
249
488
  let totalTurnUsage: any = null;
@@ -256,7 +495,11 @@ export const streamAgentChatTurnHandler = async (
256
495
  const cachedAgentConfig = selectById(getState() as RootState, agentKey);
257
496
  const agentConfig = isUsableAgentConfig(cachedAgentConfig)
258
497
  ? cachedAgentConfig
259
- : ((await dispatch(read({ dbKey: agentKey })).unwrap()) as Agent);
498
+ : await readAgentConfigForTurn(
499
+ dispatch,
500
+ agentKey,
501
+ quickChatPerfStartedAt,
502
+ );
260
503
  if (!agentConfig) {
261
504
  return rejectWithValue(`Agent config not found for ID: ${agentKey}`);
262
505
  }
@@ -486,6 +729,14 @@ export const streamAgentChatTurnHandler = async (
486
729
  cliProvider: agentConfig.cliProvider || "copilot",
487
730
  model: agentConfig.model || undefined,
488
731
  systemPrompt: agentConfig.prompt || undefined,
732
+ reasoningEffort: agentConfig.reasoning_effort || undefined,
733
+ temperature: agentConfig.temperature,
734
+ topP: agentConfig.top_p,
735
+ frequencyPenalty: agentConfig.frequency_penalty,
736
+ presencePenalty: agentConfig.presence_penalty,
737
+ maxTokens: agentConfig.max_tokens,
738
+ enableThinking: agentConfig.enableThinking,
739
+ thinkingBudget: agentConfig.thinkingBudget,
489
740
  },
490
741
  );
491
742
 
@@ -557,6 +808,7 @@ export const streamAgentChatTurnHandler = async (
557
808
  }
558
809
 
559
810
  let accumulated = "";
811
+ let cliCapabilityWarnings: string[] = [];
560
812
  const decoder = new TextDecoder();
561
813
  const buildCliAssistantMessage = () => ({
562
814
  id: messageId,
@@ -616,7 +868,9 @@ export const streamAgentChatTurnHandler = async (
616
868
  ...cliMessageMetadata,
617
869
  }));
618
870
  }
619
- // payload.done 时 stream 自然关闭,while loop 会退出
871
+ if (payload.done && Array.isArray(payload.warnings)) {
872
+ cliCapabilityWarnings = payload.warnings.filter((warning): warning is string => typeof warning === "string" && warning.trim().length > 0);
873
+ }
620
874
  } catch {
621
875
  // 忽略解析失败的行
622
876
  }
@@ -628,6 +882,18 @@ export const streamAgentChatTurnHandler = async (
628
882
  return;
629
883
  }
630
884
 
885
+ if (cliCapabilityWarnings.length > 0) {
886
+ accumulated = appendCliCapabilityWarnings(accumulated, cliCapabilityWarnings);
887
+ dispatch(messageStreaming({
888
+ id: messageId,
889
+ dialogId,
890
+ dbKey: msgKey,
891
+ content: accumulated,
892
+ role: "assistant",
893
+ ...cliMessageMetadata,
894
+ }));
895
+ }
896
+
631
897
  // 持久化最终消息:用已有 ID,避免 prepareAndPersistMessage 重新生成 ID 导致重复
632
898
  await persistMessageWithFixedId(dispatch, buildCliAssistantMessage());
633
899
  } finally {
@@ -658,6 +924,49 @@ export const streamAgentChatTurnHandler = async (
658
924
  dialogId,
659
925
  });
660
926
 
927
+ if (isDesktopBuiltinNoloAgent(agentKey, agentConfig)) {
928
+ const { key: msgKey, messageId } = createDialogMessageKeyAndId(dialogId);
929
+ const desktopMessageMetadata = buildParallelMessageMetadata(
930
+ agentConfig,
931
+ runtimeOptions,
932
+ );
933
+ remoteTransientMessageId = messageId;
934
+ loopKey = `loop:${dialogId}`;
935
+ dispatch(addActiveController({ messageId: loopKey, controller: loopController, dialogKey }));
936
+ dispatch(messageStreaming({
937
+ id: messageId,
938
+ dialogId,
939
+ dbKey: msgKey,
940
+ content: "",
941
+ role: "assistant",
942
+ ...desktopMessageMetadata,
943
+ }));
944
+
945
+ const localResult = await runDesktopAgentRuntimeTurn({
946
+ agentRef: agentConfig.dbKey || agentKey,
947
+ input: userInput,
948
+ continueDialogId: dialogId,
949
+ });
950
+ if (!localResult.ok) {
951
+ dispatch(removeTransientMessage(messageId));
952
+ remoteTransientMessageFinalized = true;
953
+ return rejectWithValue(localResult.error);
954
+ }
955
+
956
+ dispatch(messageStreaming({
957
+ id: messageId,
958
+ dialogId,
959
+ dbKey: msgKey,
960
+ content: localResult.result.content || "",
961
+ role: "assistant",
962
+ ...desktopMessageMetadata,
963
+ }));
964
+ remoteTransientMessageFinalized = true;
965
+ return {
966
+ usage: localResult.result.usage ?? undefined,
967
+ };
968
+ }
969
+
661
970
  const userInputText = extractAgentRunUserText(userInput);
662
971
 
663
972
  const explicitServerBase =
@@ -956,6 +1265,9 @@ export const streamAgentChatTurnHandler = async (
956
1265
  model: agentConfigForCall.model,
957
1266
  responseApi: true,
958
1267
  });
1268
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-post-static-entered", {
1269
+ responseApi: true,
1270
+ });
959
1271
 
960
1272
  let appendTempUserInput = true;
961
1273
  let currentParentMessageId = parentMessageId ?? undefined;
@@ -964,11 +1276,25 @@ export const streamAgentChatTurnHandler = async (
964
1276
  if (w) w.__LOOP_STOP_REASON__ = null;
965
1277
 
966
1278
  loopKey = `loop:${dialogId}`;
1279
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-active-controller-adding", {
1280
+ responseApi: true,
1281
+ });
967
1282
  dispatch(addActiveController({ messageId: loopKey, controller: loopController, dialogKey }));
1283
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-active-controller-added", {
1284
+ responseApi: true,
1285
+ });
968
1286
 
969
1287
  for (;;) {
1288
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-loop-entered", {
1289
+ responseApi: true,
1290
+ });
970
1291
  const requestParentMessageId = currentParentMessageId;
971
1292
  if (loopController.signal.aborted || thunkApi.signal.aborted) {
1293
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-loop-aborted-before-context", {
1294
+ loopControllerAborted: loopController.signal.aborted,
1295
+ thunkSignalAborted: thunkApi.signal.aborted,
1296
+ responseApi: true,
1297
+ });
972
1298
  if (w) w.__LOOP_STOP_REASON__ = "aborted";
973
1299
  break;
974
1300
  }
@@ -976,6 +1302,11 @@ export const streamAgentChatTurnHandler = async (
976
1302
  const loopState = getState() as RootState;
977
1303
  const now = Date.now();
978
1304
  if (now - startTime > MAX_TIME_MS) {
1305
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-loop-timeout-before-context", {
1306
+ maxTimeMs: MAX_TIME_MS,
1307
+ elapsedMs: now - startTime,
1308
+ responseApi: true,
1309
+ });
979
1310
  if (w) w.__LOOP_STOP_REASON__ = "timeout";
980
1311
  break;
981
1312
  }
@@ -985,18 +1316,60 @@ export const streamAgentChatTurnHandler = async (
985
1316
  loopState,
986
1317
  );
987
1318
  if (accessError) {
1319
+ const accessErrorReason = classifyQuickChatAccessError(accessError);
1320
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-access-error-before-context", {
1321
+ hasAccessError: true,
1322
+ reason: accessErrorReason,
1323
+ responseApi: true,
1324
+ });
1325
+ if (quickChatPerfStartedAt && runtimeDialogKey && !modelRequestStarted) {
1326
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-access-error-finalized", {
1327
+ reason: accessErrorReason,
1328
+ responseApi: true,
1329
+ });
1330
+ await finalizeQuickChatAgentTurnFailure(
1331
+ dispatch,
1332
+ runtimeDialogKey,
1333
+ agentKey,
1334
+ new Error(accessError),
1335
+ );
1336
+ }
988
1337
  return rejectWithValue(accessError);
989
1338
  }
990
1339
 
991
- const dynamicContexts = await buildDynamicContexts(
992
- loopState,
993
- dispatch,
994
- agentConfigForCall,
1340
+ const willSkipDynamicContext = canUseQuickChatEmptyDynamicContexts(
1341
+ quickChatPerfStartedAt,
995
1342
  userInput,
996
1343
  runtimeOptions,
997
- mergedContentCache,
998
- dialogKey,
1344
+ currentDialog,
999
1345
  );
1346
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-decision", {
1347
+ hasRuntimeOptions: !!runtimeOptions,
1348
+ isSimpleTextInput: isSimpleTextInput(userInput),
1349
+ referenceKeyCount: currentDialog?.referenceKeys?.length ?? 0,
1350
+ willSkipDynamicContext,
1351
+ responseApi: true,
1352
+ });
1353
+
1354
+ const dynamicContexts = willSkipDynamicContext
1355
+ ? (logQuickChatPerfStage(
1356
+ quickChatPerfStartedAt,
1357
+ "stream-agent-dynamic-context-skipped",
1358
+ {
1359
+ reason: "simple-quick-chat-first-turn",
1360
+ responseApi: true,
1361
+ },
1362
+ ), EMPTY_DYNAMIC_CONTEXTS)
1363
+ : await buildDynamicContextsForTurn(
1364
+ loopState,
1365
+ dispatch,
1366
+ agentConfigForCall,
1367
+ userInput,
1368
+ runtimeOptions,
1369
+ mergedContentCache,
1370
+ dialogKey,
1371
+ quickChatPerfStartedAt,
1372
+ );
1000
1373
  logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-ready", {
1001
1374
  responseApi: true,
1002
1375
  });
@@ -1072,6 +1445,7 @@ export const streamAgentChatTurnHandler = async (
1072
1445
  dynamicMessageCount: dynamicMessages.length,
1073
1446
  stableMessageCount: stableMessages.length,
1074
1447
  });
1448
+ modelRequestStarted = true;
1075
1449
 
1076
1450
  const meta: CompletionMeta = await sendOpenAIResponseRequest({
1077
1451
  bodyData,
@@ -1162,6 +1536,9 @@ export const streamAgentChatTurnHandler = async (
1162
1536
  model: agentConfigForCall.model,
1163
1537
  responseApi: false,
1164
1538
  });
1539
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-post-static-entered", {
1540
+ responseApi: false,
1541
+ });
1165
1542
 
1166
1543
  let appendTempUserInput = true;
1167
1544
  let currentParentMessageId = parentMessageId ?? undefined;
@@ -1171,13 +1548,27 @@ export const streamAgentChatTurnHandler = async (
1171
1548
 
1172
1549
  if (!isRespModel) {
1173
1550
  loopKey = `loop:${dialogId}`;
1551
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-active-controller-adding", {
1552
+ responseApi: false,
1553
+ });
1174
1554
  dispatch(addActiveController({ messageId: loopKey, controller: loopController, dialogKey }));
1555
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-active-controller-added", {
1556
+ responseApi: false,
1557
+ });
1175
1558
  }
1176
1559
 
1177
1560
  for (;;) {
1561
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-loop-entered", {
1562
+ responseApi: false,
1563
+ });
1178
1564
  const requestParentMessageId = currentParentMessageId;
1179
1565
  // 每轮开始前检查是否已中止
1180
1566
  if (loopController.signal.aborted || thunkApi.signal.aborted) {
1567
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-loop-aborted-before-context", {
1568
+ loopControllerAborted: loopController.signal.aborted,
1569
+ thunkSignalAborted: thunkApi.signal.aborted,
1570
+ responseApi: false,
1571
+ });
1181
1572
  if (w) w.__LOOP_STOP_REASON__ = "aborted";
1182
1573
  break;
1183
1574
  }
@@ -1185,6 +1576,11 @@ export const streamAgentChatTurnHandler = async (
1185
1576
  const loopState = getState() as RootState;
1186
1577
  const now = Date.now();
1187
1578
  if (now - startTime > MAX_TIME_MS) {
1579
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-loop-timeout-before-context", {
1580
+ maxTimeMs: MAX_TIME_MS,
1581
+ elapsedMs: now - startTime,
1582
+ responseApi: false,
1583
+ });
1188
1584
  if (w) w.__LOOP_STOP_REASON__ = "timeout";
1189
1585
  break;
1190
1586
  }
@@ -1195,19 +1591,61 @@ export const streamAgentChatTurnHandler = async (
1195
1591
  loopState,
1196
1592
  );
1197
1593
  if (accessError) {
1594
+ const accessErrorReason = classifyQuickChatAccessError(accessError);
1595
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-access-error-before-context", {
1596
+ hasAccessError: true,
1597
+ reason: accessErrorReason,
1598
+ responseApi: false,
1599
+ });
1600
+ if (quickChatPerfStartedAt && runtimeDialogKey && !modelRequestStarted) {
1601
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-access-error-finalized", {
1602
+ reason: accessErrorReason,
1603
+ responseApi: false,
1604
+ });
1605
+ await finalizeQuickChatAgentTurnFailure(
1606
+ dispatch,
1607
+ runtimeDialogKey,
1608
+ agentKey,
1609
+ new Error(accessError),
1610
+ );
1611
+ }
1198
1612
  return rejectWithValue(accessError);
1199
1613
  }
1200
1614
 
1201
- // 🚀 优化:每轮只构建动态上下文(currentInput、history、editingContext、dialogSummary)
1202
- const dynamicContexts = await buildDynamicContexts(
1203
- loopState,
1204
- dispatch,
1205
- agentConfigForCall,
1615
+ const willSkipDynamicContext = canUseQuickChatEmptyDynamicContexts(
1616
+ quickChatPerfStartedAt,
1206
1617
  userInput,
1207
1618
  runtimeOptions,
1208
- mergedContentCache,
1209
- dialogKey,
1619
+ currentDialog,
1210
1620
  );
1621
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-decision", {
1622
+ hasRuntimeOptions: !!runtimeOptions,
1623
+ isSimpleTextInput: isSimpleTextInput(userInput),
1624
+ referenceKeyCount: currentDialog?.referenceKeys?.length ?? 0,
1625
+ willSkipDynamicContext,
1626
+ responseApi: false,
1627
+ });
1628
+
1629
+ // 🚀 优化:每轮只构建动态上下文(currentInput、history、editingContext、dialogSummary)
1630
+ const dynamicContexts = willSkipDynamicContext
1631
+ ? (logQuickChatPerfStage(
1632
+ quickChatPerfStartedAt,
1633
+ "stream-agent-dynamic-context-skipped",
1634
+ {
1635
+ reason: "simple-quick-chat-first-turn",
1636
+ responseApi: false,
1637
+ },
1638
+ ), EMPTY_DYNAMIC_CONTEXTS)
1639
+ : await buildDynamicContextsForTurn(
1640
+ loopState,
1641
+ dispatch,
1642
+ agentConfigForCall,
1643
+ userInput,
1644
+ runtimeOptions,
1645
+ mergedContentCache,
1646
+ dialogKey,
1647
+ quickChatPerfStartedAt,
1648
+ );
1211
1649
  logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-ready", {
1212
1650
  responseApi: false,
1213
1651
  });
@@ -1286,6 +1724,27 @@ export const streamAgentChatTurnHandler = async (
1286
1724
  dynamicMessageCount: dynamicMessages.length,
1287
1725
  stableMessageCount: stableMessages.length,
1288
1726
  });
1727
+ modelRequestStarted = true;
1728
+
1729
+ const disableToolsForThisRequest =
1730
+ shouldDisableQuickChatToolsForDirectAnswer(
1731
+ quickChatPerfStartedAt,
1732
+ userInput,
1733
+ runtimeOptions,
1734
+ currentDialog,
1735
+ );
1736
+ if (disableToolsForThisRequest) {
1737
+ logQuickChatPerfStage(
1738
+ quickChatPerfStartedAt,
1739
+ "stream-agent-tools-disabled-for-direct-answer",
1740
+ {
1741
+ responseApi: false,
1742
+ toolCount: Array.isArray(agentConfigForCall.tools)
1743
+ ? agentConfigForCall.tools.length
1744
+ : 0,
1745
+ },
1746
+ );
1747
+ }
1289
1748
 
1290
1749
  const meta: CompletionMeta = await sendOpenAICompletionsRequest({
1291
1750
  bodyData,
@@ -1297,6 +1756,7 @@ export const streamAgentChatTurnHandler = async (
1297
1756
  agentConfigForCall,
1298
1757
  runtimeOptions,
1299
1758
  ),
1759
+ disableToolsForThisRequest,
1300
1760
  quickChatPerfStartedAt,
1301
1761
  });
1302
1762
  logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-model-request-finished", {
@@ -1383,6 +1843,19 @@ export const streamAgentChatTurnHandler = async (
1383
1843
  dispatch(removeTransientMessage(remoteTransientMessageId));
1384
1844
  remoteTransientMessageFinalized = true;
1385
1845
  }
1846
+ if (
1847
+ quickChatPerfStartedAt &&
1848
+ runtimeDialogKey &&
1849
+ !modelRequestStarted &&
1850
+ !remoteTransientMessageId
1851
+ ) {
1852
+ await finalizeQuickChatAgentTurnFailure(
1853
+ dispatch,
1854
+ runtimeDialogKey,
1855
+ agentKey,
1856
+ error,
1857
+ );
1858
+ }
1386
1859
 
1387
1860
  return rejectWithValue(
1388
1861
  error?.message ||