nolo-cli 0.1.22 → 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 (128) hide show
  1. package/README.md +25 -3
  2. package/agent-runtime/agentRecordConfig.ts +4 -0
  3. package/agent-runtime/hostAdapter.ts +2 -0
  4. package/agent-runtime/hybridRecordStore.ts +30 -9
  5. package/agent-runtime/index.ts +7 -0
  6. package/agent-runtime/localLoop.ts +14 -4
  7. package/agent-runtime/localToolPolicy.ts +22 -10
  8. package/agent-runtime/localWorkspaceTools.ts +140 -374
  9. package/agent-runtime/platformChatProvider.ts +3 -0
  10. package/agent-runtime/runtimeToolPolicy.ts +133 -0
  11. package/agent-runtime/types.ts +47 -0
  12. package/agentAliases.ts +1 -1
  13. package/agentCommandSupport.ts +77 -0
  14. package/agentInternalCommandEntries.ts +34 -0
  15. package/agentListCommands.ts +123 -0
  16. package/agentListHelpers.ts +187 -0
  17. package/agentMachineCommands.ts +402 -0
  18. package/agentRecordCommands.ts +141 -0
  19. package/agentRecordHelpers.ts +395 -0
  20. package/agentRunCommand.ts +75 -3
  21. package/agentRuntimeCommands.ts +7 -773
  22. package/ai/agent/_executeModel.ts +29 -3
  23. package/ai/agent/buildSystemPrompt.ts +17 -0
  24. package/ai/agent/cliChatClient.ts +38 -4
  25. package/ai/agent/cliExecutor.ts +304 -25
  26. package/ai/agent/createAgentSchema.ts +2 -2
  27. package/ai/agent/guidedCreation/assistant.ts +82 -0
  28. package/ai/agent/guidedCreation/capabilities.ts +84 -0
  29. package/ai/agent/guidedCreation/draft.ts +62 -0
  30. package/ai/agent/guidedCreation/types.ts +38 -0
  31. package/ai/agent/machineRunPermissions.ts +21 -3
  32. package/ai/agent/runtimeGuidance.ts +1 -1
  33. package/ai/agent/server/fetchPublicAgents.ts +6 -1
  34. package/ai/agent/streamAgentChatTurn.ts +593 -36
  35. package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
  36. package/ai/ai.locale.ts +14 -2
  37. package/ai/chat/fetchUtils.native.ts +2 -0
  38. package/ai/chat/fetchUtils.ts +2 -0
  39. package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
  40. package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
  41. package/ai/llm/getPricing.ts +14 -0
  42. package/ai/llm/kimi.ts +4 -1
  43. package/ai/llm/modelAvatar.ts +1 -0
  44. package/ai/llm/providers.ts +4 -1
  45. package/ai/llm/reasoningModels.ts +1 -0
  46. package/ai/taskRun/taskRunProtocol.ts +25 -10
  47. package/ai/token/modelUsageQuery.ts +17 -0
  48. package/ai/tools/agent/agentTools.ts +38 -0
  49. package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
  50. package/ai/tools/agent/startAgentDialogTool.ts +53 -0
  51. package/ai/tools/checkEnvTool.ts +52 -20
  52. package/ai/tools/convertMarxistsBookTool.ts +103 -0
  53. package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
  54. package/ai/tools/index.ts +24 -14
  55. package/ai/tools/marxistsOfflineBook.ts +253 -0
  56. package/ai/tools/modelUsageTools.ts +9 -2
  57. package/ai/tools/readTool.ts +31 -0
  58. package/ai/tools/toolApiClient.ts +35 -1
  59. package/ai/tools/toolNameAliases.ts +13 -0
  60. package/cliCommandAdapters.ts +28 -0
  61. package/cliCommandDispatch.ts +66 -0
  62. package/cliCommandFactories.ts +34 -0
  63. package/cliCommandTypes.ts +49 -0
  64. package/cliEnvHelpers.ts +44 -0
  65. package/client/agentRun.test.ts +330 -24
  66. package/client/agentRun.ts +117 -35
  67. package/client/hybridRecordStore.test.ts +42 -0
  68. package/client/hybridRecordStore.ts +11 -0
  69. package/client/localRuntimeAdapter.test.ts +191 -21
  70. package/client/localRuntimeAdapter.ts +99 -10
  71. package/client/localRuntimeDryRun.test.ts +5 -2
  72. package/client/taskRunPrompt.ts +1 -0
  73. package/commandRegistry.ts +11 -61
  74. package/connector-experimental/capabilities.ts +1 -0
  75. package/database/actions/common.ts +4 -3
  76. package/database/config.ts +19 -0
  77. package/database/server/coreDataProxy.ts +43 -26
  78. package/index.ts +54 -191
  79. package/internalCommandEntries.ts +12 -0
  80. package/machineCommands.ts +688 -64
  81. package/offlineMarxistsAgentCommand.ts +314 -0
  82. package/package.json +22 -2
  83. package/render/canvas/canvasEditContext.ts +127 -0
  84. package/render/canvas/canvasRuntime.ts +57 -0
  85. package/render/canvas/canvasSnapshotParser.ts +76 -0
  86. package/render/canvas/canvasTree.ts +308 -0
  87. package/render/canvas/types.ts +46 -0
  88. package/render/layout/deleteBehavior.ts +52 -0
  89. package/render/layout/mainLayoutSidebar.ts +17 -0
  90. package/render/layout/mainLayoutViewMode.ts +56 -0
  91. package/render/layout/topbarUtils.ts +87 -0
  92. package/render/layout/useDevReloadPending.ts +30 -0
  93. package/render/page/createPageAction.ts +183 -0
  94. package/render/page/docSlice.ts +468 -0
  95. package/render/page/server/createPage.ts +174 -0
  96. package/render/page/server/handleCreatePage.ts +91 -0
  97. package/render/page/server/index.ts +4 -0
  98. package/render/page/types.ts +17 -0
  99. package/render/page/useKeyboardSave.ts +48 -0
  100. package/render/styles/zIndex.ts +12 -0
  101. package/render/surf/WeatherIconStyles.ts +17 -0
  102. package/render/surf/color.ts +9 -0
  103. package/render/surf/config.ts +46 -0
  104. package/render/surf/screens/style.ts +1 -0
  105. package/render/surf/styles/ToggleButtonStyles.ts +8 -0
  106. package/render/surf/utils/groupedWeatherData.ts +32 -0
  107. package/render/surf/weatherUtils.ts +50 -0
  108. package/render/table/activityColumns.ts +6 -0
  109. package/render/table/createTableAction.ts +270 -0
  110. package/render/table/deleteTableAction.ts +129 -0
  111. package/render/table/fetchAndCacheTableRows.ts +174 -0
  112. package/render/table/tableSlice.ts +1106 -0
  113. package/render/table/tableView.ts +289 -0
  114. package/render/table/toolValueUtils.ts +363 -0
  115. package/render/table/types.ts +252 -0
  116. package/render/table/useCreateTable.ts +72 -0
  117. package/render/table/useTable.ts +61 -0
  118. package/render/table/utils/tableSerialization.ts +50 -0
  119. package/render/web/elements/artifactPreviewCode.ts +43 -0
  120. package/render/web/elements/artifactRuntimePreload.ts +52 -0
  121. package/render/web/elements/codeBlockAutoPreview.ts +10 -0
  122. package/render/web/elements/mermaidPreview.ts +21 -0
  123. package/render/web/ui/useInlineEdit.ts +135 -0
  124. package/runtimeDoctorCommands.ts +118 -0
  125. package/scriptCommandEntries.ts +71 -0
  126. package/systemInternalCommandEntries.ts +71 -0
  127. package/tableCommands.ts +42 -5
  128. package/workflowInternalCommandEntries.ts +11 -0
