@geminilight/mindos 0.6.34 → 0.6.36

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 (172) hide show
  1. package/.env.local.example +2 -2
  2. package/_standalone/.mindos-build-version +1 -1
  3. package/_standalone/.next/BUILD_ID +1 -1
  4. package/_standalone/.next/app-path-routes-manifest.json +15 -15
  5. package/_standalone/.next/build-manifest.json +2 -2
  6. package/_standalone/.next/cache/.previewinfo +1 -1
  7. package/_standalone/.next/cache/.rscinfo +1 -1
  8. package/_standalone/.next/cache/config.json +3 -3
  9. package/_standalone/.next/prerender-manifest.json +3 -3
  10. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/_standalone/.next/server/app/_global-error.html +2 -2
  13. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  21. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  25. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js +1 -1
  27. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  28. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  39. package/_standalone/.next/server/app/api/ask/route.js +5 -5
  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_client-reference-manifest.js +1 -1
  44. package/_standalone/.next/server/app/api/bootstrap/route.js +1 -1
  45. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/export/route.js +1 -1
  48. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  50. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/git/route.js +1 -1
  54. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  57. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  58. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  60. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  62. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  64. package/_standalone/.next/server/app/api/recent-files/route.js +1 -1
  65. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  66. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  68. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  71. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  73. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  75. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  76. package/_standalone/.next/server/app/api/setup/route.js +1 -1
  77. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  83. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  85. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/changes/page.js +1 -1
  87. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  88. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
  90. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  91. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/echo/page.js +1 -1
  93. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  94. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/explore/page.js +1 -1
  96. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  97. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/help/page.js +1 -1
  99. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  100. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/login/page.js +1 -1
  102. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  103. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  104. package/_standalone/.next/server/app/page.js +1 -1
  105. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  106. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  107. package/_standalone/.next/server/app/setup/page.js +2 -2
  108. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  109. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  110. package/_standalone/.next/server/app/trash/page.js +3 -3
  111. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  112. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  113. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  114. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  115. package/_standalone/.next/server/app-paths-manifest.json +15 -15
  116. package/_standalone/.next/server/chunks/4931.js +27 -27
  117. package/_standalone/.next/server/chunks/6539.js +1 -1
  118. package/_standalone/.next/server/chunks/7670.js +1 -1
  119. package/_standalone/.next/server/chunks/{1225.js → 9360.js} +2 -2
  120. package/_standalone/.next/server/pages/500.html +2 -2
  121. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  122. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  123. package/_standalone/.next/static/chunks/{1263-31c1efe3fd8f3f99.js → 1263-79beb8734dee7bbd.js} +2 -2
  124. package/_standalone/.next/static/chunks/app/{layout-1ad561312a9a5b5b.js → layout-d0f6dc4d3e6f0be4.js} +11 -11
  125. package/_standalone/.next/static/chunks/app/{page-e2a6e96831efa703.js → page-404b0bfb5721b7f8.js} +2 -2
  126. package/_standalone/.next/static/chunks/app/setup/page-01ab1f549d636057.js +1 -0
  127. package/_standalone/.next/static/chunks/app/trash/{page-c6e9de9ca4ab4bf7.js → page-a79804c1df44d3cc.js} +1 -1
  128. package/_standalone/.next/static/chunks/app/view/[...path]/{page-ff57a587c3f5490e.js → page-d3aceca36a1a9eb2.js} +2 -2
  129. package/_standalone/.next/trace +53 -53
  130. package/_standalone/components/SyncStatusBar.tsx +1 -1
  131. package/_standalone/components/TableOfContents.tsx +7 -0
  132. package/_standalone/components/setup/StepAI.tsx +66 -3
  133. package/_standalone/components/setup/StepDots.tsx +18 -10
  134. package/_standalone/components/setup/StepKB.tsx +26 -0
  135. package/_standalone/components/setup/constants.tsx +4 -3
  136. package/_standalone/data/skills/mindos/SKILL.md +4 -22
  137. package/_standalone/data/skills/mindos-zh/SKILL.md +2 -20
  138. package/_standalone/lib/agent/context.ts +29 -20
  139. package/_standalone/lib/i18n/modules/onboarding.ts +14 -6
  140. package/_standalone/tsconfig.tsbuildinfo +1 -1
  141. package/app/app/api/ask/route.ts +77 -24
  142. package/app/app/api/bootstrap/route.ts +4 -0
  143. package/app/app/api/export/route.ts +5 -0
  144. package/app/app/api/git/route.ts +2 -1
  145. package/app/app/api/recent-files/route.ts +3 -2
  146. package/app/app/api/setup/route.ts +1 -0
  147. package/app/components/SyncStatusBar.tsx +1 -1
  148. package/app/components/TableOfContents.tsx +7 -0
  149. package/app/components/setup/StepAI.tsx +66 -3
  150. package/app/components/setup/StepDots.tsx +18 -10
  151. package/app/components/setup/StepKB.tsx +26 -0
  152. package/app/components/setup/constants.tsx +4 -3
  153. package/app/components/setup/index.tsx +22 -32
  154. package/app/data/skills/mindos/SKILL.md +4 -22
  155. package/app/data/skills/mindos-zh/SKILL.md +2 -20
  156. package/app/lib/acp/subprocess.ts +3 -2
  157. package/app/lib/agent/context.ts +29 -20
  158. package/app/lib/fs.ts +77 -0
  159. package/app/lib/i18n/modules/onboarding.ts +14 -6
  160. package/app/lib/settings.ts +6 -0
  161. package/bin/cli.js +14 -4
  162. package/bin/lib/stop.js +5 -2
  163. package/package.json +6 -2
  164. package/scripts/setup.js +16 -0
  165. package/skills/mindos/SKILL.md +4 -22
  166. package/skills/mindos/references/write-supplement.md +35 -0
  167. package/skills/mindos-zh/SKILL.md +2 -20
  168. package/skills/mindos-zh/references/README.md +2 -1
  169. package/skills/mindos-zh/references/write-supplement.md +35 -0
  170. package/_standalone/.next/static/chunks/app/setup/page-f8a85accc3be554f.js +0 -1
  171. /package/_standalone/.next/static/{K1Y12u2jxCR7efUgJVpyl → VWZDVb8DL1Pbykh9EOj_Y}/_buildManifest.js +0 -0
  172. /package/_standalone/.next/static/{K1Y12u2jxCR7efUgJVpyl → VWZDVb8DL1Pbykh9EOj_Y}/_ssgManifest.js +0 -0
