@geminilight/mindos 0.6.52 → 0.6.53

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 (179) hide show
  1. package/_standalone/.mindos-build-version +1 -1
  2. package/_standalone/.next/BUILD_ID +1 -1
  3. package/_standalone/.next/app-path-routes-manifest.json +16 -16
  4. package/_standalone/.next/build-manifest.json +2 -2
  5. package/_standalone/.next/cache/.previewinfo +1 -1
  6. package/_standalone/.next/cache/.rscinfo +1 -1
  7. package/_standalone/.next/cache/config.json +3 -3
  8. package/_standalone/.next/prerender-manifest.json +3 -3
  9. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  10. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error.html +2 -2
  12. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  25. package/_standalone/.next/server/app/agents/page.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  27. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  28. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/ask/route.js +44 -16
  39. package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
  40. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  42. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  43. package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
  44. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
  46. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
  48. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
  50. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
  53. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  54. package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
  55. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  57. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  58. package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  59. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  60. package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
  61. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  62. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
  64. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  65. package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
  66. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
  68. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  71. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  73. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
  75. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  76. package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
  77. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/restart/route.js +1 -1
  79. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  81. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  83. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  85. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/api/settings/test-key/route.js +1 -1
  87. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  88. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  90. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  91. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  93. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  94. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
  96. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  97. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/api/update/route.js +1 -1
  99. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  102. package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
  103. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  104. package/_standalone/.next/server/app/changes/page.js +1 -1
  105. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  106. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  107. package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
  108. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  109. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  110. package/_standalone/.next/server/app/echo/page.js +1 -1
  111. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  112. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  113. package/_standalone/.next/server/app/explore/page.js +1 -1
  114. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  115. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  116. package/_standalone/.next/server/app/help/page.js +1 -1
  117. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  118. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  119. package/_standalone/.next/server/app/login/page.js +1 -1
  120. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  121. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  122. package/_standalone/.next/server/app/page.js +1 -1
  123. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  124. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  125. package/_standalone/.next/server/app/setup/page.js +1 -1
  126. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  127. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  128. package/_standalone/.next/server/app/trash/page.js +3 -3
  129. package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
  130. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  131. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  132. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  133. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  134. package/_standalone/.next/server/app-paths-manifest.json +16 -16
  135. package/_standalone/.next/server/chunks/3436.js +52 -0
  136. package/_standalone/.next/server/chunks/6539.js +1 -1
  137. package/_standalone/.next/server/chunks/8388.js +1 -1
  138. package/_standalone/.next/server/chunks/8408.js +28 -28
  139. package/_standalone/.next/server/pages/500.html +2 -2
  140. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  141. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  142. package/_standalone/.next/static/chunks/{1053-c519cb347faa75e2.js → 1053-17786066ecd6f216.js} +2 -2
  143. package/_standalone/.next/static/chunks/app/{layout-e706f9a963b177ba.js → layout-e5d2fb384207d821.js} +27 -27
  144. package/_standalone/.next/static/chunks/app/{page-2595175820acfd65.js → page-54edd045d3448df5.js} +2 -2
  145. package/_standalone/.next/static/chunks/app/trash/page-6372bb3f5463affa.js +1 -0
  146. package/_standalone/.next/static/chunks/app/view/[...path]/page-f3d1d87190dcfe28.js +12 -0
  147. package/_standalone/.next/trace +64 -64
  148. package/_standalone/components/walkthrough/WalkthroughOverlay.tsx +39 -10
  149. package/_standalone/lib/stores/walkthrough-store.ts +33 -1
  150. package/_standalone/tsconfig.tsbuildinfo +1 -1
  151. package/app/app/api/ask/route.ts +193 -3
  152. package/app/app/api/restart/route.ts +26 -21
  153. package/app/app/api/update/route.ts +24 -10
  154. package/app/components/walkthrough/WalkthroughOverlay.tsx +39 -10
  155. package/app/lib/agent/model.ts +2 -1
  156. package/app/lib/i18n/modules/ai-chat.ts +8 -0
  157. package/app/lib/settings.ts +32 -0
  158. package/app/lib/stores/walkthrough-store.ts +33 -1
  159. package/bin/commands/update.js +23 -11
  160. package/bin/lib/clean-env.js +38 -0
  161. package/bin/lib/gateway.js +41 -4
  162. package/bin/lib/stop.js +40 -4
  163. package/package.json +1 -1
  164. package/_standalone/.next/server/chunks/2673.js +0 -52
  165. package/_standalone/.next/static/chunks/app/trash/page-61c6210fac1e9710.js +0 -1
  166. package/_standalone/.next/static/chunks/app/view/[...path]/page-798359c36f10ef8d.js +0 -12
  167. package/_standalone/lib/i18n/generated/explore-i18n.generated.ts +0 -138
  168. package/_standalone/lib/i18n/index.ts +0 -38
  169. package/_standalone/lib/i18n/modules/ai-chat.ts +0 -237
  170. package/_standalone/lib/i18n/modules/common.ts +0 -71
  171. package/_standalone/lib/i18n/modules/features.ts +0 -153
  172. package/_standalone/lib/i18n/modules/knowledge.ts +0 -727
  173. package/_standalone/lib/i18n/modules/navigation.ts +0 -157
  174. package/_standalone/lib/i18n/modules/onboarding.ts +0 -445
  175. package/_standalone/lib/i18n/modules/panels.ts +0 -1255
  176. package/_standalone/lib/i18n/modules/settings.ts +0 -889
  177. package/_standalone/lib/i18n.ts +0 -3
  178. /package/_standalone/.next/static/{IiTZbEzZo8l5MSUxlDwKp → W-wLgvhPtPJ5Psq9V4i3M}/_buildManifest.js +0 -0
  179. /package/_standalone/.next/static/{IiTZbEzZo8l5MSUxlDwKp → W-wLgvhPtPJ5Psq9V4i3M}/_ssgManifest.js +0 -0