@@ -4,7 +4,7 @@ import { createDialogMessageKeyAndId } from "database/keys";
4
4
  import { DataType } from "create/types";
5
5
 
6
6
  import type { RootState } from "app/store";
7
- import { patch, read } from "database/dbSlice";
7
+ import { patch, read, selectById } from "database/dbSlice";
8
8
  import { generateRequestBody } from "ai/llm/generateRequestBody";
9
9
  import {
10
10
  selectCurrentDialogConfig,
@@ -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;
@@ -146,8 +174,174 @@ export interface StreamAgentChatTurnArgs {
146
174
  isStreaming?: boolean;
147
175
  parentMessageId?: string;
148
176
  runtimeOptions?: AgentRuntimeOptions;
177
+ quickChatPerfStartedAt?: number;
149
178
  }
150
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
+
198
+ const logQuickChatPerfStage = (
199
+ startedAt: number | undefined,
200
+ stage: string,
201
+ details: Record<string, unknown> = {}
202
+ ) => {
203
+ if (!startedAt) return;
204
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
205
+ console.info("[QuickChatPerf]", {
206
+ stage,
207
+ elapsedMs: now - startedAt,
208
+ ...(typeof performance !== "undefined" ? { atMs: now } : {}),
209
+ ...details,
210
+ });
211
+ };
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
+
151
345
  const normalizeAgentRunUserInput = (userInput: string | any[]) => {
152
346
  if (typeof userInput === "string") {
153
347
  return userInput;
@@ -166,6 +360,75 @@ const normalizeAgentRunUserInput = (userInput: string | any[]) => {
166
360
  });
167
361
  };
168
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
+
421
+ const isUsableAgentConfig = (value: unknown): value is Agent =>
422
+ !!value &&
423
+ typeof value === "object" &&
424
+ (value as any).__ssrPreviewOnly !== true &&
425
+ typeof (value as Agent).dbKey === "string" &&
426
+ !!(value as Agent).dbKey &&
427
+ typeof (value as Agent).model === "string" &&
428
+ !!(value as Agent).model &&
429
+ typeof (value as Agent).provider === "string" &&
430
+ !!(value as Agent).provider;
431
+
169
432
  const extractAgentRunUserText = (userInput: string | any[]) => {
170
433
  if (typeof userInput === "string") {
171
434
  return userInput;
@@ -180,17 +443,6 @@ const extractAgentRunUserText = (userInput: string | any[]) => {
180
443
  .trim();
181
444
  };
182
445
 
183
- const requiresServerResolvedProviderConfig = (agentConfig: Agent): boolean => {
184
- if (agentConfig.useServerProxy !== true) return false;
185
-
186
- const provider = String(agentConfig.provider ?? "").toLowerCase();
187
- const apiSource = String((agentConfig as any).apiSource ?? "").toLowerCase();
188
- const isCustomProvider = provider === "custom" || apiSource === "custom";
189
- if (!isCustomProvider) return false;
190
-
191
- return !agentConfig.customProviderUrl?.trim() || !agentConfig.apiKey?.trim();
192
- };
193
-
194
446
  const hasAgentRunUserInputContent = (userInput: string | any[]) => {
195
447
  if (typeof userInput === "string") {
196
448
  return userInput.trim().length > 0;
@@ -209,7 +461,14 @@ export const streamAgentChatTurnHandler = async (
209
461
  args: StreamAgentChatTurnArgs,
210
462
  thunkApi: any,
211
463
  ) => {
212
- const { agentKey, userInput, dialogKey: explicitDialogKey, parentMessageId, runtimeOptions } = args;
464
+ const {
465
+ agentKey,
466
+ userInput,
467
+ dialogKey: explicitDialogKey,
468
+ parentMessageId,
469
+ runtimeOptions,
470
+ quickChatPerfStartedAt,
471
+ } = args;
213
472
  const { getState, dispatch, rejectWithValue } = thunkApi;
214
473
  const state = getState() as RootState;
215
474
 
@@ -223,15 +482,34 @@ export const streamAgentChatTurnHandler = async (
223
482
  let runtimeDialogKey: string | null = explicitDialogKey ?? null;
224
483
  let remoteTransientMessageId: string | null = null;
225
484
  let remoteTransientMessageFinalized = false;
485
+ let modelRequestStarted = false;
226
486
 
227
487
  try {
228
488
  let totalTurnUsage: any = null;
229
489
  const agentRunUserInput = normalizeAgentRunUserInput(userInput);
230
- // 1. 读取 Agent 配置
231
- const agentConfig = (await dispatch(read({ dbKey: agentKey })).unwrap()) as Agent;
490
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-entered", {
491
+ agentKey,
492
+ dialogKey: explicitDialogKey ?? null,
493
+ });
494
+ // 1. 读取 Agent 配置。Quick Chat 会提前预热默认 agent;命中 Redux DB 缓存时避免重复读。
495
+ const cachedAgentConfig = selectById(getState() as RootState, agentKey);
496
+ const agentConfig = isUsableAgentConfig(cachedAgentConfig)
497
+ ? cachedAgentConfig
498
+ : await readAgentConfigForTurn(
499
+ dispatch,
500
+ agentKey,
501
+ quickChatPerfStartedAt,
502
+ );
232
503
  if (!agentConfig) {
233
504
  return rejectWithValue(`Agent config not found for ID: ${agentKey}`);
234
505
  }
506
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-config-read", {
507
+ agentKey,
508
+ model: agentConfig.model,
509
+ provider: agentConfig.provider,
510
+ apiSource: agentConfig.apiSource,
511
+ source: cachedAgentConfig === agentConfig ? "cache" : "read",
512
+ });
235
513
 
236
514
  // ── CLI Agent 专用路由 ────────────────────────────────────────────────
237
515
  // CLI 共享 prompt / model 这些入口能力,但不复用本地 tool-call 循环。
@@ -330,6 +608,8 @@ export const streamAgentChatTurnHandler = async (
330
608
  userInput: agentRunUserInput,
331
609
  messages: cleanedMessages,
332
610
  stream: true,
611
+ persistDialog: false,
612
+ clientDialogId: dialogId,
333
613
  runtimeContext: {
334
614
  surface: "web",
335
615
  host: "browser",
@@ -449,6 +729,14 @@ export const streamAgentChatTurnHandler = async (
449
729
  cliProvider: agentConfig.cliProvider || "copilot",
450
730
  model: agentConfig.model || undefined,
451
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,
452
740
  },
453
741
  );
454
742
 
@@ -520,6 +808,7 @@ export const streamAgentChatTurnHandler = async (
520
808
  }
521
809
 
522
810
  let accumulated = "";
811
+ let cliCapabilityWarnings: string[] = [];
523
812
  const decoder = new TextDecoder();
524
813
  const buildCliAssistantMessage = () => ({
525
814
  id: messageId,
@@ -579,7 +868,9 @@ export const streamAgentChatTurnHandler = async (
579
868
  ...cliMessageMetadata,
580
869
  }));
581
870
  }
582
- // 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
+ }
583
874
  } catch {
584
875
  // 忽略解析失败的行
585
876
  }
@@ -591,6 +882,18 @@ export const streamAgentChatTurnHandler = async (
591
882
  return;
592
883
  }
593
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
+
594
897
  // 持久化最终消息:用已有 ID,避免 prepareAndPersistMessage 重新生成 ID 导致重复
595
898
  await persistMessageWithFixedId(dispatch, buildCliAssistantMessage());
596
899
  } finally {
@@ -616,6 +919,53 @@ export const streamAgentChatTurnHandler = async (
616
919
  }
617
920
  runtimeDialogKey = dialogKey;
618
921
  const dialogId = extractCustomId(dialogKey);
922
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dialog-resolved", {
923
+ dialogKey,
924
+ dialogId,
925
+ });
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
+ }
619
969
 