@@ -95,21 +95,12 @@ description: >
95
95
  | 列出顶层心智空间 | `mindos_list_spaces` | 只需分区概览却拉整棵 `list_files` |
96
96
  | 找文件 | `mindos_search_notes`(2-4 条并行关键词变体)| 单关键词搜索 |
97
97
  | 读内容 | `mindos_read_file` 或 `mindos_read_lines`(大文件) | 只需 10 行却读整文件 |
98
- | 小范围文字修改 | `mindos_update_section` / `update_lines` / `insert_after_heading` | 小修改用 `write_file` |
99
- | 追加到末尾 | `mindos_append_to_file` | 为了加一行重写整文件 |
100
- | 整文件替换 | `mindos_write_file` | 用它做章节级编辑 |
101
- | 新建文件 | `mindos_create_file` | 自动创建父目录,但不会生成空间脚手架文件 |
102
- | 新建心智空间(目录 + README + INSTRUCTION)| `mindos_create_space` | 创建空间的唯一方式。`create_file` 只创建普通目录 |
103
- | 重命名空间目录 | `mindos_rename_space` | `rename_file`(仅文件,不能重命名文件夹)|
104
- | 追加 CSV | `mindos_append_csv`(校验表头)| 手动拼字符串不校验 |
105
- | 重命名前查影响 | `mindos_get_backlinks` | 不查引用就重命名 |
106
- | 查看近期变动 | `mindos_get_recent` | 猜最近改了什么 |
107
- | 恢复历史版本 | `mindos_get_file_at_version` | 让用户回忆之前内容 |
98
+
99
+ 写入、结构变更、历史工具 [references/write-supplement.md](./references/write-supplement.md)。
108
100
 
109
101
  ### 回退
110
102
 
111
103
  - `mindos_bootstrap` 不可用 → 手动读根 `INSTRUCTION.md` + `README.md`。
112
- - 行级/章节级工具不可用 → 读 + 受限 `mindos_write_file`(模拟最小修改)。
113
104
  - 搜索无结果 → 不放弃:(1) 扫上下文中的树;(2) 直接读候选文件;(3) `mindos_list_files` 细化子目录;(4) 用同义词/中英文变体重试。
114
105
 
115
106
  ---
@@ -119,15 +110,6 @@ description: >
119
110
  | 模式 | 适用场景 | 关键步骤 |
120
111
  |------|----------|----------|
121
112
  | **只读问答** | 查找 / 总结 / 引用 | 目录树推断 → 搜索 → 读取 → 标注来源 → 明确说信息缺口 |
122
- | **单文件编辑** | 目标文件明确 | 启动协议 → 读目标 + 局部约定 → 最小修改 → 验证 → 总结 |
123
- | **多文件路由** | 非结构化输入,多个目的地 | 解析为语义单元 → 路由表 → 确认 → 编辑 → 汇总 |
124
- | **对话复盘** | 提炼 / 沉淀会话 | 确认范围 → 抽取决策/踩坑/下一步 → 路由 → 记录变更 |
125
- | **SOP 执行** | 可重复流程 | 完整读 SOP → 分步执行 → 更新过时段落 → 偏差则提议更新 |
126
- | **结构变更** | 重命名 / 移动 / 删除 | `get_backlinks` → 影响报告 → 确认 → 执行 → 更新引用 → 同步 README |
127
- | **CSV 追加** | 追加表格行 | 读表头 → 校验字段 → `mindos_append_csv` |
128
- | **跨 Agent 接力** | 继续其他 Agent 的工作 | 读任务状态+决策 → 无需重新探索直接接续 → 回写进度 |
129
- | **周期性回顾** | 汇总近期变动 | `get_recent`/`get_history` → 读变动文件 → 结构化总结 |
130
- | **交接文档** | 创建简报 | 读来源 → 合成(背景、决策、状态、待办)→ 放项目目录 |
131
113
 
132
114
  写入模式详细执行步骤 → [references/write-supplement.md](./references/write-supplement.md)。
133
115
 
