@geminilight/mindos 0.6.60 → 0.6.61

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 (161) 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 +22 -22
  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.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/agents/copy-skill/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
  42. package/_standalone/.next/server/app/api/ask/route.js +3 -3
  43. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  44. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
  50. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  54. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  57. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  58. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  60. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/mcp/install/route.js +1 -1
  62. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  64. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  65. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  66. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  68. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  71. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  73. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  75. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  76. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  83. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  85. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  87. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  88. package/_standalone/.next/server/app/changes/page.js +1 -1
  89. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  90. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  91. package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
  92. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  93. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  94. package/_standalone/.next/server/app/echo/page.js +1 -1
  95. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  96. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  97. package/_standalone/.next/server/app/explore/page.js +1 -1
  98. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  99. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/help/page.js +1 -1
  101. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  102. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  103. package/_standalone/.next/server/app/inbox/history/page.js +1 -1
  104. package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
  105. package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
  106. package/_standalone/.next/server/app/login/page.js +1 -1
  107. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  108. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  109. package/_standalone/.next/server/app/page.js +1 -1
  110. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  111. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  112. package/_standalone/.next/server/app/setup/page.js +1 -1
  113. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  114. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  115. package/_standalone/.next/server/app/trash/page.js +3 -3
  116. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  117. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  118. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  119. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  120. package/_standalone/.next/server/app/wiki/page.js +1 -1
  121. package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
  122. package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
  123. package/_standalone/.next/server/app-paths-manifest.json +22 -22
  124. package/_standalone/.next/server/chunks/1550.js +1 -1
  125. package/_standalone/.next/server/chunks/1750.js +1 -1
  126. package/_standalone/.next/server/chunks/530.js +31 -31
  127. package/_standalone/.next/server/chunks/6539.js +1 -1
  128. package/_standalone/.next/server/chunks/{9753.js → 8955.js} +3 -3
  129. package/_standalone/.next/server/pages/500.html +2 -2
  130. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  131. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  132. package/_standalone/.next/static/chunks/{5581-974dfb26d1d1b38f.js → 5581-0c700c20718bd916.js} +2 -2
  133. package/_standalone/.next/static/chunks/app/{layout-6a0169582db11a08.js → layout-a5d5925b47e87cc3.js} +10 -10
  134. package/_standalone/.next/static/chunks/app/trash/page-40bc7316806acd62.js +1 -0
  135. package/_standalone/.next/static/chunks/app/view/[...path]/page-6fbb14b8f322d0f0.js +12 -0
  136. package/_standalone/.next/static/chunks/app/wiki/page-ba36eccf4fe62cfe.js +1 -0
  137. package/_standalone/.next/static/css/{9c558c42831fae58.css → b57c4eb3cc88308b.css} +1 -1
  138. package/_standalone/.next/trace +65 -65
  139. package/_standalone/__tests__/api/mcp-install.test.ts +3 -2
  140. package/_standalone/components/Panel.tsx +14 -0
  141. package/_standalone/components/home/InboxSection.tsx +148 -32
  142. package/_standalone/tsconfig.tsbuildinfo +1 -1
  143. package/app/app/api/ask/route.ts +2 -2
  144. package/app/app/api/mcp/install/route.ts +17 -1
  145. package/app/components/Panel.tsx +14 -0
  146. package/app/components/home/InboxSection.tsx +148 -32
  147. package/app/lib/acp/agent-descriptors.ts +3 -0
  148. package/app/lib/acp/subprocess.ts +56 -8
  149. package/app/lib/i18n/modules/knowledge.ts +10 -0
  150. package/app/lib/mcp-agents.ts +9 -0
  151. package/bin/lib/mcp-agents.js +34 -1
  152. package/bin/lib/mcp-install.js +94 -20
  153. package/bin/lib/toml.js +125 -0
  154. package/package.json +1 -1
  155. package/scripts/build-runtime-archive.sh +5 -1
  156. package/scripts/setup.js +19 -0
  157. package/_standalone/.next/static/chunks/app/trash/page-ed7ba3b0b50223a6.js +0 -1
  158. package/_standalone/.next/static/chunks/app/view/[...path]/page-678139779971cfcb.js +0 -12
  159. package/_standalone/.next/static/chunks/app/wiki/page-194ae9af2d461481.js +0 -1
  160. /package/_standalone/.next/static/{K3wZNf5bj_AFYwtqcRXIf → 5GmVArEG8OX03azKICsGq}/_buildManifest.js +0 -0
  161. /package/_standalone/.next/static/{K3wZNf5bj_AFYwtqcRXIf → 5GmVArEG8OX03azKICsGq}/_ssgManifest.js +0 -0