620
970
  const userInputText = extractAgentRunUserText(userInput);
621
971
 
@@ -625,12 +975,10 @@ export const streamAgentChatTurnHandler = async (
625
975
  : null;
626
976
  const currentServer = selectCurrentServer(state);
627
977
  const declaredRuntimeServerBase = extractAgentRuntimeServerBase(agentConfig);
628
- const needsServerResolvedProviderConfig =
629
- requiresServerResolvedProviderConfig(agentConfig);
630
978
  const requestedServerBase =
631
979
  explicitServerBase ??
632
980
  declaredRuntimeServerBase ??
633
- (needsServerResolvedProviderConfig ? currentServer : null);
981
+ null;
634
982
  const normalizedRequestedServerBase =
635
983
  requestedServerBase && normalizeServerOrigin(requestedServerBase);
636
984
  const normalizedCurrentServer = normalizeServerOrigin(
@@ -643,12 +991,13 @@ export const streamAgentChatTurnHandler = async (
643
991
  !runtimeOptions?.imageConfigOverride;
644
992
  if (requestedServerBase && canAutoRouteRemotely) {
645
993
  if (
646
- !needsServerResolvedProviderConfig &&
647
994
  normalizedRequestedServerBase &&
648
995
  normalizedCurrentServer &&
649
996
  normalizedRequestedServerBase === normalizedCurrentServer
650
997
  ) {
651
- // same server as current workspace; keep local flow
998
+ // Same server as the current workspace; keep the UI-managed
999
+ // chat/tool loop and let /api/chat hydrate redacted provider
1000
+ // credentials server-side when needed.
652
1001
  } else {
653
1002
  const token = selectCurrentToken(state);
654
1003
  const authHeader = token ? `Bearer ${token}` : "";
@@ -711,6 +1060,8 @@ export const streamAgentChatTurnHandler = async (
711
1060
  userInput: agentRunUserInput,
712
1061
  messages: cleanedMessages,
713
1062
  stream: true,
1063
+ persistDialog: false,
1064
+ clientDialogId: dialogId,
714
1065
  runtimeContext: {
715
1066
  surface: "web",
716
1067
  host: "browser",
@@ -811,6 +1162,10 @@ export const streamAgentChatTurnHandler = async (
811
1162
  recommendedSkillHints: referenceRecommendedSkillHints,
812
1163
  skillPromptPatches: referenceSkillPromptPatches,
813
1164
  } = await resolveReferenceAssets(agentConfig.references, dispatch);
1165
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-references-resolved", {
1166
+ referenceCount: normalizedReferences?.length ?? 0,
1167
+ referencedToolCount: referenceTools?.length ?? 0,
1168
+ });
814
1169
 
815
1170
  const agentConfigWithReferences = {
816
1171
  ...agentConfig,
@@ -906,6 +1261,13 @@ export const streamAgentChatTurnHandler = async (
906
1261
  currentDialog ?? undefined,
907
1262
  mergedContentCache,
908
1263
  );
1264
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-static-context-ready", {
1265
+ model: agentConfigForCall.model,
1266
+ responseApi: true,
1267
+ });
1268
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-post-static-entered", {
1269
+ responseApi: true,
1270
+ });
909
1271
 
910
1272
  let appendTempUserInput = true;
911
1273
  let currentParentMessageId = parentMessageId ?? undefined;
@@ -914,11 +1276,25 @@ export const streamAgentChatTurnHandler = async (
914
1276
  if (w) w.__LOOP_STOP_REASON__ = null;
915
1277
 
916
1278
  loopKey = `loop:${dialogId}`;
1279
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-active-controller-adding", {
1280
+ responseApi: true,
1281
+ });
917
1282
  dispatch(addActiveController({ messageId: loopKey, controller: loopController, dialogKey }));
1283
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-active-controller-added", {
1284
+ responseApi: true,
1285
+ });
918
1286
 