@@ -26,7 +26,8 @@ import { AGENT_SYSTEM_PROMPT, ORGANIZE_SYSTEM_PROMPT, CHAT_SYSTEM_PROMPT } from
26
26
  import type { AskModeApi } from '@/lib/types';
27
27
  import { toAgentMessages } from '@/lib/agent/to-agent-messages';
28
28
  import { logAgentOp } from '@/lib/agent/log';
29
- import { readSettings } from '@/lib/settings';
29
+ import { readSettings, readBaseUrlCompat, writeBaseUrlCompat } from '@/lib/settings';
30
+ import { en as i18nEn, zh as i18nZh } from '@/lib/i18n';
30
31
  import { MindOSError, apiError, ErrorCodes } from '@/lib/errors';
31
32
  import { metrics } from '@/lib/metrics';
32
33
  import { assertNotProtected } from '@/lib/core';
@@ -400,6 +401,134 @@ function toPiCustomToolDefinitions(tools: AgentTool<any>[]): ToolDefinition<any,
400
401
  }));
401
402
  }
402
403
 
404
+ // ---------------------------------------------------------------------------
405
+ // Non-streaming fallback for proxies that don't support stream + tools
406
+ // ---------------------------------------------------------------------------
407
+
408
+ /**
409
+ * Mini agent loop using non-streaming OpenAI-compatible API.
410
+ * Used when a proxy silently breaks stream+tools by returning plain text.
411
+ * Emits SSE events identical to the streaming path so the frontend is unaffected.
412
+ */
413
+ async function runNonStreamingFallback(opts: {
414
+ baseUrl: string;
415
+ apiKey: string;
416
+ model: string;
417
+ systemPrompt: string;
418
+ historyMessages: { role: string; content: unknown }[];
419
+ userContent: string;
420
+ tools: AgentTool<any>[];
421
+ send: (event: MindOSSSEvent) => void;
422
+ signal: AbortSignal;
423
+ maxSteps: number;
424
+ }): Promise<void> {
425
+ const { baseUrl, apiKey, model, systemPrompt, historyMessages, userContent, tools, send, signal, maxSteps } = opts;
426
+
427
+ const openaiTools = tools.map(t => ({
428
+ type: 'function' as const,
429
+ function: {
430
+ name: t.name,
431
+ description: t.description ?? '',
432
+ parameters: (t as any).parameters ?? { type: 'object', properties: {} },
433
+ },
434
+ }));
435
+
436
+ const messages: { role: string; content: unknown; tool_calls?: unknown; tool_call_id?: string }[] = [
437
+ { role: 'system', content: systemPrompt },
438
+ ...historyMessages,
439
+ { role: 'user', content: userContent },
440
+ ];
441
+
442
+ const toolMap = new Map(tools.map(t => [t.name, t]));
443
+ const endpoint = `${baseUrl.replace(/\/$/, '')}/chat/completions`;
444
+ let step = 0;
445
+
446
+ while (step < maxSteps) {
447
+ if (signal.aborted) throw new Error('Request aborted');
448
+ step++;
449
+
450
+ const resp = await fetch(endpoint, {
451
+ method: 'POST',
452
+ headers: {
453
+ 'Content-Type': 'application/json',
454
+ 'Authorization': `Bearer ${apiKey}`,
455
+ },
456
+ body: JSON.stringify({
457
+ model,
458
+ messages,
459
+ tools: openaiTools.length > 0 ? openaiTools : undefined,
460
+ tool_choice: openaiTools.length > 0 ? 'auto' : undefined,
461
+ stream: false,
462
+ }),
463
+ signal,
464
+ });
465
+
466
+ if (!resp.ok) {
467
+ const errText = await resp.text().catch(() => '');
468
+ throw new Error(`Non-streaming API error ${resp.status}: ${errText.slice(0, 200)}`);
469
+ }
470
+
471
+ const data = await resp.json() as any;
472
+ const choice = data?.choices?.[0];
473
+ if (!choice) throw new Error('Empty response from API');
474
+
475
+ const msg = choice.message;
476
+ const finishReason: string = choice.finish_reason ?? 'stop';
477
+
478
+ // Emit text content in chunks to simulate streaming appearance
479
+ if (msg.content) {
480
+ const text: string = typeof msg.content === 'string' ? msg.content : '';
481
+ if (text) {
482
+ const chunkSize = 40;
483
+ for (let i = 0; i < text.length; i += chunkSize) {
484
+ send({ type: 'text_delta', delta: text.slice(i, i + chunkSize) });
485
+ await new Promise(r => setTimeout(r, 8));
486
+ }
487
+ }
488
+ }
489
+
490
+ // No tool calls or naturally stopped → done
491
+ if (finishReason === 'stop' || !msg.tool_calls?.length) break;
492
+
493
+ // Execute each tool call
494
+ const toolResultMessages: { role: string; tool_call_id: string; content: string }[] = [];
495
+ for (const tc of msg.tool_calls) {
496
+ const toolName = tc.function?.name ?? '';
497
+ const toolCallId = tc.id ?? `call_${Date.now()}`;
498
+ let parsedArgs: Record<string, unknown> = {};
499
+ try { parsedArgs = JSON.parse(tc.function?.arguments ?? '{}'); } catch { /* ignore */ }
500
+
501
+ const tool = toolMap.get(toolName);
502
+ send({ type: 'tool_start', toolCallId, toolName, args: parsedArgs });
503
+
504
+ let resultText = '';
505
+ let isError = false;
506
+ if (tool) {
507
+ try {
508
+ const result = await tool.execute(toolCallId, parsedArgs, signal);
509
+ resultText = result.content
510
+ .filter((c: any) => c.type === 'text')
511
+ .map((c: any) => c.text)
512
+ .join('\n');
513
+ } catch (err) {
514
+ resultText = err instanceof Error ? err.message : String(err);
515
+ isError = true;
516
+ }
517
+ } else {
518
+ resultText = `Tool "${toolName}" not found`;
519
+ isError = true;
520
+ }
521
+
522
+ send({ type: 'tool_end', toolCallId, output: resultText, isError });
523
+ toolResultMessages.push({ role: 'tool', tool_call_id: toolCallId, content: resultText });
524
+ }
525
+
526
+ // Append assistant turn + tool results for next iteration
527
+ messages.push({ role: 'assistant', content: msg.content ?? null, tool_calls: msg.tool_calls });
528
+ messages.push(...toolResultMessages);
529
+ }
530
+ }
531
+
403
532
  // ---------------------------------------------------------------------------