@@ -20,7 +20,7 @@ import fs from 'fs';
20
20
  import path from 'path';
21
21
  import { getFileContent, getMindRoot, collectAllFiles } from '@/lib/fs';
22
22
  import { getModelConfig, hasImages } from '@/lib/agent/model';
23
- import { isProviderId, type ProviderId } from '@/lib/agent/providers';
23
+ import { isProviderId, type ProviderId, toPiProvider } from '@/lib/agent/providers';
24
24
  import { getRequestScopedTools, getOrganizeTools, getChatTools, WRITE_TOOLS, truncate } from '@/lib/agent/tools';
25
25
  import { AGENT_SYSTEM_PROMPT, ORGANIZE_SYSTEM_PROMPT, CHAT_SYSTEM_PROMPT } from '@/lib/agent/prompt';
26
26
  import type { AskModeApi } from '@/lib/types';
@@ -1016,7 +1016,7 @@ export async function POST(req: NextRequest) {
1016
1016
  const customTools = toPiCustomToolDefinitions(requestTools);
1017
1017
 
1018
1018
  const authStorage = AuthStorage.create();
1019
- authStorage.setRuntimeApiKey(provider, requestApiKey);
1019
+ authStorage.setRuntimeApiKey(toPiProvider(provider), requestApiKey);
1020
1020
  const modelRegistry = new ModelRegistry(authStorage);
1021
1021
  const settingsManager = SettingsManager.inMemory({
1022
1022
  enableSkillCommands: true,
@@ -2,8 +2,9 @@ export const dynamic = 'force-dynamic';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
- import { MCP_AGENTS, expandHome, resolveSkillWorkspaceProfile } from '@/lib/mcp-agents';
5
+ import { MCP_AGENTS, SKILL_AGENT_REGISTRY, expandHome, resolveSkillWorkspaceProfile } from '@/lib/mcp-agents';
6
6
  import { readSettings, recordSkillInstall } from '@/lib/settings';
7
+ import { copyDir, dirExists } from '@/lib/file-ops';
7
8
 
8
9
  /** Parse JSONC — strips comments before JSON.parse. Returns {} for empty/whitespace-only input. */
9
10
  function parseJsonc(text: string): Record<string, unknown> {
@@ -213,6 +214,21 @@ export async function POST(req: NextRequest) {
213
214
  const activeSkill = settings.disabledSkills?.includes('mindos') ? 'mindos-zh' : 'mindos';
214
215
  const skillPath = path.join(skillProfile.workspacePath, activeSkill, 'SKILL.md');
215
216
  recordSkillInstall(key, activeSkill, skillPath);
217
+
218
+ // Auto-copy skill for unsupported agents (QClaw, WorkBuddy, Lingma, etc.)
219
+ const reg = SKILL_AGENT_REGISTRY[key];
220
+ if (reg?.mode === 'unsupported') {
221
+ const projectRoot = process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
222
+ const candidates = [
223
+ path.join(projectRoot, 'skills', activeSkill),
224
+ path.join(projectRoot, 'app', 'data', 'skills', activeSkill),
225
+ ];
226
+ const skillSrc = candidates.find(p => dirExists(p));
227
+ const targetDir = path.join(skillProfile.workspacePath, activeSkill);
228
+ if (skillSrc && !dirExists(targetDir)) {
229
+ await copyDir(skillSrc, targetDir);
230
+ }
231
+ }
216
232
  } catch { /* best-effort, don't fail the install */ }
217
233
 
218
234
  // Verify http connections
@@ -324,6 +324,20 @@ export default function Panel({
324
324
  >
325
325
  <FileTree nodes={fileTree} onNavigate={onNavigate} maxOpenDepth={maxOpenDepth} onImport={onImport} />
326
326
  </div>
327
+ {/* Inbox quick entry — always visible above sync bar */}
328
+ <button
329
+ type="button"
330
+ onClick={() => router.push('/view/Inbox/')}
331
+ className="flex items-center gap-2 mx-2 px-2 py-1.5 text-sm rounded-md transition-colors hover:bg-muted group border-t border-border/40 shrink-0"
332
+ >
333
+ <Inbox size={14} className="shrink-0 text-[var(--amber)]" />
334
+ <span className="flex-1 text-left text-muted-foreground group-hover:text-foreground transition-colors text-xs">
335
+ {t.sidebar.capture}
336
+ </span>
337
+ {inboxCount > 0 && (
338
+ <span className="text-xs font-medium text-[var(--amber)] tabular-nums">{inboxCount}</span>
339
+ )}
340
+ </button>
327
341
  <SyncStatusBar collapsed={false} onOpenSyncSettings={onOpenSyncSettings} />
328
342
  </div>
329
343
 
@@ -15,7 +15,13 @@ import {
15
15
  Check,
16
16
  Clock,
17
17
  ChevronDown,
18
+ X,
19
+ ExternalLink,
20
+ Copy,
21
+ Trash2,
18
22
  } from 'lucide-react';
23
+ import { useRouter } from 'next/navigation';
24
+ import { toast } from '@/lib/toast';
19
25
  import { useLocale } from '@/lib/stores/locale-store';
20
26
  import { encodePath } from '@/lib/utils';
21
27
  import { quickDropToInbox } from '@/lib/inbox-upload';
@@ -55,6 +61,22 @@ export function InboxSection({ isOrganizing: externalOrganizing = false }: Inbox
55
61
  );
56
62
  }, [files, isOrganizing]);
57
63
 
64
+ const handleDeleteFile = useCallback(async (name: string) => {
65
+ try {
66
+ const res = await fetch('/api/inbox', {
67
+ method: 'DELETE',
68
+ headers: { 'Content-Type': 'application/json' },
69
+ body: JSON.stringify({ names: [name] }),
70
+ });
71
+ if (!res.ok) throw new Error('Failed to delete');
72
+ setFiles(prev => prev.filter(f => f.name !== name));
73
+ window.dispatchEvent(new Event('mindos:inbox-updated'));
74
+ toast.success(t.inbox.fileRemoved);
75
+ } catch {
76
+ toast.error(t.inbox.fileRemoveFailed);
77
+ }
78
+ }, [t]);
79
+
58
80
  const handleUpload = useCallback((selected: FileList | null) => {
59
81
  if (!selected || selected.length === 0) return;
60
82
  quickDropToInbox(Array.from(selected), t);
@@ -208,7 +230,7 @@ export function InboxSection({ isOrganizing: externalOrganizing = false }: Inbox
208
230
  <>
209
231
  <div className="flex flex-col gap-0.5 mb-3">
210
232
  {visibleFiles.map((file) => (
211
- <InboxFileRow key={file.path} file={file} />
233
+ <InboxFileRow key={file.path} file={file} onDelete={handleDeleteFile} />
212
234
  ))}
213
235
  </div>
214
236
  {overflowCount > 0 && (
@@ -312,45 +334,139 @@ export function InboxSection({ isOrganizing: externalOrganizing = false }: Inbox
312
334
  );
313
335
  }
314
336
 
315
- function InboxFileRow({ file }: { file: InboxFile }) {
337
+ function InboxFileRow({ file, onDelete }: { file: InboxFile; onDelete: (name: string) => void }) {
316
338
  const { t } = useLocale();
339
+ const router = useRouter();
317
340
  const isCSV = file.name.endsWith('.csv');
318
341
  const age = formatRelativeTime(file.modifiedAt, t.home.relativeTime);
342
+ const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number } | null>(null);
343
+
344
+ const handleNavigate = () => {
345
+ router.push(`/view/${encodePath(file.path)}`);
346
+ };
347
+
348
+ const handleDelete = (e: React.MouseEvent) => {
349
+ e.stopPropagation();
350
+ e.preventDefault();
351
+ onDelete(file.name);
352
+ };
353
+
354
+ const handleContextMenu = (e: React.MouseEvent) => {
355
+ e.preventDefault();
356
+ e.stopPropagation();
357
+ setCtxMenu({ x: e.clientX, y: e.clientY });
358
+ };
319
359
 
320
360
  return (
321
- <Link
322
- href={`/view/${encodePath(file.path)}`}
323
- className="flex items-center gap-3 px-3 py-2 rounded-lg transition-all duration-100 hover:translate-x-0.5 hover:bg-muted group"
324
- >
325
- <span
326
- className={`w-1.5 h-1.5 rounded-full shrink-0 ${
327
- file.isAging ? 'bg-[var(--amber)]/60' : 'bg-[var(--amber)]'
328
- }`}
329
- />
330
- {isCSV ? (
331
- <Table size={12} className="shrink-0 text-success" />
332
- ) : (
333
- <FileText size={12} className="shrink-0 text-muted-foreground" />
334
- )}
335
- <span
336
- className="text-sm truncate flex-1 text-foreground"
337
- title={file.name}
338
- suppressHydrationWarning
361
+ <>
362
+ <div
363
+ role="button"
364
+ tabIndex={0}
365
+ onClick={handleNavigate}
366
+ onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate(); }}
367
+ onContextMenu={handleContextMenu}
368
+ className="flex items-center gap-3 px-3 py-2 rounded-lg transition-all duration-100 hover:translate-x-0.5 hover:bg-muted group cursor-pointer"
339
369
  >
340
- {file.name}
341
- </span>
342
- <span className="text-2xs text-muted-foreground/50 tabular-nums shrink-0">
343
- {age}
344
- </span>
345
- {file.isAging && (
346
- <span title="7+ days">
347
- <AlertCircle
348
- size={11}
349
- className="shrink-0 text-[var(--amber)]/60"
350
- />
370
+ <span
371
+ className={`w-1.5 h-1.5 rounded-full shrink-0 ${
372
+ file.isAging ? 'bg-[var(--amber)]/60' : 'bg-[var(--amber)]'
373
+ }`}
374
+ />
375
+ {isCSV ? (
376
+ <Table size={12} className="shrink-0 text-success" />
377
+ ) : (
378
+ <FileText size={12} className="shrink-0 text-muted-foreground" />
379
+ )}
380
+ <span
381
+ className="text-sm truncate flex-1 text-foreground"
382
+ title={file.name}
383
+ suppressHydrationWarning
384
+ >
385
+ {file.name}
386
+ </span>
387
+ <span className="text-2xs text-muted-foreground/50 tabular-nums shrink-0 group-hover:hidden">
388
+ {age}
351
389
  </span>
390
+ {file.isAging && (
391
+ <span title="7+ days" className="group-hover:hidden">
392
+ <AlertCircle
393
+ size={11}
394
+ className="shrink-0 text-[var(--amber)]/60"
395
+ />
396
+ </span>
397
+ )}
398
+ {/* Hover delete button */}
399
+ <button
400
+ type="button"
401
+ onClick={handleDelete}
402
+ className="hidden group-hover:flex items-center justify-center w-5 h-5 rounded shrink-0 text-muted-foreground/50 hover:text-destructive hover:bg-destructive/10 transition-colors"
403
+ title={t.inbox.removeFile}
404
+ >
405
+ <X size={12} />
406
+ </button>
407
+ </div>
408
+ {ctxMenu && (
409
+ <InboxFileContextMenu
410
+ x={ctxMenu.x}
411
+ y={ctxMenu.y}
412
+ file={file}
413
+ onDelete={() => { setCtxMenu(null); onDelete(file.name); }}
414
+ onClose={() => setCtxMenu(null)}
415
+ />
352
416
  )}
353
- </Link>
417
+ </>
418
+ );
419
+ }
420
+
421
+ /** Right-click context menu for Inbox file items */
422
+ function InboxFileContextMenu({ x, y, file, onDelete, onClose }: {
423
+ x: number; y: number; file: InboxFile; onDelete: () => void; onClose: () => void;
424
+ }) {
425
+ const menuRef = useRef<HTMLDivElement>(null);
426
+ const router = useRouter();
427
+ const { t } = useLocale();
428
+
429
+ useEffect(() => {
430
+ const handleClick = (e: MouseEvent) => {
431
+ if (menuRef.current && !menuRef.current.contains(e.target as Node)) onClose();
432
+ };
433
+ const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
434
+ document.addEventListener('mousedown', handleClick);
435
+ document.addEventListener('keydown', handleKey);
436
+ return () => { document.removeEventListener('mousedown', handleClick); document.removeEventListener('keydown', handleKey); };
437
+ }, [onClose]);
438
+
439
+ const adjX = typeof window !== 'undefined' ? Math.min(x, window.innerWidth - 200) : x;
440
+ const adjY = typeof window !== 'undefined' ? Math.min(y, window.innerHeight - 120) : y;
441
+
442
+ const menuItemClass = 'w-full flex items-center gap-2 px-3 py-1.5 text-sm text-foreground hover:bg-muted transition-colors text-left';
443
+
444
+ return (
445
+ <div
446
+ ref={menuRef}
447
+ className="fixed z-50 min-w-[160px] bg-card border border-border rounded-lg shadow-lg py-1"
448
+ style={{ top: adjY, left: adjX }}
449
+ >
450
+ <button
451
+ className={menuItemClass}
452
+ onClick={() => { router.push(`/view/${encodePath(file.path)}`); onClose(); }}
453
+ >
454
+ <ExternalLink size={14} className="shrink-0" /> {t.inbox.openFile}
455
+ </button>
456
+ <button
457
+ className={menuItemClass}
458
+ onClick={() => { navigator.clipboard.writeText(file.name); toast.copy(); onClose(); }}
459
+ >
460
+ <Copy size={14} className="shrink-0" /> {t.inbox.copyName}
461
+ </button>
462
+ <div className="border-t border-border my-1" />
463
+ <button
464
+ className={`${menuItemClass} text-destructive hover:text-destructive`}
465
+ onClick={onDelete}
466
+ >
467
+ <Trash2 size={14} className="shrink-0" /> {t.inbox.removeFile}
468
+ </button>
469
+ </div>
354
470
  );
355
471
  }
356
472
 
@@ -128,6 +128,9 @@ export const AGENT_DESCRIPTORS: Record<string, AcpAgentDescriptor> = {
128
128
  'qwen-code': { binary: 'qwen-code', cmd: 'qwen-code', args: [], installCmd: 'npm install -g @qwen-code/qwen-code',
129
129
  displayName: 'Qwen Code',
130
130
  description: '阿里通义千问 Qwen 编程智能体。基于 Qwen 大模型,支持代码生成、审查和多语言编程,深度适配中文开发场景。' },
131
+ 'lingma': { binary: 'lingma', cmd: 'lingma', args: [],
132
+ displayName: 'Lingma',
133
+ description: '阿里通义灵码智能编程助手。提供代码补全、智能问答、多文件修改和编程智能体能力,支持 MCP 工具扩展。' },
131
134
  };
132
135
 
133
136
  /* ── Resolution ────────────────────────────────────────────────────────── */
@@ -27,6 +27,10 @@ export interface AcpProcess {
27
27
  agentId: string;
28
28
  proc: ChildProcess;
29
29
  alive: boolean;
30
+ /** Set when the process fails to spawn or exits with an error. */
31
+ spawnError?: string;
32
+ /** Exit code when the process has terminated. */
33
+ exitCode?: number | null;
30
34
  }
31
35
 
32
36
  type MessageCallback = (msg: AcpJsonRpcResponse) => void;
@@ -119,15 +123,18 @@ export function spawnAcpAgent(
119
123
 
120
124
  proc.on('close', (code) => {
121
125
  acpProc.alive = false;
122
- if (code && code !== 0 && stderrBuf.trim()) {
123
- console.error(`[ACP] ${entry.id} exited with code ${code}: ${stderrBuf.trim().slice(0, 500)}`);
126
+ acpProc.exitCode = code;
127
+ if (code && code !== 0) {
128
+ acpProc.spawnError = stderrBuf.trim().slice(0, 500) || `Process exited with code ${code}`;
129
+ console.error(`[ACP] ${entry.id} exited with code ${code}: ${acpProc.spawnError}`);
124
130
  }
125
- messageListeners.delete(id);
126
- requestListeners.delete(id);
131
+ // Do NOT delete listeners here — sendAndWait still needs them to reject.
132
+ // Listeners are cleaned up in killAgent() and by individual unsub calls.
127
133
  });
128
134
 
129
135
  proc.on('error', (err) => {
130
136
  acpProc.alive = false;
137
+ acpProc.spawnError = err.message;
131
138
  console.error(`[ACP] ${entry.id} spawn error:`, err.message);
132
139
  });
133
140
 
@@ -176,20 +183,61 @@ export function sendAndWait(
176
183
  timeoutMs = 30_000,
177
184
  ): Promise<AcpJsonRpcResponse> {
178
185
  return new Promise((resolve, reject) => {
179
- const rpcId = sendMessage(acpProc, method, params);
186
+ // Fail fast if process is already dead (e.g. binary not found).
187
+ if (!acpProc.alive) {
188
+ const reason = acpProc.spawnError || 'Process is not alive';
189
+ reject(new Error(
190
+ `Agent "${acpProc.agentId}" is not running: ${reason}. ` +
191
+ `Please check that the agent is installed and available on your PATH.`,
192
+ ));
193
+ return;
194
+ }
180
195
 
181
- const timer = setTimeout(() => {
196
+ let rpcId: string;
197
+ try {
198
+ rpcId = sendMessage(acpProc, method, params);
199
+ } catch (err) {
200
+ reject(err);
201
+ return;
202
+ }
203
+
204
+ const cleanup = () => {
205
+ clearTimeout(timer);
182
206
  unsub();
207
+ acpProc.proc.removeListener('close', onClose);
208
+ acpProc.proc.removeListener('error', onError);
209
+ };
210
+
211
+ const timer = setTimeout(() => {
212
+ cleanup();
183
213
  reject(new Error(`ACP RPC timeout after ${timeoutMs}ms for method: ${method}`));
184
214
  }, timeoutMs);
185
215
 
186
216
  const unsub = onMessage(acpProc, (msg) => {
187
217
  if (String(msg.id) === rpcId) {
188
- clearTimeout(timer);
189
- unsub();
218
+ cleanup();
190
219
  resolve(msg);
191
220
  }
192
221
  });
222
+
223
+ // Reject immediately if the process dies while we're waiting.
224
+ const onClose = (code: number | null) => {
225
+ cleanup();
226
+ const reason = acpProc.spawnError || `Process exited with code ${code}`;
227
+ reject(new Error(
228
+ `Agent "${acpProc.agentId}" exited unexpectedly: ${reason}. ` +
229
+ `Please check that the agent is installed and available on your PATH.`,
230
+ ));
231
+ };
232
+ const onError = (err: Error) => {
233
+ cleanup();
234
+ reject(new Error(
235
+ `Agent "${acpProc.agentId}" failed to start: ${err.message}. ` +
236
+ `Please check that the agent is installed and available on your PATH.`,
237
+ ));
238
+ };
239
+ acpProc.proc.once('close', onClose);
240
+ acpProc.proc.once('error', onError);
193
241
  });
194
242
  }
195
243
 
@@ -119,6 +119,11 @@ export const knowledgeEn = {
119
119
  noMindRoot: 'Please configure your knowledge base first',
120
120
  saveFailed: 'Failed to save files. Check disk space and permissions.',
121
121
  organizeFailed: 'Could not read files for organizing. Check file permissions.',
122
+ removeFile: 'Remove from Inbox',
123
+ openFile: 'Open',
124
+ copyName: 'Copy Name',
125
+ fileRemoved: 'File removed from Inbox',
126
+ fileRemoveFailed: 'Failed to remove file',
122
127
  },
123
128
  pulse: {
124
129
  title: 'Your Agents',
@@ -498,6 +503,11 @@ export const knowledgeZh = {
498
503
  noMindRoot: '请先配置知识库路径',
499
504
  saveFailed: '保存文件失败,请检查磁盘空间和权限。',
500
505
  organizeFailed: '无法读取文件进行整理,请检查文件权限。',
506
+ removeFile: '从暂存台移除',
507
+ openFile: '打开',
508
+ copyName: '复制文件名',
509
+ fileRemoved: '文件已从暂存台移除',
510
+ fileRemoveFailed: '移除文件失败',
501
511
  },
502
512
  pulse: {
503
513
  title: '你的 Agent',
@@ -262,6 +262,14 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
262
262
  presenceCli: 'workbuddy',
263
263
  presenceDirs: ['~/.workbuddy/'],
264
264
  },
265
+ 'lingma': {
266
+ name: 'Lingma',
267
+ project: null,
268
+ global: '~/.lingma/mcp.json',
269
+ key: 'mcpServers',
270
+ preferredTransport: 'stdio',
271
+ presenceDirs: ['~/.lingma/'],
272
+ },
265
273
  };
266
274
 
267
275
  /**
@@ -291,6 +299,7 @@ export const SKILL_AGENT_REGISTRY: Record<string, SkillAgentRegistration> = {
291
299
  'antigravity': { mode: 'additional', skillAgentName: 'antigravity' },
292
300
  'qclaw': { mode: 'unsupported' },
293
301
  'workbuddy': { mode: 'unsupported' },
302
+ 'lingma': { mode: 'unsupported' },
294
303
  };
295
304
 
296
305
  export interface SkillWorkspaceProfile {
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Shared MCP agent definitions for CLI tools.
3
- * Mirrors app/lib/mcp-agents.ts — keep in sync manually.
3
+ *
4
+ * ⚠️ KEEP IN SYNC WITH:
5
+ * - app/lib/mcp-agents.ts (TypeScript source of truth)
6
+ * - bin/lib/toml.js (TOML format support for agents with format: 'toml')
7
+ * - app/app/api/mcp/install/route.ts (server-side install with TOML merge)
4
8
  *
5
9
  * Each agent entry includes presenceCli / presenceDirs for detecting
6
10
  * whether the agent is installed on the user's machine. To add a new
@@ -226,6 +230,32 @@ export const MCP_AGENTS = {
226
230
  presenceCli: 'agy',
227
231
  presenceDirs: ['~/.gemini/antigravity/'],
228
232
  },
233
+ 'qclaw': {
234
+ name: 'QClaw',
235
+ project: null,
236
+ global: '~/.qclaw/mcp.json',
237
+ key: 'mcpServers',
238
+ preferredTransport: 'stdio',
239
+ presenceCli: 'qclaw',
240
+ presenceDirs: ['~/.qclaw/'],
241
+ },
242
+ 'workbuddy': {
243
+ name: 'WorkBuddy',
244
+ project: null,
245
+ global: '~/.workbuddy/mcp.json',
246
+ key: 'mcpServers',
247
+ preferredTransport: 'stdio',
248
+ presenceCli: 'workbuddy',
249
+ presenceDirs: ['~/.workbuddy/'],
250
+ },
251
+ 'lingma': {
252
+ name: 'Lingma',
253
+ project: null,
254
+ global: '~/.lingma/mcp.json',
255
+ key: 'mcpServers',
256
+ preferredTransport: 'stdio',
257
+ presenceDirs: ['~/.lingma/'],
258
+ },
229
259
  };
230
260
 
231
261
  /**
@@ -253,6 +283,9 @@ export const SKILL_AGENT_REGISTRY = {
253
283
  'github-copilot': { mode: 'universal' },
254
284
  'codex': { mode: 'universal' },
255
285
  'antigravity': { mode: 'additional', skillAgentName: 'antigravity' },
286
+ 'qclaw': { mode: 'unsupported' },
287
+ 'workbuddy': { mode: 'unsupported' },
288
+ 'lingma': { mode: 'unsupported' },
256
289
  };
257
290
 
258
291
  export function detectAgentPresence(agentKey) {
@@ -1,10 +1,62 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
- import { resolve } from 'node:path';
3
- import { CONFIG_PATH } from './constants.js';
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync } from 'node:fs';
2
+ import { resolve, join } from 'node:path';
3
+ import { CONFIG_PATH, ROOT } from './constants.js';
4
4
  import { bold, dim, cyan, green, red, yellow } from './colors.js';
5
5
  import { expandHome } from './path-expand.js';
6
6
  import { parseJsonc } from './jsonc.js';
7
- import { MCP_AGENTS, detectAgentPresence } from './mcp-agents.js';
7
+ import { MCP_AGENTS, SKILL_AGENT_REGISTRY, detectAgentPresence } from './mcp-agents.js';
8
+ import { mergeTomlEntry } from './toml.js';
9
+
10
+ /**
11
+ * Recursively copy a directory using pure Node.js (cross-platform).
12
+ * Uses cpSync on Node >=16.7, falls back to manual walk otherwise.
13
+ */
14
+ function copyDirSync(src, dst) {
15
+ mkdirSync(dst, { recursive: true });
16
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
17
+ const s = join(src, entry.name);
18
+ const d = join(dst, entry.name);
19
+ if (entry.isDirectory()) {
20
+ copyDirSync(s, d);
21
+ } else {
22
+ copyFileSync(s, d);
23
+ }
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Auto-copy skill folder for agents with mode 'unsupported'.
29
+ * Called after MCP config is written. Best-effort, never throws.
30
+ */
31
+ function autoInstallSkillForAgent(agentKey, skillName) {
32
+ const reg = SKILL_AGENT_REGISTRY[agentKey];
33
+ if (!reg || reg.mode !== 'unsupported') return null;
34
+
35
+ const agent = MCP_AGENTS[agentKey];
36
+ if (!agent) return null;
37
+
38
+ // Resolve skill source: project skills/ or app/data/skills/
39
+ const candidates = [
40
+ join(ROOT, 'skills', skillName),
41
+ join(ROOT, 'app', 'data', 'skills', skillName),
42
+ ];
43
+ const skillSrc = candidates.find(p => existsSync(p));
44
+ if (!skillSrc) return null;
45
+
46
+ // Resolve target: agent's presenceDirs[0]/skills/<skillName>
47
+ const agentRoot = (agent.presenceDirs ?? []).map(d => expandHome(d)).find(d => existsSync(d))
48
+ || resolve(expandHome(agent.global), '..');
49
+ const targetDir = join(agentRoot, 'skills', skillName);
50
+
51
+ if (existsSync(targetDir)) return 'exists';
52
+
53
+ try {
54
+ copyDirSync(skillSrc, targetDir);
55
+ return 'copied';
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
8
60
 
9
61
  export { MCP_AGENTS };
10
62
 
@@ -195,8 +247,15 @@ export async function mcpInstall() {
195
247
  const abs = expandHome(cfgPath);
196
248
  if (!existsSync(abs)) continue;
197
249
  try {
198
- const config = parseJsonc(readFileSync(abs, 'utf-8'));
199
- if (config[agent.key]?.mindos) { installed = true; break; }
250
+ const content = readFileSync(abs, 'utf-8');
251
+ if (agent.format === 'toml') {
252
+ // TOML: look for [section.mindos] header
253
+ installed = content.includes(`[${agent.key}.mindos]`);
254
+ } else {
255
+ const config = parseJsonc(content);
256
+ if (config[agent.key]?.mindos) installed = true;
257
+ }
258
+ if (installed) break;
200
259
  } catch {}
201
260
  }
202
261
  const hint = installed ? 'configured' : present ? 'detected' : 'not found';
@@ -312,24 +371,39 @@ export async function mcpInstall() {
312
371
 
313
372
  // read + merge — resolve to absolute path for cross-platform safety
314
373
  const absPath = resolve(expandHome(configPath));
315
- let config = {};
316
- if (existsSync(absPath)) {
317
- try { config = parseJsonc(readFileSync(absPath, 'utf-8')); } catch {
318
- console.error(red(` Failed to parse existing config: ${absPath} — skipping.`));
319
- continue;
320
- }
321
- }
322
-
323
- if (!config[agent.key]) config[agent.key] = {};
324
- const existed = !!config[agent.key].mindos;
325
- config[agent.key].mindos = entry;
326
-
327
- // write
328
374
  const dir = resolve(absPath, '..');
329
375
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
330
- writeFileSync(absPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
376
+
377
+ let existed = false;
378
+
379
+ if (agent.format === 'toml') {
380
+ // TOML format (e.g. Codex): line-based merge preserving existing content
381
+ const existing = existsSync(absPath) ? readFileSync(absPath, 'utf-8') : '';
382
+ existed = existing.includes(`[${agent.key}.mindos]`);
383
+ const merged = mergeTomlEntry(existing, agent.key, 'mindos', entry);
384
+ writeFileSync(absPath, merged, 'utf-8');
385
+ } else {
386
+ // JSON format (default)
387
+ let config = {};
388
+ if (existsSync(absPath)) {
389
+ try { config = parseJsonc(readFileSync(absPath, 'utf-8')); } catch {
390
+ console.error(red(` Failed to parse existing config: ${absPath} — skipping.`));
391
+ continue;
392
+ }
393
+ }
394
+ if (!config[agent.key]) config[agent.key] = {};
395
+ existed = !!config[agent.key].mindos;
396
+ config[agent.key].mindos = entry;
397
+ writeFileSync(absPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
398
+ }
331
399
 
332
400
  console.log(`${green('✔')} ${existed ? 'Updated' : 'Installed'} MindOS MCP for ${bold(agent.name)} ${dim(`→ ${absPath}`)}`);
401
+
402
+ // Auto-copy skill for unsupported agents
403
+ const skillResult = autoInstallSkillForAgent(agentKey, 'mindos');
404
+ if (skillResult === 'copied') {
405
+ console.log(`${green('✔')} Copied MindOS Skill for ${bold(agent.name)}`);
406
+ }
333
407
  }
334
408
 
335
409
  console.log(`\n${green('Done!')} ${agentKeys.length} agent(s) configured.`);