919
1287
  for (;;) {
1288
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-loop-entered", {
1289
+ responseApi: true,
1290
+ });
920
1291
  const requestParentMessageId = currentParentMessageId;
921
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
+ });
922
1298
  if (w) w.__LOOP_STOP_REASON__ = "aborted";
923
1299
  break;
924
1300
  }
@@ -926,6 +1302,11 @@ export const streamAgentChatTurnHandler = async (
926
1302
  const loopState = getState() as RootState;
927
1303
  const now = Date.now();
928
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
+ });
929
1310
  if (w) w.__LOOP_STOP_REASON__ = "timeout";
930
1311
  break;
931
1312
  }
@@ -935,18 +1316,63 @@ export const streamAgentChatTurnHandler = async (
935
1316
  loopState,
936
1317
  );
937
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
+ }
938
1337
  return rejectWithValue(accessError);
939
1338
  }
940
1339
 
941
- const dynamicContexts = await buildDynamicContexts(
942
- loopState,
943
- dispatch,
944
- agentConfigForCall,
1340
+ const willSkipDynamicContext = canUseQuickChatEmptyDynamicContexts(
1341
+ quickChatPerfStartedAt,
945
1342
  userInput,
946
1343
  runtimeOptions,
947
- mergedContentCache,
948
- dialogKey,
1344
+ currentDialog,
949
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
+ );
1373
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-ready", {
1374
+ responseApi: true,
1375
+ });
950
1376
  const contexts = mergeContexts(staticContexts, dynamicContexts);