@@ -451,14 +451,15 @@ export function installAutoApproval(acpProc: AcpProcess): () => void {
451
451
 
452
452
  // ── Permission requests (auto-approve all) ──
453
453
  case 'session/request_permission': {
454
- console.log(`[ACP] Auto-approving permission: ${JSON.stringify(params.toolCall ?? {}).slice(0, 200)}`);
454
+ // Auto-approve in production; log in dev for debugging
455
+ if (process.env.NODE_ENV === 'development') console.log(`[ACP] Auto-approving permission: ${JSON.stringify(params.toolCall ?? {}).slice(0, 200)}`);
455
456
  sendResponse(acpProc, req.id, { outcome: { selected: { optionId: 'allow_once' } } });
456
457
  return;
457
458
  }
458
459
 
459
460
  // ── Unknown methods: auto-approve for backwards compat ──
460
461
  default: {
461
- console.log(`[ACP] Auto-approving unknown agent request: ${method} (id=${req.id})`);
462
+ if (process.env.NODE_ENV === 'development') console.log(`[ACP] Auto-approving unknown agent request: ${method} (id=${req.id})`);
462
463
  sendResponse(acpProc, req.id, {});
463
464
  }
464
465
  }
@@ -9,6 +9,15 @@ import type { AgentMessage } from '@mariozechner/pi-agent-core';
9
9
  import type { ToolResultMessage, AssistantMessage, UserMessage } from '@mariozechner/pi-ai';
10
10
  import { countCjkChars } from '@/lib/core/cjk';
11
11
 
12
+ const DEV = process.env.NODE_ENV === 'development';
13
+
14
+ // AgentMessage is opaque; cast to access role/content at runtime.
15
+ interface AgentMessageFields {
16
+ role: string;
17
+ content: string | Array<{ type: string; text?: string }>;
18
+ }
19
+ function asMsg(m: AgentMessage): AgentMessageFields { return m as unknown as AgentMessageFields; }
20
+
12
21
  // ---------------------------------------------------------------------------
13
22
  // Token estimation — CJK-aware (CJK ~1.5 tokens/char, ASCII ~0.25 tokens/char)
14
23
  // ---------------------------------------------------------------------------