404
533
  // POST /api/ask
405
534
  // ---------------------------------------------------------------------------
@@ -433,6 +562,10 @@ export async function POST(req: NextRequest) {
433
562
  // Read agent config from settings
434
563
  const serverSettings = readSettings();
435
564
  const agentConfig = serverSettings.agent ?? {};
565
+
566
+ // Detect locale from Accept-Language header for i18n status messages
567
+ const acceptLang = req.headers.get('accept-language') ?? '';
568
+ const t = acceptLang.startsWith('zh') ? i18nZh.ask : i18nEn.ask;
436
569
  const defaultMaxSteps = askMode === 'chat' ? 8 : (agentConfig.maxSteps ?? 20);
437
570
  const stepLimit = Number.isFinite(body.maxSteps)
438
571
  ? Math.min(30, Math.max(1, Number(body.maxSteps)))
@@ -689,7 +822,7 @@ export async function POST(req: NextRequest) {
689
822
  const provOverride = body.providerOverride && isProviderId(body.providerOverride)
690
823
  ? body.providerOverride as ProviderId
691
824
  : undefined;
692
- const { model, modelName, apiKey, provider } = getModelConfig({
825
+ const { model, modelName, apiKey, provider, baseUrl } = getModelConfig({
693
826
  provider: provOverride,
694
827
  hasImages: hasImages(messages),
695
828
  });
@@ -788,6 +921,7 @@ export async function POST(req: NextRequest) {
788
921
 
789
922
  let hasContent = false;
790
923
  let lastModelError = '';
924
+ const effectiveBaseUrlKey = baseUrl || 'default';
791
925
 
792
926
  session.subscribe((event: AgentEvent) => {
793
927
  if (isTextDeltaEvent(event)) {
@@ -993,6 +1127,36 @@ export async function POST(req: NextRequest) {
993
1127
  safeClose();
994
1128
  } else {
995
1129
  // Route to MindOS agent (existing logic)
1130
+
1131
+ // ── Proxy compatibility check ──
1132
+ // If this baseUrl is known to reject stream+tools, skip session.prompt() entirely
1133
+ // and go straight to the non-streaming fallback path.
1134
+ const compatCache = readBaseUrlCompat();
1135
+ if (compatCache[effectiveBaseUrlKey] === 'non-streaming' && baseUrl && provider === 'openai') {
1136
+ send({ type: 'status', message: t.proxyCompatMode });
1137
+ try {
1138
+ await runNonStreamingFallback({
1139
+ baseUrl,
1140
+ apiKey,
1141
+ model: modelName,
1142
+ systemPrompt,
1143
+ historyMessages: llmHistoryMessages,
1144
+ userContent: typeof lastUserContent === 'string' ? lastUserContent : JSON.stringify(lastUserContent),
1145
+ tools: requestTools,
1146
+ send,
1147
+ signal: req.signal,
1148
+ maxSteps: stepLimit,
1149
+ });
1150
+ metrics.recordRequest(Date.now() - requestStartTime);
1151
+ send({ type: 'done' });
1152
+ } catch (fallbackErr) {
1153
+ metrics.recordRequest(Date.now() - requestStartTime);
1154
+ send({ type: 'error', message: t.proxyCompatFailed(fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)) });
1155
+ }
1156
+ safeClose();
1157
+ return;
1158
+ }
1159
+
996
1160
  // Retry with exponential backoff for transient failures (timeout, rate limit, 5xx).
997
1161
  // Only retry if no content has been streamed yet — once the user sees partial
998
1162
  // output, retrying would produce duplicate/garbled content.
@@ -1022,7 +1186,33 @@ export async function POST(req: NextRequest) {
1022
1186
 
1023
1187
  metrics.recordRequest(Date.now() - requestStartTime);
1024
1188
  if (!hasContent && lastModelError) {
1025
- send({ type: 'error', message: lastModelError });
1189
+ // No content received check if this looks like a proxy stream+tools incompatibility.
1190
+ // Only attempt fallback for OpenAI-compatible endpoints with a custom baseUrl.
1191
+ if (baseUrl && provider === 'openai') {
1192
+ send({ type: 'status', message: t.proxyCompatDetecting });
1193
+ try {
1194
+ await runNonStreamingFallback({
1195
+ baseUrl,
1196
+ apiKey,
1197
+ model: modelName,
1198
+ systemPrompt,
1199
+ historyMessages: llmHistoryMessages,
1200
+ userContent: typeof lastUserContent === 'string' ? lastUserContent : JSON.stringify(lastUserContent),
1201
+ tools: requestTools,
1202
+ send,
1203
+ signal: req.signal,
1204
+ maxSteps: stepLimit,
1205
+ });
1206
+ // Success → cache this endpoint as non-streaming so future requests skip the probe
1207
+ writeBaseUrlCompat(effectiveBaseUrlKey, 'non-streaming');
1208
+ console.log(`[ask] Proxy compat detected: ${effectiveBaseUrlKey} → non-streaming (cached)`);
1209
+ send({ type: 'done' });
1210
+ } catch (fallbackErr) {
1211
+ send({ type: 'error', message: t.proxyCompatAlsoFailed(fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)) });
1212
+ }
1213
+ } else {
1214
+ send({ type: 'error', message: lastModelError });
1215
+ }
1026
1216
  } else {
1027
1217
  send({ type: 'done' });
1028
1218
  }
@@ -3,31 +3,36 @@ import { NextResponse } from 'next/server';
3
3
  import { spawn } from 'node:child_process';
4
4
  import { resolve } from 'node:path';
5
5
 
6
+ /**
7
+ * Strip ALL MINDOS_ and MIND_ prefixed env vars so the restart child
8
+ * process re-derives paths from its own installation root.
9
+ * Preserves old port values via MINDOS_OLD_ for cleanup.
10
+ */
11
+ function cleanEnvForRestart(): { env: NodeJS.ProcessEnv; oldWebPort?: string; oldMcpPort?: string } {
12
+ const cleaned = { ...process.env };
13
+ const oldWebPort = cleaned.MINDOS_WEB_PORT;
14
+ const oldMcpPort = cleaned.MINDOS_MCP_PORT;
15
+ for (const key of Object.keys(cleaned)) {
16
+ if (key.startsWith('MINDOS_') || key.startsWith('MIND_')) {
17
+ delete cleaned[key];
18
+ }
19
+ }
20
+ delete cleaned.AUTH_TOKEN;
21
+ delete cleaned.WEB_PASSWORD;
22
+ delete cleaned.NODE_OPTIONS;
23
+ // Pass old ports so restart command can clean up stale listeners
24
+ if (oldWebPort) cleaned.MINDOS_OLD_WEB_PORT = oldWebPort;
25
+ if (oldMcpPort) cleaned.MINDOS_OLD_MCP_PORT = oldMcpPort;
26
+ return { env: cleaned, oldWebPort, oldMcpPort };
27
+ }
28
+
6
29
  export async function POST() {
7
30
  try {
31
+ // Resolve CLI path BEFORE cleaning env (we still need current vars)
8
32
  const cliPath = process.env.MINDOS_CLI_PATH || resolve(process.env.MINDOS_PROJECT_ROOT || process.cwd(), '..', 'bin', 'cli.js');
9
33
  const nodeBin = process.env.MINDOS_NODE_BIN || process.execPath;
10
- // Use 'restart' (stop all → wait for ports free → start) instead of bare
11
- // 'start' which would fail assertPortFree because the current process and
12
- // its MCP child are still holding the ports.
13
- //
14
- // IMPORTANT: Strip MINDOS_* env vars so the child's loadConfig() reads
15
- // the *updated* config file instead of inheriting stale values from this
16
- // process. Without this, changing ports in the GUI has no effect on the
17
- // restarted server — it would start on the old ports.
18
- //
19
- // Pass the current (old) ports via MINDOS_OLD_* so the restart command
20
- // can clean up processes still listening on the previous ports.
21
- const childEnv = { ...process.env };
22
- const oldWebPort = childEnv.MINDOS_WEB_PORT;
23
- const oldMcpPort = childEnv.MINDOS_MCP_PORT;
24
- delete childEnv.MINDOS_WEB_PORT;
25
- delete childEnv.MINDOS_MCP_PORT;
26
- delete childEnv.MIND_ROOT;
27
- delete childEnv.AUTH_TOKEN;
28
- delete childEnv.WEB_PASSWORD;
29
- if (oldWebPort) childEnv.MINDOS_OLD_WEB_PORT = oldWebPort;
30
- if (oldMcpPort) childEnv.MINDOS_OLD_MCP_PORT = oldMcpPort;
34
+
35
+ const { env: childEnv } = cleanEnvForRestart();
31
36
  const child = spawn(nodeBin, [cliPath, 'restart'], {
32
37
  detached: true,
33
38
  stdio: 'ignore',
@@ -3,25 +3,39 @@ import { NextResponse } from 'next/server';
3
3
  import { spawn } from 'node:child_process';
4
4
  import { resolve } from 'node:path';
5
5
 
6
+ /**
7
+ * Strip ALL MINDOS_ and MIND_ prefixed env vars so the update child
8
+ * process re-derives paths from its own installation root after npm install.
9
+ * This prevents the "fake update" bug where the new process inherits
10
+ * stale MINDOS_PROJECT_ROOT / MINDOS_CLI_PATH pointing to old code.
11
+ */
12
+ function cleanEnvForUpdate(): NodeJS.ProcessEnv {
13
+ const cleaned = { ...process.env };
14
+ for (const key of Object.keys(cleaned)) {
15
+ if (key.startsWith('MINDOS_') || key.startsWith('MIND_')) {
16
+ delete cleaned[key];
17
+ }
18
+ }
19
+ delete cleaned.AUTH_TOKEN;
20
+ delete cleaned.WEB_PASSWORD;
21
+ delete cleaned.NODE_OPTIONS;
22
+ return cleaned;
23
+ }
24
+
6
25
  /**
7
26
  * POST /api/update — trigger `mindos update` as a detached child process.
8
27
  *
9
- * Similar to /api/restart: spawns the CLI command and returns immediately.
10
- * The update process will npm install, remove build stamp, and restart
11
- * the server. The current process will be killed during restart.
28
+ * Spawns the CLI command and returns immediately. The update process will
29
+ * npm install, remove build stamp, and restart the server.
30
+ * The current process will be killed during restart.
12
31
  */
13
32
  export async function POST() {
14
33
  try {
34
+ // Resolve CLI path BEFORE cleaning env (we still need current vars)
15
35
  const cliPath = process.env.MINDOS_CLI_PATH || resolve(process.env.MINDOS_PROJECT_ROOT || process.cwd(), '..', 'bin', 'cli.js');
16
36
  const nodeBin = process.env.MINDOS_NODE_BIN || process.execPath;
17
37
 
18
- // Strip MINDOS_* env vars so the child reads fresh config
19
- const childEnv = { ...process.env };
20
- delete childEnv.MINDOS_WEB_PORT;
21
- delete childEnv.MINDOS_MCP_PORT;
22
- delete childEnv.MIND_ROOT;
23
- delete childEnv.AUTH_TOKEN;
24
- delete childEnv.WEB_PASSWORD;
38
+ const childEnv = cleanEnvForUpdate();
25
39
 
26
40
  const child = spawn(nodeBin, [cliPath, 'update'], {
27
41
  detached: true,
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState, useEffect, useCallback, useId } from 'react';
4
4
  import { useLocale } from '@/lib/stores/locale-store';
5
- import { useWalkthrough } from '@/lib/stores/walkthrough-store';
5
+ import { useWalkthrough, useWalkthroughStore } from '@/lib/stores/walkthrough-store';
6
6
  import { walkthroughSteps } from './steps';
7
7
  import WalkthroughTooltip from './WalkthroughTooltip';
8
8
 
@@ -68,16 +68,31 @@ export default function WalkthroughOverlay() {
68
68
  return () => window.removeEventListener('keydown', handler, true);
69
69
  }, [skipFn]);
70
70
 
71
- // If target element doesn't exist (e.g. Echo not enabled), auto-skip this step
71
+ // If target element doesn't exist (e.g. panel not enabled), auto-skip
72
+ // after a grace period. Use stable store ref to avoid re-triggering on
73
+ // every store change (which caused spurious skips during step transitions).
74
+ const currentStep = wt?.currentStep ?? 0;
72
75
  useEffect(() => {
73
76
  if (!step) return;
74
- const el = document.querySelector(`[data-walkthrough="${step.anchor}"]`);
75
- if (!el) {
76
- // Target not in DOM — skip to next step after a brief delay
77
- const timer = setTimeout(() => wt?.next(), 100);
78
- return () => clearTimeout(timer);
79
- }
80
- }, [step, wt]);
77
+ // Wait 500ms for DOM to settle (sidebar animations, lazy rendering)
78
+ const timer = setTimeout(() => {
79
+ const el = document.querySelector(`[data-walkthrough="${step.anchor}"]`);
80
+ if (!el) {
81
+ useWalkthroughStore.getState().next();
82
+ }
83
+ }, 500);
84
+ return () => clearTimeout(timer);
85
+ // eslint-disable-next-line react-hooks/exhaustive-deps
86
+ }, [currentStep]); // Only re-run when step index changes, not on every store update
87
+
88
+ // Safety timeout: if overlay stays for 60s without user interaction, auto-dismiss.
89
+ // Prevents permanently stuck overlay from any unforeseen edge case.
90
+ useEffect(() => {
91
+ const timer = setTimeout(() => {
92
+ useWalkthroughStore.getState().skip();
93
+ }, 60_000);
94
+ return () => clearTimeout(timer);
95
+ }, [currentStep]); // Reset on each step change
81
96
 
82
97
  if (!wt || !step) return null;
83
98
 
@@ -138,7 +153,7 @@ export default function WalkthroughOverlay() {
138
153
  />
139
154
  </svg>
140
155
 
141
- {/* Tooltip */}
156
+ {/* Tooltip — shown when target element found */}
142
157
  {targetRect && (
143
158
  <WalkthroughTooltip
144
159
  stepIndex={wt.currentStep}
@@ -146,6 +161,20 @@ export default function WalkthroughOverlay() {
146
161
  position={step.position}
147
162
  />
148
163
  )}
164
+
165
+ {/* Fallback dismiss button — shown when target element NOT found.
166
+ Without this, user sees dark overlay but no visible UI to dismiss it. */}
167
+ {!targetRect && (
168
+ <div className="fixed inset-0 z-[101] flex items-center justify-center pointer-events-none">
169
+ <button
170
+ onClick={wt.skip}
171
+ className="pointer-events-auto px-4 py-2 text-sm rounded-lg font-medium transition-all hover:opacity-90"
172
+ style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
173
+ >
174
+ {wt.currentStep === wt.totalSteps - 1 ? '✓' : '→'}
175
+ </button>
176
+ </div>
177
+ )}
149
178
  </>
150
179
  );
151
180
  }
@@ -32,6 +32,7 @@ export function getModelConfig(options?: ModelConfigOverrides): {
32
32
  modelName: string;
33
33
  apiKey: string;
34
34
  provider: ProviderId;
35
+ baseUrl: string;
35
36
  } {
36
37
  const saved = effectiveAiConfig(options?.provider);
37
38
 
@@ -49,7 +50,7 @@ export function getModelConfig(options?: ModelConfigOverrides): {
49
50
  model = ensureVisionCapable(model);
50
51
  }
51
52
 
52
- return { model, modelName, apiKey: cfg.apiKey, provider: cfg.provider };
53
+ return { model, modelName, apiKey: cfg.apiKey, provider: cfg.provider, baseUrl: cfg.baseUrl };
53
54
  }
54
55
 
55
56
  /**
@@ -30,6 +30,10 @@ export const aiChatEn = {
30
30
  errorNoResponse: 'AI returned no content. Possible causes: model does not support streaming, proxy compatibility issue, or request exceeds context limit.',
31
31
  reconnecting: (attempt: number, max: number) => `Connection lost. Reconnecting (${attempt}/${max})...`,
32
32
  reconnectFailed: 'Connection failed after multiple attempts.',
33
+ proxyCompatMode: 'Using compatibility mode (non-streaming)...',
34
+ proxyCompatFailed: (err: string) => `Compatibility mode failed: ${err}. Please check your Base URL, API key, and model name.`,
35
+ proxyCompatDetecting: 'Detecting proxy compatibility, switching to non-streaming mode...',
36
+ proxyCompatAlsoFailed: (err: string) => `Compatibility mode also failed: ${err}. Please check your Base URL, API key, and model name.`,
33
37
  retry: 'Retry',
34
38
  suggestions: [
35
39
  'Summarize this document',
@@ -148,6 +152,10 @@ export const aiChatZh = {
148
152
  errorNoResponse: 'AI 未返回有效内容。可能原因:模型不支持流式输出、中转站兼容性问题、或请求超出上下文限制。',
149
153
  reconnecting: (attempt: number, max: number) => `连接中断,正在重连 (${attempt}/${max})...`,
150
154
  reconnectFailed: '多次重连失败,请检查网络后重试。',
155
+ proxyCompatMode: '正在以兼容模式调用...',
156
+ proxyCompatFailed: (err: string) => `兼容模式失败:${err}。请检查 Base URL、API Key 和模型名称。`,
157
+ proxyCompatDetecting: '正在检测接口兼容性,切换到兼容模式重试...',
158
+ proxyCompatAlsoFailed: (err: string) => `兼容模式也失败了:${err}。请检查 Base URL、API Key 和模型名称。`,
151
159
  retry: '重试',
152
160
  suggestions: [
153
161
  '总结这篇文档',
@@ -51,6 +51,8 @@ export interface ServerSettings {
51
51
  guideState?: GuideState;
52
52
  /** Per-agent ACP overrides (command, args, env, enabled). Keyed by agent ID. */
53
53
  acpAgents?: Record<string, import('./acp/agent-descriptors').AcpAgentOverride>;
54
+ /** Proxy compatibility cache: keyed by baseUrl, value is detected mode. */
55
+ baseUrlCompat?: Record<string, 'streaming' | 'non-streaming'>;
54
56
  }
55
57
 
56
58
  const DEFAULTS: ServerSettings = {
@@ -179,6 +181,15 @@ export function readSettings(): ServerSettings {
179
181
  setupPending: parsed.setupPending === true ? true : undefined,
180
182
  disabledSkills: Array.isArray(parsed.disabledSkills) ? parsed.disabledSkills as string[] : undefined,
181
183
  guideState: parseGuideState(parsed.guideState),
184
+ baseUrlCompat: (() => {
185
+ const raw = parsed.baseUrlCompat;
186
+ if (!raw || typeof raw !== 'object') return undefined;
187
+ const result: Record<string, 'streaming' | 'non-streaming'> = {};
188
+ for (const [k, v] of Object.entries(raw)) {
189
+ if (v === 'streaming' || v === 'non-streaming') result[k] = v;
190
+ }
191
+ return Object.keys(result).length > 0 ? result : undefined;
192
+ })(),
182
193
  };
183
194
  } catch {
184
195
  // Config file missing or corrupt → force setup wizard
@@ -206,6 +217,7 @@ export function writeSettings(settings: ServerSettings): void {
206
217
  if (settings.disabledSkills !== undefined) merged.disabledSkills = settings.disabledSkills;
207
218
  if (settings.guideState !== undefined) merged.guideState = settings.guideState;
208
219
  if (settings.acpAgents !== undefined) merged.acpAgents = settings.acpAgents;
220
+ if (settings.baseUrlCompat !== undefined) merged.baseUrlCompat = settings.baseUrlCompat;
209
221
  // setupPending: false/undefined → remove the field (cleanup); true → set it
210
222
  if ('setupPending' in settings) {
211
223
  if (settings.setupPending) merged.setupPending = true;
@@ -287,3 +299,23 @@ export function effectiveSopRoot(): string {
287
299
  const s = readSettings();
288
300
  return s.mindRoot || process.env.MIND_ROOT || path.join(os.homedir(), 'MindOS', 'mind');
289
301
  }
302
+
303
+ /** Read the baseUrl → compat mode cache from config. Never throws. */
304
+ export function readBaseUrlCompat(): Record<string, 'streaming' | 'non-streaming'> {
305
+ try {
306
+ const s = readSettings();
307
+ return s.baseUrlCompat ?? {};
308
+ } catch {
309
+ return {};
310
+ }
311
+ }
312
+
313
+ /** Persist a baseUrl compatibility detection result. Thread-safe via merge-write. */
314
+ export function writeBaseUrlCompat(baseUrl: string, mode: 'streaming' | 'non-streaming'): void {
315
+ const s = readSettings();
316
+ const updated: Record<string, 'streaming' | 'non-streaming'> = {
317
+ ...(s.baseUrlCompat ?? {}),
318
+ [baseUrl]: mode,
319
+ };
320
+ writeSettings({ ...s, baseUrlCompat: updated });
321
+ }
@@ -21,7 +21,20 @@ export interface WalkthroughStoreState {
21
21
 
22
22
  /* ── Helpers ── */
23
23
 
24
+ const LS_KEY = 'mindos_walkthrough_done';
25
+
26
+ /**
27
+ * Persist walkthrough state to server AND localStorage.
28
+ * localStorage acts as a safety net: even if the server persist fails
29
+ * (e.g. during update restart, network blip), the completion state
30
+ * survives page reload and prevents the "stuck overlay" bug.
31
+ */
24
32
  function persistStep(step: number, dismissed: boolean) {
33
+ // Local safety net — instant, survives server outage
34
+ if (step >= walkthroughSteps.length || dismissed) {
35
+ try { localStorage.setItem(LS_KEY, '1'); } catch {}
36
+ }
37
+ // Server persist — fire-and-forget (localStorage is the safety net)
25
38
  fetch('/api/setup', {
26
39
  method: 'PATCH',
27
40
  headers: { 'Content-Type': 'application/json' },
@@ -31,6 +44,11 @@ function persistStep(step: number, dismissed: boolean) {
31
44
  }).catch((err) => { console.warn('[walkthrough-store] persist failed:', err); });
32
45
  }
33
46
 
47
+ /** Check if walkthrough was completed/dismissed (fast, sync, local) */
48
+ function isLocallyDone(): boolean {
49
+ try { return localStorage.getItem(LS_KEY) === '1'; } catch { return false; }
50
+ }
51
+
34
52
  /* ── Store ── */
35
53
 
36
54
  export const useWalkthroughStore = create<WalkthroughStoreState>((set, get) => {
@@ -84,12 +102,26 @@ export const useWalkthroughStore = create<WalkthroughStoreState>((set, get) => {
84
102
  // Only auto-start on desktop
85
103
  if (window.innerWidth < 768) return () => {};
86
104
 
105
+ // Fast local check: if walkthrough was completed/dismissed, never reactivate.
106
+ // This prevents the "stuck overlay" bug where server persist failed during
107
+ // update restart but localStorage recorded the completion.
108
+ if (isLocallyDone()) return () => {};
109
+
87
110
  fetch('/api/setup')
88
111
  .then(r => r.json())
89
112
  .then(data => {
90
113
  const gs = data.guideState;
91
114
  if (!gs) return;
92
- if (gs.walkthroughDismissed) return;
115
+ if (gs.walkthroughDismissed) {
116
+ try { localStorage.setItem(LS_KEY, '1'); } catch {} // sync local
117
+ return;
118
+ }
119
+
120
+ // If server says completed (step >= totalSteps), mark locally done
121
+ if (typeof gs.walkthroughStep === 'number' && gs.walkthroughStep >= totalSteps) {
122
+ try { localStorage.setItem(LS_KEY, '1'); } catch {};
123
+ return;
124
+ }
93
125
 
94
126
  if (gs.active && !gs.dismissed && gs.walkthroughStep === undefined) {
95
127
  if (isWelcome) {
@@ -15,6 +15,7 @@ import { EXIT } from '../lib/command.js';
15
15
  import { stopMindos } from '../lib/stop.js';
16
16
  import { getLocalIP } from '../lib/startup.js';
17
17
  import { isPortInUse } from '../lib/port.js';
18
+ import { cleanEnvForRestart } from '../lib/clean-env.js';
18
19
 
19
20
  /**
20
21
  * Dynamically resolve the new ROOT after `npm install -g`.
@@ -180,7 +181,7 @@ export const run = async () => {
180
181
  const webPort = updateConfig.port ?? 3456;
181
182
  const mcpPort = updateConfig.mcpPort ?? 8781;
182
183
  console.log(dim(' (Waiting for Web UI to come back up — first run after update includes a rebuild...)'));
183
- const ready = await gateway.waitForHttp(Number(webPort), { retries: 450, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
184
+ const ready = await gateway.waitForHttp(Number(webPort), { retries: 450, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH, expectedVersion: newVersion });
184
185
  if (ready) {
185
186
  const localIP = getLocalIP();
186
187
  console.log(`\n${'─'.repeat(53)}`);
@@ -214,13 +215,29 @@ export const run = async () => {
214
215
  if (wasRunning) {
215
216
  console.log(cyan('\n MindOS is running — restarting to apply the new version...'));
216
217
  stopMindos();
217
- // Wait for ports to free (up to 15s)
218
- const deadline = Date.now() + 15_000;
218
+ // Wait for ports to free (up to 20s) with stabilization check.
219
+ // After first "free" reading, wait 1s and check again to avoid
220
+ // false negatives from TCP TIME_WAIT flickering.
221
+ const deadline = Date.now() + 20_000;
222
+ let portsFree = false;
219
223
  while (Date.now() < deadline) {
220
224
  const busy = await isPortInUse(webPort) || await isPortInUse(mcpPort);
221
- if (!busy) break;
225
+ if (!busy) {
226
+ // Stabilization: wait 1s, then double-check
227
+ await new Promise((r) => setTimeout(r, 1000));
228
+ const stillFree = !(await isPortInUse(webPort)) && !(await isPortInUse(mcpPort));
229
+ if (stillFree) { portsFree = true; break; }
230
+ }
222
231
  await new Promise((r) => setTimeout(r, 500));
223
232
  }
233
+ if (!portsFree) {
234
+ console.log(yellow(' ⚠ Ports not fully released, force-killing remaining processes...'));
235
+ // Last resort: import killByPort and SIGKILL anything on these ports
236
+ const stopLib = await import('../lib/stop.js');
237
+ stopLib.killByPort(webPort);
238
+ stopLib.killByPort(mcpPort);
239
+ await new Promise((r) => setTimeout(r, 2000));
240
+ }
224
241
 
225
242
  // Stage 3: Rebuild
226
243
  writeUpdateStatus('rebuilding', vOpts);
@@ -237,12 +254,7 @@ export const run = async () => {
237
254
  // (`mindos start` has its own build-on-startup logic)
238
255
  writeUpdateStatus('restarting', vOpts);
239
256
  const newCliPath = resolve(updatedRoot, 'bin', 'cli.js');
240
- const childEnv = { ...process.env };
241
- delete childEnv.MINDOS_WEB_PORT;
242
- delete childEnv.MINDOS_MCP_PORT;
243
- delete childEnv.MIND_ROOT;
244
- delete childEnv.AUTH_TOKEN;
245
- delete childEnv.WEB_PASSWORD;
257
+ const childEnv = cleanEnvForRestart();
246
258
  const child = nodeSpawn(
247
259
  process.execPath, [newCliPath, 'start'],
248
260
  { detached: true, stdio: 'ignore', env: childEnv },
@@ -250,7 +262,7 @@ export const run = async () => {
250
262
  child.unref();
251
263
 
252
264
  console.log(dim(' (Waiting for Web UI to come back up...)'));
253
- const ready = await gateway.waitForHttp(webPort, { retries: 180, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
265
+ const ready = await gateway.waitForHttp(webPort, { retries: 180, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH, expectedVersion: newVersion });
254
266
  if (ready) {
255
267
  const localIP = getLocalIP();
256
268
  console.log(`\n${'─'.repeat(53)}`);