951
1377
 
952
1378
  const rawMessages = filterMessagesForParallelBranch(
@@ -1014,6 +1440,12 @@ export const streamAgentChatTurnHandler = async (
1014
1440
  userInput: userInputText,
1015
1441
  contexts,
1016
1442
  });
1443
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-model-request-starting", {
1444
+ responseApi: true,
1445
+ dynamicMessageCount: dynamicMessages.length,
1446
+ stableMessageCount: stableMessages.length,
1447
+ });
1448
+ modelRequestStarted = true;
1017
1449
 
1018
1450
  const meta: CompletionMeta = await sendOpenAIResponseRequest({
1019
1451
  bodyData,
@@ -1025,6 +1457,13 @@ export const streamAgentChatTurnHandler = async (
1025
1457
  agentConfigForCall,
1026
1458
  runtimeOptions,
1027
1459
  ),
1460
+ quickChatPerfStartedAt,
1461
+ });
1462
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-model-request-finished", {
1463
+ responseApi: true,
1464
+ hasToolCalls: meta.hasToolCalls,
1465
+ hasHandedOff: meta.hasHandedOff,
1466
+ hasPendingInteraction: meta.hasPendingInteraction,
1028
1467
  });
1029
1468
 
1030
1469
  appendTempUserInput = false;