@@ -28,7 +37,7 @@ export function estimateStringTokens(text: string): number {
28
37
  /** Rough token count for a single AgentMessage */
29
38
  function messageTokens(msg: AgentMessage): number {
30
39
  if ('content' in msg) {
31
- const content = (msg as any).content;
40
+ const content = asMsg(msg).content;
32
41
  if (typeof content === 'string') return estimateStringTokens(content);
33
42
  if (Array.isArray(content)) {
34
43
  let tokens = 0;
@@ -121,11 +130,11 @@ export function truncateToolOutputs(messages: AgentMessage[]): AgentMessage[] {
121
130
  // Find the index of the last 'toolResult' role message
122
131
  let lastToolIdx = -1;
123
132
  for (let i = messages.length - 1; i >= 0; i--) {
124
- if ((messages[i] as any).role === 'toolResult') { lastToolIdx = i; break; }
133
+ if (asMsg(messages[i]).role === 'toolResult') { lastToolIdx = i; break; }
125
134
  }
126
135
 
127
136
  return messages.map((msg, idx) => {
128
- const m = msg as any;
137
+ const m = asMsg(msg);
129
138
  if (m.role !== 'toolResult' || idx === lastToolIdx) return msg;
130
139
 
131
140
  const toolMsg = m as ToolResultMessage;
@@ -160,7 +169,7 @@ Be concise and factual. Output only the summary, no preamble.`;
160
169
 
161
170
  /** Extract a short text representation from an AgentMessage for summarization */
162
171
  function messageToText(m: AgentMessage): string {
163
- const msg = m as any;
172
+ const msg = asMsg(m);
164
173
  const role = msg.role;
165
174
  let content = '';
166
175
 
@@ -202,7 +211,7 @@ export async function compactMessages(
202
211
  // Adjust split point to avoid cutting between an assistant (with tool calls)
203
212
  // and its tool result.
204
213
  let splitIdx = messages.length - 6;
205
- while (splitIdx > 0 && (messages[splitIdx] as any).role === 'toolResult') {
214
+ while (splitIdx > 0 && asMsg(messages[splitIdx]).role === 'toolResult') {
206
215
  splitIdx--;
207
216
  }
208
217
  if (splitIdx < 2) {
@@ -230,17 +239,17 @@ export async function compactMessages(
230
239
 
231
240
  const summaryText = summaryMessage.content
232
241
  .filter(p => p.type === 'text')
233
- .map(p => (p as any).text)
242
+ .map(p => (p as { text?: string }).text)
234
243
  .join('');
235
244
 
236
- console.log(`[ask] Compacted ${earlyMessages.length} early messages into summary (${summaryText.length} chars)`);
245
+ if (DEV) console.log(`[ask] Compacted ${earlyMessages.length} early messages into summary (${summaryText.length} chars)`);
237
246
 
238
247
  const summaryContent = `[System Note: Older conversation history has been truncated due to context length limits, but here is an AI-generated summary of what was discussed so far.]\n\n${summaryText}`;
239
248
 
240
249
  // If first recent message is also 'user', merge summary into it to avoid
241
250
  // consecutive user messages (Anthropic rejects user→user sequences).
242
- if ((recentMessages[0] as any)?.role === 'user') {
243
- const merged = { ...(recentMessages[0] as any) };
251
+ if (asMsg(recentMessages[0])?.role === 'user') {
252
+ const merged = { ...asMsg(recentMessages[0]) } as AgentMessageFields;
244
253
  if (typeof merged.content === 'string') {
245
254
  merged.content = `${summaryContent}\n\n---\n\n${merged.content}`;
246
255
  } else if (Array.isArray(merged.content)) {
@@ -270,7 +279,7 @@ export async function compactMessages(
270
279
  console.warn('[ask] Compact failed, applying hard prune as fallback:', err);
271
280
  const pruned = hardPrune(messages, systemPrompt, modelName);
272
281
  if (pruned.length < messages.length) {
273
- console.log(`[ask] Hard prune fallback succeeded (${messages.length} → ${pruned.length} messages)`);
282
+ if (DEV) console.log(`[ask] Hard prune fallback succeeded (${messages.length} → ${pruned.length} messages)`);
274
283
  return { messages: pruned, compacted: false };
275
284
  }
276
285
  // If pruning also can't help, let it bubble up so request fails safely
@@ -307,28 +316,28 @@ export function hardPrune(
307
316
  }
308
317
 
309
318
  // Ensure we don't cut between an assistant (with tool calls) and its tool result.
310
- while (cutIdx < messages.length - 1 && (messages[cutIdx] as any).role === 'toolResult') {
319
+ while (cutIdx < messages.length - 1 && asMsg(messages[cutIdx]).role === 'toolResult') {
311
320
  total -= messageTokens(messages[cutIdx]);
312
321
  cutIdx++;
313
322
  }
314
323
 
315
324
  // Ensure first message is 'user' (Anthropic requirement)
316
- while (cutIdx < messages.length - 1 && (messages[cutIdx] as any).role !== 'user') {
325
+ while (cutIdx < messages.length - 1 && asMsg(messages[cutIdx]).role !== 'user') {
317
326
  total -= messageTokens(messages[cutIdx]);
318
327
  cutIdx++;
319
328
  }
320
329
 
321
330
  // Fallback: if no user message found in remaining messages, inject a synthetic one
322
331
  const pruned = cutIdx > 0 ? messages.slice(cutIdx) : messages;
323
- if (pruned.length > 0 && (pruned[0] as any).role !== 'user') {
324
- console.log(`[ask] Hard pruned ${cutIdx} messages, injecting synthetic user message (${messages.length} → ${pruned.length + 1})`);
332
+ if (pruned.length > 0 && asMsg(pruned[0]).role !== 'user') {
333
+ if (DEV) console.log(`[ask] Hard pruned ${cutIdx} messages, injecting synthetic user message (${messages.length} → ${pruned.length + 1})`);
325
334
  const syntheticUser: UserMessage = {
326
335
  role: 'user',
327
336
  content: '[System Note: Older conversation history has been truncated due to context length limits. The user may refer to things you can no longer see. If so, kindly ask them to repeat the context.]',
328
337
  timestamp: Date.now(),
329
338
  };
330
339
  return [syntheticUser as AgentMessage, ...pruned];
331
- } else if (cutIdx > 0 && pruned.length > 0 && (pruned[0] as any).role === 'user') {
340
+ } else if (cutIdx > 0 && pruned.length > 0 && asMsg(pruned[0]).role === 'user') {
332
341
  // If we pruned and the first message IS a user message, prepend the warning to it
333
342
  const firstMsg = { ...pruned[0] } as UserMessage;
334
343
  firstMsg.content = `[System Note: Older conversation history has been truncated due to context length limits. The user may refer to things you can no longer see. If so, kindly ask them to repeat the context.]\n\n` + firstMsg.content;
@@ -336,7 +345,7 @@ export function hardPrune(
336
345
  }
337
346
 
338
347
  if (cutIdx > 0) {
339
- console.log(`[ask] Hard pruned ${cutIdx} messages (${messages.length} → ${messages.length - cutIdx})`);
348
+ if (DEV) console.log(`[ask] Hard pruned ${cutIdx} messages (${messages.length} → ${messages.length - cutIdx})`);
340
349
  return pruned;
341
350
  }
342
351
 
@@ -365,11 +374,11 @@ export function createTransformContext(
365
374
  const preTokens = estimateTokens(result);
366
375
  const sysTokens = estimateStringTokens(systemPrompt);
367
376
  const ctxLimit = getContextLimit(modelName);
368
- console.log(`[ask] Context: ~${preTokens + sysTokens} tokens (messages=${preTokens}, system=${sysTokens}), limit=${ctxLimit}`);
377
+ if (DEV) console.log(`[ask] Context: ~${preTokens + sysTokens} tokens (messages=${preTokens}, system=${sysTokens}), limit=${ctxLimit}`);
369
378
 
370
379
  // 2. Compact if >70% context limit (skip if user disabled)
371
380
  if (contextStrategy === 'auto' && needsCompact(result, systemPrompt, modelName)) {
372
- console.log('[ask] Context >70% limit, compacting...');
381
+ if (DEV) console.log('[ask] Context >70% limit, compacting...');
373
382
  const compactResult = await compactMessages(
374
383
  result,
375
384
  getCompactModel(),
@@ -380,9 +389,9 @@ export function createTransformContext(
380
389
  result = compactResult.messages;
381
390
  if (compactResult.compacted) {
382
391
  const postTokens = estimateTokens(result);
383
- console.log(`[ask] After compact: ~${postTokens + sysTokens} tokens`);
392
+ if (DEV) console.log(`[ask] After compact: ~${postTokens + sysTokens} tokens`);
384
393
  } else {
385
- console.log('[ask] Compact skipped (too few messages or fallback used), hard prune will handle overflow if needed');
394
+ if (DEV) console.log('[ask] Compact skipped (too few messages or fallback used), hard prune will handle overflow if needed');
386
395
  }
387
396
  }
388
397
 
package/app/lib/fs.ts CHANGED
@@ -323,10 +323,19 @@ export function listMindSpaces(): MindSpaceSummary[] {
323
323
  return summarizeTopLevelSpaces(getMindRoot(), ensureCache().tree);
324
324
  }
325
325
 
326
+ /** Appends a structured change event to the change log. */
326
327
  export function appendContentChange(input: ContentChangeInput): ContentChangeEvent {
327
328
  return coreAppendContentChange(getMindRoot(), input);
328
329
  }
329
330
 
331
+ /**
332
+ * Lists content change events with optional filtering.
333
+ * @param options.path Filter by file path (prefix match)
334
+ * @param options.limit Max events to return (default: unlimited)
335
+ * @param options.source Filter by source: 'user' | 'agent' | 'system'
336
+ * @param options.op Filter by operation type (e.g. 'create', 'update', 'delete')
337
+ * @param options.q Free-text search within change descriptions
338
+ */
330
339
  export function listContentChanges(options: {
331
340
  path?: string;
332
341
  limit?: number;
@@ -337,10 +346,12 @@ export function listContentChanges(options: {
337
346
  return coreListContentChanges(getMindRoot(), options);
338
347
  }
339
348
 
349
+ /** Marks all unseen content changes as seen. */
340
350
  export function markContentChangesSeen(): void {
341
351
  coreMarkContentChangesSeen(getMindRoot());
342
352
  }
343
353
 
354
+ /** Returns a summary of content changes (total, unseen count, latest timestamp). */
344
355
  export function getContentChangeSummary(): ContentChangeSummary {
345
356
  return coreGetContentChangeSummary(getMindRoot());
346
357
  }
@@ -411,6 +422,10 @@ export function getDirEntries(dirPath: string): FileNode[] {
411
422
  return nodes;
412
423
  }
413
424
 
425
+ /**
426
+ * Returns the N most recently modified files.
427
+ * @param limit Max files to return (default: 10)
428
+ */
414
429
  export function getRecentlyModified(limit = 10): Array<{ path: string; mtime: number }> {
415
430
  const root = getMindRoot();
416
431
  const allFiles = collectAllFiles();
@@ -447,6 +462,10 @@ export function createFile(filePath: string, initialContent = ''): void {
447
462
  invalidateCacheForNewFile(filePath);
448
463
  }
449
464
 
465
+ /**
466
+ * Deletes a file and moves it to the trash.
467
+ * @returns Trash metadata for undo support
468
+ */
450
469
  export function deleteFile(filePath: string): void {
451
470
  coreDeleteFile(getMindRoot(), filePath);
452
471
  invalidateCacheForDeletedFile(filePath);
@@ -480,20 +499,41 @@ export function convertToSpace(dirPath: string): void {
480
499
 
481
500
  // ─── Public API: Line-level operations (delegated to @mindos/core) ───────────
482
501
 
502
+ /**
503
+ * Reads all lines of a file as an array of strings.
504
+ * @param filePath Relative path from MIND_ROOT
505
+ */
483
506
  export function readLines(filePath: string): string[] {
484
507
  return coreReadLines(getMindRoot(), filePath);
485
508
  }
486
509
 
510
+ /**
511
+ * Inserts lines after the given index (0-based).
512
+ * @param filePath Relative path from MIND_ROOT
513
+ * @param afterIndex Insert after this line index (-1 = prepend)
514
+ * @param lines Lines to insert
515
+ */
487
516
  export function insertLines(filePath: string, afterIndex: number, lines: string[]): void {
488
517
  coreInsertLines(getMindRoot(), filePath, afterIndex, lines);
489
518
  invalidateCacheForFile(filePath);
490
519
  }
491
520
 
521
+ /**
522
+ * Replaces lines in the range [startIndex, endIndex] (inclusive, 0-based).
523
+ * @param filePath Relative path from MIND_ROOT
524
+ * @param startIndex First line to replace
525
+ * @param endIndex Last line to replace
526
+ * @param newLines Replacement lines
527
+ */
492
528
  export function updateLines(filePath: string, startIndex: number, endIndex: number, newLines: string[]): void {
493
529
  coreUpdateLines(getMindRoot(), filePath, startIndex, endIndex, newLines);
494
530
  invalidateCacheForFile(filePath);
495
531
  }
496
532
 
533
+ /**
534
+ * Deletes lines in the range [startIndex, endIndex] (inclusive, 0-based).
535
+ * @throws {MindOSError} If indices are out of range
536
+ */
497
537
  export function deleteLines(filePath: string, startIndex: number, endIndex: number): void {
498
538
  const existing = readLines(filePath);
499
539
  if (startIndex < 0 || endIndex < 0) throw new MindOSError(ErrorCodes.INVALID_RANGE, 'Invalid line index: indices must be >= 0', { startIndex, endIndex });
@@ -505,16 +545,19 @@ export function deleteLines(filePath: string, startIndex: number, endIndex: numb
505
545
 
506
546
  // ─── Public API: High-level semantic operations (delegated to @mindos/core) ──
507
547
 
548
+ /** Appends content to the end of a file with a leading newline separator. */
508
549
  export function appendToFile(filePath: string, content: string): void {
509
550
  coreAppendToFile(getMindRoot(), filePath, content);
510
551
  invalidateCacheForFile(filePath);
511
552
  }
512
553
 
554
+ /** Inserts content after the first occurrence of a markdown heading. */
513
555
  export function insertAfterHeading(filePath: string, heading: string, content: string): void {
514
556
  coreInsertAfterHeading(getMindRoot(), filePath, heading, content);
515
557
  invalidateCacheForFile(filePath);
516
558
  }
517
559
 
560
+ /** Replaces the content of a markdown section (heading to next heading of same or higher level). */
518
561
  export function updateSection(filePath: string, heading: string, newContent: string): void {
519
562
  coreUpdateSection(getMindRoot(), filePath, heading, newContent);
520
563
  invalidateCacheForFile(filePath);
@@ -668,6 +711,10 @@ function generateSnippet(
668
711
 
669
712
  // ─── Public API: CSV (delegated to @mindos/core) ────────────────────────────
670
713
 
714
+ /**
715
+ * Appends a row to a CSV file.
716
+ * @returns Object with the new total row count
717
+ */
671
718
  export function appendCsvRow(filePath: string, row: string[]): { newRowCount: number } {
672
719
  const result = coreAppendCsvRow(getMindRoot(), filePath, row);
673
720
  invalidateCache();
@@ -676,6 +723,10 @@ export function appendCsvRow(filePath: string, row: string[]): { newRowCount: nu
676
723
 
677
724
  // ─── Public API: Move file (delegated to @mindos/core) ──────────────────────
678
725
 
726
+ /**
727
+ * Moves a file from one path to another, updating internal wikilinks.
728
+ * @returns The new path and list of files whose links were updated
729
+ */
679
730
  export function moveFile(fromPath: string, toPath: string): { newPath: string; affectedFiles: string[] } {
680
731
  const result = coreMoveFile(getMindRoot(), fromPath, toPath, coreFindBacklinks);
681
732
  invalidateCache();
@@ -684,14 +735,25 @@ export function moveFile(fromPath: string, toPath: string): { newPath: string; a
684
735
 
685
736
  // ─── Public API: Git operations (delegated to @mindos/core) ─────────────────
686
737
 
738
+ /** Returns whether the knowledge base root is a git repository. */
687
739
  export function isGitRepo(): boolean {
688
740
  return coreIsGitRepo(getMindRoot());
689
741
  }
690
742
 
743
+ /**
744
+ * Returns git log entries for a file.
745
+ * @param filePath Relative path from MIND_ROOT
746
+ * @param limit Max entries (default: 10)
747
+ */
691
748
  export function gitLog(filePath: string, limit = 10): Array<{ hash: string; date: string; message: string; author: string }> {
692
749
  return coreGitLog(getMindRoot(), filePath, limit);
693
750
  }
694
751
 
752
+ /**
753
+ * Shows file content at a specific git commit.
754
+ * @param filePath Relative path from MIND_ROOT
755
+ * @param commit Git commit hash or ref
756
+ */
695
757
  export function gitShowFile(filePath: string, commit: string): string {
696
758
  return coreGitShowFile(getMindRoot(), filePath, commit);
697
759
  }
@@ -716,40 +778,55 @@ import {
716
778
  } from './core/trash';
717
779
  export type { TrashMeta } from './core/trash';
718
780
 
781
+ /** Moves a file to the .mindos/.trash/ directory for later recovery. */
719
782
  export function moveToTrashFile(filePath: string) {
720
783
  const result = coreMoveToTrash(getMindRoot(), filePath);
721
784
  invalidateCache();
722
785
  return result;
723
786
  }
724
787
 
788
+ /**
789
+ * Restores a file from trash to its original path.
790
+ * @param trashId The trash entry ID
791
+ * @param overwrite If true, overwrite existing file at original path
792
+ */
725
793
  export function restoreFromTrash(trashId: string, overwrite = false) {
726
794
  const result = coreRestoreFromTrash(getMindRoot(), trashId, overwrite);
727
795
  invalidateCache();
728
796
  return result;
729
797
  }
730
798
 
799
+ /** Restores a file from trash as a copy (appends suffix to avoid conflict). */
731
800
  export function restoreAsCopy(trashId: string) {
732
801
  const result = coreRestoreAsCopy(getMindRoot(), trashId);
733
802
  invalidateCache();
734
803
  return result;
735
804
  }
736
805
 
806
+ /** Permanently deletes a file from trash (no recovery possible). */
737
807
  export function permanentlyDeleteFromTrash(trashId: string) {
738
808
  corePermanentlyDelete(getMindRoot(), trashId);
739
809
  }
740
810
 
811
+ /** Lists all items currently in the trash. */
741
812
  export function listTrash() {
742
813
  return coreListTrash(getMindRoot());
743
814
  }
744
815
 
816
+ /** Permanently deletes all items in the trash. */
745
817
  export function emptyTrashAll() {
746
818
  return coreEmptyTrash(getMindRoot());
747
819
  }
748
820
 
821
+ /** Removes trash items older than 30 days. Called automatically on listTrash. */
749
822
  export function purgeExpiredTrash() {
750
823
  return corePurgeExpired(getMindRoot());
751
824
  }
752
825
 
826
+ /**
827
+ * Finds all files that link to the given target path via wikilinks.
828
+ * Uses the pre-built LinkIndex for O(1) source lookup.
829
+ */
753
830
  export function findBacklinks(targetPath: string): BacklinkEntry[] {
754
831
  const mindRoot = getMindRoot();
755
832
  // Use LinkIndex for O(1) source lookup, then only scan matching files
@@ -15,7 +15,7 @@ export const onboardingEn = {
15
15
  dismiss: 'Dismiss',
16
16
  },
17
17
  setup: {
18
- stepTitles: ['Knowledge Base', 'AI Provider', 'Ports', 'Security', 'Agent Tools', 'Review'],
18
+ stepTitles: ['Knowledge Base', 'AI Configuration', 'Agent Tools', 'Confirm'],
19
19
  // Step 1
20
20
  kbPath: 'Knowledge base path',
21
21
  kbPathHint: 'Absolute path to your notes directory.',
@@ -56,8 +56,12 @@ export const onboardingEn = {
56
56
  generateToken: 'Generate',
57
57
  copyToken: 'Copy',
58
58
  copiedToken: 'Copied!',
59
- webPassword: 'Web UI Password',
60
- webPasswordHint: 'Optional. Protect browser access with a password.',
59
+ webPassword: 'Web Password',
60
+ webPasswordHint: 'Protect your knowledge base from unauthorized browser access.',
61
+ webPasswordRequired: 'Password is required to protect your data.',
62
+ advancedPorts: 'Advanced Settings',
63
+ tokenSectionTitle: 'MCP Auth Token (auto-generated)',
64
+ tokenSectionHint: 'Used by AI agents to connect to your MindOS server.',
61
65
  // Step 5 — Agent Tools
62
66
  agentToolsTitle: 'Agent Tools',
63
67
  agentToolsHint: 'Select AI agents to configure with MindOS MCP. Agents marked "not installed" can be configured now — they will work once you install the app.',
@@ -210,7 +214,7 @@ export const onboardingZh = {
210
214
  dismiss: '关闭',
211
215
  },
212
216
  setup: {
213
- stepTitles: ['知识库', 'AI 服务商', '端口', '安全', 'Agent 工具', '确认'],
217
+ stepTitles: ['知识库', 'AI 配置', 'Agent 工具', '确认'],
214
218
  // Step 1
215
219
  kbPath: '知识库路径',
216
220
  kbPathHint: '笔记目录的绝对路径。',
@@ -251,8 +255,12 @@ export const onboardingZh = {
251
255
  generateToken: '生成',
252
256
  copyToken: '复制',
253
257
  copiedToken: '已复制!',
254
- webPassword: '网页访问密码',
255
- webPasswordHint: '可选。设置后浏览器访问需要登录。',
258
+ webPassword: '访问密码',
259
+ webPasswordHint: '保护你的知识库,防止未授权的浏览器访问。',
260
+ webPasswordRequired: '密码是必填项,用于保护你的数据。',
261
+ advancedPorts: '高级设置',
262
+ tokenSectionTitle: 'MCP 认证令牌(已自动生成)',
263
+ tokenSectionHint: 'AI Agent 连接你的 MindOS 服务器时使用。',
256
264
  // Step 5 — Agent Tools
257
265
  agentToolsTitle: 'Agent 工具',
258
266
  agentToolsHint: '选择要与 MindOS MCP 配置的 AI Agent。标注「未安装」的 agent 可以先行配置,安装应用后即可生效。',
@@ -48,6 +48,7 @@ export interface ServerSettings {
48
48
  webPassword?: string;
49
49
  startMode?: 'dev' | 'start' | 'daemon';
50
50
  setupPending?: boolean; // true → / redirects to /setup
51
+ setupPort?: number; // temporary port used by GUI setup; cleared on completion
51
52
  disabledSkills?: string[];
52
53
  guideState?: GuideState;
53
54
  /** Per-agent ACP overrides (command, args, env, enabled). Keyed by agent ID. */
@@ -204,6 +205,11 @@ export function writeSettings(settings: ServerSettings): void {
204
205
  if (settings.setupPending) merged.setupPending = true;
205
206
  else delete merged.setupPending;
206
207
  }
208
+ // setupPort: clear when explicitly set to undefined/0 (setup completed)
209
+ if ('setupPort' in settings) {
210
+ if (settings.setupPort) merged.setupPort = settings.setupPort;
211
+ else delete merged.setupPort;
212
+ }
207
213
  fs.writeFileSync(SETTINGS_PATH, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
208
214
  }
209
215
 
package/bin/cli.js CHANGED
@@ -53,7 +53,7 @@ import { loadConfig, getStartMode, isDaemonMode } from './lib/config.js';
53
53
  import { needsBuild, writeBuildStamp, cleanNextDir, ensureAppDeps, hasPrebuiltStandalone } from './lib/build.js';
54
54
  import { isPortInUse, assertPortFree } from './lib/port.js';
55
55
  import { savePids, clearPids } from './lib/pid.js';
56
- import { stopMindos } from './lib/stop.js';
56
+ import { stopMindos, killByPort } from './lib/stop.js';
57
57
  import { printStartupInfo, getLocalIP } from './lib/startup.js';
58
58
  import { spawnMcp } from './lib/mcp-spawn.js';
59
59
  import { parseArgs, EXIT } from './lib/command.js';
@@ -360,12 +360,22 @@ const commands = {
360
360
  const webPort = process.env.MINDOS_WEB_PORT;
361
361
  const mcpPort = process.env.MINDOS_MCP_PORT;
362
362
 
363
+ // Clean up zombie processes from an abandoned GUI setup session.
364
+ // setup.js records a temporary port (setupPort) in config; if the user
365
+ // closed the browser without completing setup, that process is still
366
+ // running. Kill it before we proceed.
367
+ // Also read config for auto-migration below (avoids double readFileSync).
368
+ let startupCfg = {};
369
+ try { startupCfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {}
370
+ if (startupCfg.setupPort && Number(startupCfg.setupPort) !== Number(webPort)) {
371
+ killByPort(Number(startupCfg.setupPort));
372
+ }
373
+
363
374
  // ── Auto-migrate user-rules.md to root user-skill-rules.md ─────────────
364
375
  try {
365
- const cfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
366
- const mr = cfg.mindRoot;
376
+ const mr = startupCfg.mindRoot;
367
377
  if (mr && existsSync(mr)) {
368
- const isZh = cfg.disabledSkills?.includes('mindos');
378
+ const isZh = startupCfg.disabledSkills?.includes('mindos');
369
379
  const sName = isZh ? 'mindos-zh' : 'mindos';
370
380
  const sDir = resolve(mr, '.agents', 'skills', sName);
371
381
  const rootUserRules = resolve(mr, 'user-skill-rules.md');
package/bin/lib/stop.js CHANGED
@@ -9,7 +9,7 @@ import { CONFIG_PATH } from './constants.js';
9
9
  * Tries lsof first, then falls back to parsing `ss` output.
10
10
  * Returns number of processes killed.
11
11
  */
12
- function killByPort(port) {
12
+ export function killByPort(port) {
13
13
  const pidsToKill = new Set();
14
14
 
15
15
  // Method 1: lsof
@@ -82,11 +82,13 @@ export function stopMindos(opts = {}) {
82
82
  }
83
83
 
84
84
  // Read ports from config for port-based cleanup
85
- let webPort = '3456', mcpPort = '8781';
85
+ let webPort = '3456', mcpPort = '8781', setupPort = null;
86
86
  try {
87
87
  const config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
88
88
  if (config.port) webPort = String(config.port);
89
89
  if (config.mcpPort) mcpPort = String(config.mcpPort);
90
+ // Temporary port used by GUI setup — may still have a zombie process
91
+ if (config.setupPort) setupPort = String(config.setupPort);
90
92
  } catch {}
91
93
 
92
94
  const pids = loadPids();
@@ -106,6 +108,7 @@ export function stopMindos(opts = {}) {
106
108
  // are not recorded in the PID file and would otherwise become orphaned.
107
109
  // Include any extra ports (e.g. old ports from before a config change).
108
110
  const portsToClean = new Set([webPort, mcpPort]);
111
+ if (setupPort) portsToClean.add(setupPort);
109
112
  if (opts.extraPorts) {
110
113
  for (const p of opts.extraPorts) portsToClean.add(String(p));
111
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.6.34",
3
+ "version": "0.6.36",
4
4
  "description": "MindOS — Human-Agent Collaborative Mind System. Local-first knowledge base that syncs your mind to all AI Agents via MCP.",
5
5
  "keywords": [
6
6
  "mindos",
@@ -19,6 +19,10 @@
19
19
  "type": "git",
20
20
  "url": "https://github.com/GeminiLight/MindOS"
21
21
  },
22
+ "homepage": "https://mindos.app",
23
+ "bugs": {
24
+ "url": "https://github.com/GeminiLight/MindOS/issues"
25
+ },
22
26
  "bin": {
23
27
  "mindos": "bin/cli.js"
24
28
  },
@@ -58,7 +62,7 @@
58
62
  "!app/package-lock.json"
59
63
  ],
60
64
  "scripts": {
61
- "prepack": "cd mcp && npm install --include=dev && npm run build && cd ../app && npm install --no-workspaces && ./node_modules/.bin/next build --webpack && cd .. && node scripts/prepare-standalone.mjs",
65
+ "prepack": "rm -rf _standalone && cd mcp && npm install --include=dev && npm run build && cd ../app && npm install --no-workspaces && ./node_modules/.bin/next build --webpack && cd .. && node scripts/prepare-standalone.mjs",
62
66
  "setup": "node scripts/setup.js",
63
67
  "dev": "mindos dev",
64
68
  "build": "mindos build",
package/scripts/setup.js CHANGED
@@ -843,6 +843,17 @@ async function startGuiSetup() {
843
843
  try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { /* ignore */ }
844
844
 
845
845
  const isFirstTime = !config.mindRoot;
846
+
847
+ // Clean up zombie process from a previous abandoned setup session.
848
+ if (config.setupPort) {
849
+ try {
850
+ const { killByPort } = await import('../bin/lib/stop.js');
851
+ killByPort(Number(config.setupPort));
852
+ // Brief wait for port to free
853
+ await new Promise(r => setTimeout(r, 500));
854
+ } catch { /* best effort */ }
855
+ }
856
+
846
857
  config.setupPending = true;
847
858
  writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
848
859
 
@@ -890,6 +901,11 @@ async function startGuiSetup() {
890
901
  write(c.yellow(t('guiStarting') + '\n'));
891
902
 
892
903
  // Start the server in the background
904
+ // Record the temporary setup port in config so stopMindos() can clean it up
905
+ // if the user abandons setup mid-way or closes the browser without completing.
906
+ config.setupPort = usePort;
907
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
908
+
893
909
  // Pass MINDOS_WEB_PORT (not PORT) so loadConfig() won't override with the
894
910
  // config file port — this is critical when we need a temporary port.
895
911
  const cliPath = resolve(__dirname, '../bin/cli.js');