@@ -1093,6 +1532,13 @@ export const streamAgentChatTurnHandler = async (
1093
1532
  currentDialog ?? undefined,
1094
1533
  mergedContentCache,
1095
1534
  );
1535
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-static-context-ready", {
1536
+ model: agentConfigForCall.model,
1537
+ responseApi: false,
1538
+ });
1539
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-post-static-entered", {
1540
+ responseApi: false,
1541
+ });
1096
1542
 
1097
1543
  let appendTempUserInput = true;
1098
1544
  let currentParentMessageId = parentMessageId ?? undefined;
@@ -1102,13 +1548,27 @@ export const streamAgentChatTurnHandler = async (
1102
1548
 
1103
1549
  if (!isRespModel) {
1104
1550
  loopKey = `loop:${dialogId}`;
1551
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-active-controller-adding", {
1552
+ responseApi: false,
1553
+ });
1105
1554
  dispatch(addActiveController({ messageId: loopKey, controller: loopController, dialogKey }));
1555
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-active-controller-added", {
1556
+ responseApi: false,
1557
+ });
1106
1558
  }
1107
1559
 
1108
1560
  for (;;) {
1561
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-loop-entered", {
1562
+ responseApi: false,
1563
+ });
1109
1564
  const requestParentMessageId = currentParentMessageId;
1110
1565
  // 每轮开始前检查是否已中止
1111
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
+ });
1112
1572
  if (w) w.__LOOP_STOP_REASON__ = "aborted";
1113
1573
  break;
1114
1574
  }
@@ -1116,6 +1576,11 @@ export const streamAgentChatTurnHandler = async (
1116
1576
  const loopState = getState() as RootState;
1117
1577
  const now = Date.now();
1118
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
+ });
1119
1584
  if (w) w.__LOOP_STOP_REASON__ = "timeout";
1120
1585
  break;
1121
1586
  }
@@ -1126,19 +1591,64 @@ export const streamAgentChatTurnHandler = async (
1126
1591
  loopState,
1127
1592
  );
1128
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
+ }
1129
1612
  return rejectWithValue(accessError);
1130
1613
  }
1131
1614
 
1132
- // 🚀 优化:每轮只构建动态上下文(currentInput、history、editingContext、dialogSummary)
1133
- const dynamicContexts = await buildDynamicContexts(
1134
- loopState,
1135
- dispatch,
1136
- agentConfigForCall,
1615
+ const willSkipDynamicContext = canUseQuickChatEmptyDynamicContexts(
1616
+ quickChatPerfStartedAt,
1137
1617
  userInput,
1138
1618
  runtimeOptions,
1139
- mergedContentCache,
1140
- dialogKey,
1619
+ currentDialog,
1141
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
+ );
1649
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-dynamic-context-ready", {
1650
+ responseApi: false,
1651
+ });
1142
1652
 
1143
1653
  // 合并静态和动态上下文
1144
1654
  const contexts = mergeContexts(staticContexts, dynamicContexts);
@@ -1209,6 +1719,32 @@ export const streamAgentChatTurnHandler = async (
1209
1719
  userInput: userInputText,
1210
1720
  contexts,
1211
1721
  });
1722
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-model-request-starting", {
1723
+ responseApi: false,
1724
+ dynamicMessageCount: dynamicMessages.length,
1725
+ stableMessageCount: stableMessages.length,
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
+ }
1212
1748
 
1213
1749
  const meta: CompletionMeta = await sendOpenAICompletionsRequest({
1214
1750
  bodyData,
@@ -1220,6 +1756,14 @@ export const streamAgentChatTurnHandler = async (
1220
1756
  agentConfigForCall,
1221
1757
  runtimeOptions,
1222
1758
  ),
1759
+ disableToolsForThisRequest,
1760
+ quickChatPerfStartedAt,
1761
+ });
1762
+ logQuickChatPerfStage(quickChatPerfStartedAt, "stream-agent-model-request-finished", {
1763
+ responseApi: false,
1764
+ hasToolCalls: meta.hasToolCalls,
1765
+ hasHandedOff: meta.hasHandedOff,
1766
+ hasPendingInteraction: meta.hasPendingInteraction,
1223
1767
  });
1224
1768
 
1225
1769
  appendTempUserInput = false;
@@ -1299,6 +1843,19 @@ export const streamAgentChatTurnHandler = async (
1299
1843
  dispatch(removeTransientMessage(remoteTransientMessageId));
1300
1844
  remoteTransientMessageFinalized = true;
1301
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
+ }
1302
1859
 
1303
1860
  return rejectWithValue(
1304
1861
  error?.message ||