@geminilight/mindos 0.5.63 → 0.5.65
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.
- package/README.md +4 -0
- package/README_zh.md +4 -0
- package/app/app/api/ask/route.ts +12 -0
- package/app/app/api/changes/route.ts +7 -1
- package/app/app/api/file/route.ts +9 -0
- package/app/app/api/mcp/agents/route.ts +27 -1
- package/app/app/api/mcp/install-skill/route.ts +9 -24
- package/app/app/api/skills/route.ts +18 -2
- package/app/app/api/tree-version/route.ts +8 -0
- package/app/app/layout.tsx +1 -0
- package/app/app/page.tsx +1 -2
- package/app/app/view/[...path]/ViewPageClient.tsx +0 -1
- package/app/components/ActivityBar.tsx +2 -2
- package/app/components/Backlinks.tsx +5 -5
- package/app/components/CreateSpaceModal.tsx +3 -2
- package/app/components/DirPicker.tsx +1 -1
- package/app/components/DirView.tsx +2 -3
- package/app/components/EditorWrapper.tsx +3 -3
- package/app/components/FileTree.tsx +25 -10
- package/app/components/GuideCard.tsx +4 -4
- package/app/components/HomeContent.tsx +44 -14
- package/app/components/MarkdownView.tsx +2 -2
- package/app/components/OnboardingView.tsx +1 -1
- package/app/components/Panel.tsx +1 -1
- package/app/components/RightAgentDetailPanel.tsx +2 -1
- package/app/components/RightAskPanel.tsx +1 -1
- package/app/components/SearchModal.tsx +10 -2
- package/app/components/SidebarLayout.tsx +36 -10
- package/app/components/ThemeToggle.tsx +1 -1
- package/app/components/agents/AgentDetailContent.tsx +454 -59
- package/app/components/agents/AgentsContentPage.tsx +89 -20
- package/app/components/agents/AgentsMcpSection.tsx +513 -85
- package/app/components/agents/AgentsOverviewSection.tsx +418 -59
- package/app/components/agents/AgentsPrimitives.tsx +335 -0
- package/app/components/agents/AgentsSkillsSection.tsx +746 -105
- package/app/components/agents/SkillDetailPopover.tsx +416 -0
- package/app/components/agents/agents-content-model.ts +308 -10
- package/app/components/ask/AskContent.tsx +34 -5
- package/app/components/ask/FileChip.tsx +1 -0
- package/app/components/ask/MentionPopover.tsx +13 -1
- package/app/components/ask/MessageList.tsx +5 -7
- package/app/components/ask/ToolCallBlock.tsx +4 -4
- package/app/components/changes/ChangesBanner.tsx +89 -13
- package/app/components/changes/ChangesContentPage.tsx +134 -51
- package/app/components/echo/EchoHero.tsx +10 -24
- package/app/components/echo/EchoInsightCollapsible.tsx +52 -43
- package/app/components/echo/EchoPageSections.tsx +13 -9
- package/app/components/echo/EchoSegmentNav.tsx +14 -11
- package/app/components/echo/EchoSegmentPageClient.tsx +64 -43
- package/app/components/explore/ExploreContent.tsx +3 -7
- package/app/components/explore/UseCaseCard.tsx +4 -15
- package/app/components/panels/AgentsPanel.tsx +22 -128
- package/app/components/panels/AgentsPanelAgentDetail.tsx +7 -6
- package/app/components/panels/AgentsPanelAgentGroups.tsx +8 -13
- package/app/components/panels/AgentsPanelAgentListRow.tsx +39 -16
- package/app/components/panels/AgentsPanelHubNav.tsx +12 -12
- package/app/components/panels/EchoPanel.tsx +8 -10
- package/app/components/panels/PanelNavRow.tsx +9 -2
- package/app/components/panels/PluginsPanel.tsx +5 -5
- package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +30 -8
- package/app/components/renderers/agent-inspector/manifest.ts +5 -3
- package/app/components/renderers/config/manifest.ts +1 -0
- package/app/components/renderers/csv/manifest.ts +1 -0
- package/app/components/renderers/todo/manifest.ts +1 -0
- package/app/components/settings/AiTab.tsx +3 -3
- package/app/components/settings/AppearanceTab.tsx +2 -2
- package/app/components/settings/KnowledgeTab.tsx +3 -3
- package/app/components/settings/McpAgentInstall.tsx +3 -6
- package/app/components/settings/McpSkillCreateForm.tsx +2 -3
- package/app/components/settings/McpSkillRow.tsx +2 -3
- package/app/components/settings/McpSkillsSection.tsx +2 -2
- package/app/components/settings/McpTab.tsx +12 -13
- package/app/components/settings/MonitoringTab.tsx +13 -13
- package/app/components/settings/PluginsTab.tsx +6 -5
- package/app/components/settings/Primitives.tsx +3 -4
- package/app/components/settings/SettingsContent.tsx +3 -3
- package/app/components/settings/SyncTab.tsx +11 -17
- package/app/components/settings/UpdateTab.tsx +18 -21
- package/app/components/settings/types.ts +14 -0
- package/app/components/setup/StepKB.tsx +1 -1
- package/app/hooks/useMcpData.tsx +7 -4
- package/app/hooks/useMention.ts +25 -8
- package/app/lib/agent/log.ts +15 -18
- package/app/lib/agent/stream-consumer.ts +3 -0
- package/app/lib/agent/to-agent-messages.ts +6 -4
- package/app/lib/core/agent-audit-log.ts +280 -0
- package/app/lib/core/content-changes.ts +148 -8
- package/app/lib/core/index.ts +11 -0
- package/app/lib/fs.ts +16 -1
- package/app/lib/i18n-en.ts +317 -36
- package/app/lib/i18n-zh.ts +316 -35
- package/app/lib/mcp-agents.ts +273 -2
- package/app/lib/renderers/index.ts +1 -2
- package/app/lib/renderers/registry.ts +10 -0
- package/app/lib/types.ts +2 -0
- package/app/next-env.d.ts +1 -1
- package/bin/lib/mcp-agents.js +38 -13
- package/package.json +1 -1
- package/scripts/migrate-agent-audit-log.js +170 -0
- package/scripts/migrate-agent-diff.js +146 -0
- package/scripts/setup.js +12 -17
- package/skills/plugin-core-builtin-migration/SKILL.md +178 -0
- package/app/components/renderers/diff/DiffRenderer.tsx +0 -311
- package/app/components/renderers/diff/manifest.ts +0 -14
package/app/lib/mcp-agents.ts
CHANGED
|
@@ -33,6 +33,13 @@ export interface AgentDef {
|
|
|
33
33
|
presenceDirs?: string[];
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export type SkillInstallMode = 'universal' | 'additional' | 'unsupported';
|
|
37
|
+
export interface SkillAgentRegistration {
|
|
38
|
+
mode: SkillInstallMode;
|
|
39
|
+
/** npx skills `-a` value for additional agents. */
|
|
40
|
+
skillAgentName?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
export const MCP_AGENTS: Record<string, AgentDef> = {
|
|
37
44
|
'claude-code': {
|
|
38
45
|
name: 'Claude Code',
|
|
@@ -161,6 +168,15 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
|
|
|
161
168
|
presenceCli: 'qwen',
|
|
162
169
|
presenceDirs: ['~/.qwen/'],
|
|
163
170
|
},
|
|
171
|
+
'qoder': {
|
|
172
|
+
name: 'Qoder',
|
|
173
|
+
project: null,
|
|
174
|
+
global: '~/.qoder.json',
|
|
175
|
+
key: 'mcpServers',
|
|
176
|
+
preferredTransport: 'stdio',
|
|
177
|
+
presenceCli: 'qoder',
|
|
178
|
+
presenceDirs: ['~/.qoder/', '~/.qoder.json'],
|
|
179
|
+
},
|
|
164
180
|
'trae-cn': {
|
|
165
181
|
name: 'Trae CN',
|
|
166
182
|
project: '.trae/mcp.json',
|
|
@@ -215,6 +231,259 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
|
|
|
215
231
|
},
|
|
216
232
|
};
|
|
217
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Skill-install registry keyed by MCP agent key.
|
|
236
|
+
* Keep in sync with docs and bin/lib/mcp-agents.js.
|
|
237
|
+
*/
|
|
238
|
+
export const SKILL_AGENT_REGISTRY: Record<string, SkillAgentRegistration> = {
|
|
239
|
+
'claude-code': { mode: 'additional', skillAgentName: 'claude-code' },
|
|
240
|
+
'cursor': { mode: 'universal' },
|
|
241
|
+
'windsurf': { mode: 'additional', skillAgentName: 'windsurf' },
|
|
242
|
+
'cline': { mode: 'universal' },
|
|
243
|
+
'trae': { mode: 'additional', skillAgentName: 'trae' },
|
|
244
|
+
'gemini-cli': { mode: 'universal' },
|
|
245
|
+
'openclaw': { mode: 'additional', skillAgentName: 'openclaw' },
|
|
246
|
+
'codebuddy': { mode: 'additional', skillAgentName: 'codebuddy' },
|
|
247
|
+
'iflow-cli': { mode: 'additional', skillAgentName: 'iflow-cli' },
|
|
248
|
+
'kimi-cli': { mode: 'universal' },
|
|
249
|
+
'opencode': { mode: 'universal' },
|
|
250
|
+
'pi': { mode: 'additional', skillAgentName: 'pi' },
|
|
251
|
+
'augment': { mode: 'additional', skillAgentName: 'augment' },
|
|
252
|
+
'qwen-code': { mode: 'additional', skillAgentName: 'qwen-code' },
|
|
253
|
+
'qoder': { mode: 'additional', skillAgentName: 'qoder' },
|
|
254
|
+
'trae-cn': { mode: 'additional', skillAgentName: 'trae-cn' },
|
|
255
|
+
'roo': { mode: 'additional', skillAgentName: 'roo' },
|
|
256
|
+
'vscode': { mode: 'universal' },
|
|
257
|
+
'codex': { mode: 'universal' },
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export interface SkillWorkspaceProfile {
|
|
261
|
+
mode: SkillInstallMode;
|
|
262
|
+
skillAgentName?: string;
|
|
263
|
+
workspacePath: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export interface AgentRuntimeSignals {
|
|
267
|
+
hiddenRootPath: string;
|
|
268
|
+
hiddenRootPresent: boolean;
|
|
269
|
+
conversationSignal: boolean;
|
|
270
|
+
usageSignal: boolean;
|
|
271
|
+
lastActivityAt?: string;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export interface AgentConfiguredMcpServers {
|
|
275
|
+
servers: string[];
|
|
276
|
+
sources: string[];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export interface AgentInstalledSkills {
|
|
280
|
+
skills: string[];
|
|
281
|
+
sourcePath: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function resolveHiddenRootPath(agent: AgentDef): string {
|
|
285
|
+
const dirs = agent.presenceDirs ?? [];
|
|
286
|
+
for (const entry of dirs) {
|
|
287
|
+
const abs = expandHome(entry);
|
|
288
|
+
if (!fs.existsSync(abs)) continue;
|
|
289
|
+
try {
|
|
290
|
+
const stat = fs.statSync(abs);
|
|
291
|
+
if (stat.isDirectory()) return abs;
|
|
292
|
+
if (stat.isFile()) return path.dirname(abs);
|
|
293
|
+
} catch {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return path.dirname(expandHome(agent.global));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function readDirectoryEntries(dir: string): fs.Dirent[] {
|
|
301
|
+
try {
|
|
302
|
+
return fs.readdirSync(dir, { withFileTypes: true });
|
|
303
|
+
} catch {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function detectSignalsFromName(name: string): { conversation: boolean; usage: boolean } {
|
|
309
|
+
const lowered = name.toLowerCase();
|
|
310
|
+
return {
|
|
311
|
+
conversation: /(session|history|conversation|chat|transcript)/.test(lowered),
|
|
312
|
+
usage: /(usage|token|cost|billing|metric|analytics)/.test(lowered),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function readNestedRecord(obj: Record<string, unknown>, nestedPath: string): Record<string, unknown> | null {
|
|
317
|
+
const parts = nestedPath.split('.').filter(Boolean);
|
|
318
|
+
let current: unknown = obj;
|
|
319
|
+
for (const part of parts) {
|
|
320
|
+
if (!current || typeof current !== 'object') return null;
|
|
321
|
+
current = (current as Record<string, unknown>)[part];
|
|
322
|
+
}
|
|
323
|
+
if (!current || typeof current !== 'object') return null;
|
|
324
|
+
return current as Record<string, unknown>;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function parseJsonServerNames(content: string, configKey: string, globalNestedKey?: string): string[] {
|
|
328
|
+
try {
|
|
329
|
+
const config = parseJsonc(content) as Record<string, unknown>;
|
|
330
|
+
const section = globalNestedKey
|
|
331
|
+
? readNestedRecord(config, globalNestedKey)
|
|
332
|
+
: (config[configKey] as unknown);
|
|
333
|
+
if (!section || typeof section !== 'object') return [];
|
|
334
|
+
return Object.keys(section as Record<string, unknown>);
|
|
335
|
+
} catch {
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function parseTomlServerNames(content: string, sectionKey: string): string[] {
|
|
341
|
+
const names = new Set<string>();
|
|
342
|
+
const lines = content.split('\n');
|
|
343
|
+
let inRootSection = false;
|
|
344
|
+
const sectionPrefix = `${sectionKey}.`;
|
|
345
|
+
for (const line of lines) {
|
|
346
|
+
const trimmed = line.trim();
|
|
347
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
348
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
349
|
+
const section = trimmed.slice(1, -1).trim();
|
|
350
|
+
inRootSection = section === sectionKey;
|
|
351
|
+
if (section.startsWith(sectionPrefix)) {
|
|
352
|
+
const name = section.slice(sectionPrefix.length).split('.')[0]?.trim();
|
|
353
|
+
if (name) names.add(name);
|
|
354
|
+
}
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (!inRootSection) continue;
|
|
358
|
+
const kv = trimmed.match(/^([a-zA-Z0-9_-]+)\s*=\s*/);
|
|
359
|
+
if (!kv) continue;
|
|
360
|
+
const name = kv[1]?.trim();
|
|
361
|
+
if (name) names.add(name);
|
|
362
|
+
}
|
|
363
|
+
return [...names];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function resolveSkillWorkspaceProfile(agentKey: string): SkillWorkspaceProfile {
|
|
367
|
+
const registration = SKILL_AGENT_REGISTRY[agentKey] ?? { mode: 'unsupported' as const };
|
|
368
|
+
if (registration.mode === 'universal') {
|
|
369
|
+
return { mode: registration.mode, workspacePath: expandHome('~/.agents/skills') };
|
|
370
|
+
}
|
|
371
|
+
const agent = MCP_AGENTS[agentKey];
|
|
372
|
+
const root = agent ? resolveHiddenRootPath(agent) : expandHome('~/.agents');
|
|
373
|
+
const workspacePath = path.join(root, 'skills');
|
|
374
|
+
return {
|
|
375
|
+
mode: registration.mode,
|
|
376
|
+
skillAgentName: registration.skillAgentName,
|
|
377
|
+
workspacePath,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function detectAgentConfiguredMcpServers(agentKey: string): AgentConfiguredMcpServers {
|
|
382
|
+
const agent = MCP_AGENTS[agentKey];
|
|
383
|
+
if (!agent) return { servers: [], sources: [] };
|
|
384
|
+
const serverSet = new Set<string>();
|
|
385
|
+
const sources: string[] = [];
|
|
386
|
+
for (const [scopeType, cfgPath] of [['global', agent.global], ['project', agent.project]] as Array<[string, string | null]>) {
|
|
387
|
+
if (!cfgPath) continue;
|
|
388
|
+
const absPath = expandHome(cfgPath);
|
|
389
|
+
if (!fs.existsSync(absPath)) continue;
|
|
390
|
+
try {
|
|
391
|
+
const content = fs.readFileSync(absPath, 'utf-8');
|
|
392
|
+
const nestedPath = scopeType === 'global' ? agent.globalNestedKey : undefined;
|
|
393
|
+
const names =
|
|
394
|
+
agent.format === 'toml'
|
|
395
|
+
? parseTomlServerNames(content, agent.key)
|
|
396
|
+
: parseJsonServerNames(content, agent.key, nestedPath);
|
|
397
|
+
for (const name of names) serverSet.add(name);
|
|
398
|
+
sources.push(`${scopeType}:${cfgPath}`);
|
|
399
|
+
} catch {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
servers: [...serverSet].sort((a, b) => a.localeCompare(b)),
|
|
405
|
+
sources,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function detectAgentInstalledSkills(agentKey: string): AgentInstalledSkills {
|
|
410
|
+
const profile = resolveSkillWorkspaceProfile(agentKey);
|
|
411
|
+
const sourcePath = profile.workspacePath;
|
|
412
|
+
if (!fs.existsSync(sourcePath)) return { skills: [], sourcePath };
|
|
413
|
+
let entries: fs.Dirent[] = [];
|
|
414
|
+
try {
|
|
415
|
+
entries = fs.readdirSync(sourcePath, { withFileTypes: true });
|
|
416
|
+
} catch {
|
|
417
|
+
return { skills: [], sourcePath };
|
|
418
|
+
}
|
|
419
|
+
const skills = entries
|
|
420
|
+
.filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && !entry.name.startsWith('.'))
|
|
421
|
+
.map((entry) => entry.name)
|
|
422
|
+
.sort((a, b) => a.localeCompare(b));
|
|
423
|
+
return { skills, sourcePath };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function detectAgentRuntimeSignals(agentKey: string): AgentRuntimeSignals {
|
|
427
|
+
const agent = MCP_AGENTS[agentKey];
|
|
428
|
+
if (!agent) {
|
|
429
|
+
return {
|
|
430
|
+
hiddenRootPath: '',
|
|
431
|
+
hiddenRootPresent: false,
|
|
432
|
+
conversationSignal: false,
|
|
433
|
+
usageSignal: false,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const hiddenRootPath = resolveHiddenRootPath(agent);
|
|
437
|
+
if (!fs.existsSync(hiddenRootPath)) {
|
|
438
|
+
return {
|
|
439
|
+
hiddenRootPath,
|
|
440
|
+
hiddenRootPresent: false,
|
|
441
|
+
conversationSignal: false,
|
|
442
|
+
usageSignal: false,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const maxDepth = 3;
|
|
447
|
+
const maxEntries = 300;
|
|
448
|
+
let scanned = 0;
|
|
449
|
+
let conversationSignal = false;
|
|
450
|
+
let usageSignal = false;
|
|
451
|
+
let latestMtime = 0;
|
|
452
|
+
const queue: Array<{ dir: string; depth: number }> = [{ dir: hiddenRootPath, depth: 0 }];
|
|
453
|
+
|
|
454
|
+
while (queue.length > 0 && scanned < maxEntries) {
|
|
455
|
+
const current = queue.shift();
|
|
456
|
+
if (!current) break;
|
|
457
|
+
const entries = readDirectoryEntries(current.dir);
|
|
458
|
+
for (const entry of entries) {
|
|
459
|
+
if (scanned >= maxEntries) break;
|
|
460
|
+
scanned += 1;
|
|
461
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
462
|
+
const fullPath = path.join(current.dir, entry.name);
|
|
463
|
+
try {
|
|
464
|
+
const stat = fs.statSync(fullPath);
|
|
465
|
+
if (stat.mtimeMs > latestMtime) latestMtime = stat.mtimeMs;
|
|
466
|
+
const signals = detectSignalsFromName(entry.name);
|
|
467
|
+
if (signals.conversation) conversationSignal = true;
|
|
468
|
+
if (signals.usage) usageSignal = true;
|
|
469
|
+
if (entry.isDirectory() && current.depth < maxDepth) {
|
|
470
|
+
queue.push({ dir: fullPath, depth: current.depth + 1 });
|
|
471
|
+
}
|
|
472
|
+
} catch {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
hiddenRootPath,
|
|
480
|
+
hiddenRootPresent: true,
|
|
481
|
+
conversationSignal,
|
|
482
|
+
usageSignal,
|
|
483
|
+
lastActivityAt: latestMtime > 0 ? new Date(latestMtime).toISOString() : undefined,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
218
487
|
/* ── MindOS MCP Install Detection ──────────────────────────────────────── */
|
|
219
488
|
|
|
220
489
|
export function detectInstalled(agentKey: string): { installed: boolean; scope?: string; transport?: string; configPath?: string } {
|
|
@@ -238,9 +507,11 @@ export function detectInstalled(agentKey: string): { installed: boolean; scope?:
|
|
|
238
507
|
} else {
|
|
239
508
|
// JSON format (default)
|
|
240
509
|
const config = parseJsonc(content);
|
|
241
|
-
const servers =
|
|
510
|
+
const servers = scopeType === 'global' && agent.globalNestedKey
|
|
511
|
+
? readNestedRecord(config as Record<string, unknown>, agent.globalNestedKey)
|
|
512
|
+
: (config[agent.key] as Record<string, unknown> | undefined);
|
|
242
513
|
if (servers?.mindos) {
|
|
243
|
-
const entry = servers.mindos
|
|
514
|
+
const entry = servers.mindos as Record<string, unknown>;
|
|
244
515
|
const transport = entry.type === 'stdio' ? 'stdio' : entry.url ? 'http' : 'unknown';
|
|
245
516
|
return { installed: true, scope: scopeType, transport, configPath: cfgPath };
|
|
246
517
|
}
|
|
@@ -7,7 +7,6 @@ import { manifest as agentInspector } from '@/components/renderers/agent-inspect
|
|
|
7
7
|
import { manifest as backlinks } from '@/components/renderers/backlinks/manifest';
|
|
8
8
|
import { manifest as config } from '@/components/renderers/config/manifest';
|
|
9
9
|
import { manifest as csv } from '@/components/renderers/csv/manifest';
|
|
10
|
-
import { manifest as diff } from '@/components/renderers/diff/manifest';
|
|
11
10
|
import { manifest as summary } from '@/components/renderers/summary/manifest';
|
|
12
11
|
import { manifest as timeline } from '@/components/renderers/timeline/manifest';
|
|
13
12
|
import { manifest as todo } from '@/components/renderers/todo/manifest';
|
|
@@ -15,7 +14,7 @@ import { manifest as workflow } from '@/components/renderers/workflow/manifest';
|
|
|
15
14
|
import { manifest as graph } from '@/components/renderers/graph/manifest';
|
|
16
15
|
|
|
17
16
|
const manifests = [
|
|
18
|
-
agentInspector, backlinks, config, csv,
|
|
17
|
+
agentInspector, backlinks, config, csv, summary, timeline, todo, workflow, graph,
|
|
19
18
|
];
|
|
20
19
|
|
|
21
20
|
for (const m of manifests) {
|
|
@@ -16,6 +16,11 @@ export interface RendererDefinition {
|
|
|
16
16
|
tags: string[];
|
|
17
17
|
builtin: boolean; // true = ships with MindOS; false = user-installed (future)
|
|
18
18
|
core?: boolean; // true = default renderer for a file type, cannot be disabled by user
|
|
19
|
+
/**
|
|
20
|
+
* App-builtin feature (not a user-facing plugin).
|
|
21
|
+
* When true, keep renderer functional but hide from Plugins surfaces.
|
|
22
|
+
*/
|
|
23
|
+
appBuiltinFeature?: boolean;
|
|
19
24
|
entryPath?: string; // canonical entry file shown on home page (e.g. 'TODO.md')
|
|
20
25
|
match: (ctx: Pick<RendererContext, 'filePath' | 'extension'>) => boolean;
|
|
21
26
|
// Provide either `component` (eager) or `load` (lazy). Prefer `load` for code-splitting.
|
|
@@ -78,3 +83,8 @@ export function resolveRenderer(
|
|
|
78
83
|
export function getAllRenderers(): RendererDefinition[] {
|
|
79
84
|
return registry;
|
|
80
85
|
}
|
|
86
|
+
|
|
87
|
+
/** User-facing plugins only (exclude app-builtin features like CSV). */
|
|
88
|
+
export function getPluginRenderers(): RendererDefinition[] {
|
|
89
|
+
return registry.filter((r) => !r.appBuiltinFeature);
|
|
90
|
+
}
|
package/app/lib/types.ts
CHANGED
|
@@ -37,6 +37,8 @@ export type MessagePart = TextPart | ToolCallPart | ReasoningPart;
|
|
|
37
37
|
export interface Message {
|
|
38
38
|
role: 'user' | 'assistant';
|
|
39
39
|
content: string;
|
|
40
|
+
/** Unix timestamp in milliseconds when this message was created */
|
|
41
|
+
timestamp?: number;
|
|
40
42
|
/** Structured parts for assistant messages (tool calls + text segments) */
|
|
41
43
|
parts?: MessagePart[];
|
|
42
44
|
}
|
package/app/next-env.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/
|
|
3
|
+
import "./.next/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
package/bin/lib/mcp-agents.js
CHANGED
|
@@ -26,16 +26,6 @@ export const MCP_AGENTS = {
|
|
|
26
26
|
presenceCli: 'claude',
|
|
27
27
|
presenceDirs: ['~/.claude/'],
|
|
28
28
|
},
|
|
29
|
-
'claude-desktop': {
|
|
30
|
-
name: 'Claude Desktop',
|
|
31
|
-
project: null,
|
|
32
|
-
global: process.platform === 'darwin'
|
|
33
|
-
? '~/Library/Application Support/Claude/claude_desktop_config.json'
|
|
34
|
-
: '~/.config/Claude/claude_desktop_config.json',
|
|
35
|
-
key: 'mcpServers',
|
|
36
|
-
preferredTransport: 'http',
|
|
37
|
-
presenceDirs: ['~/Library/Application Support/Claude/', '~/.config/Claude/'],
|
|
38
|
-
},
|
|
39
29
|
'cursor': {
|
|
40
30
|
name: 'Cursor',
|
|
41
31
|
project: '.cursor/mcp.json',
|
|
@@ -94,11 +84,11 @@ export const MCP_AGENTS = {
|
|
|
94
84
|
'codebuddy': {
|
|
95
85
|
name: 'CodeBuddy',
|
|
96
86
|
project: null,
|
|
97
|
-
global: '~/.
|
|
87
|
+
global: '~/.codebuddy/mcp.json',
|
|
98
88
|
key: 'mcpServers',
|
|
99
89
|
preferredTransport: 'stdio',
|
|
100
|
-
presenceCli: '
|
|
101
|
-
presenceDirs: ['~/.
|
|
90
|
+
presenceCli: 'codebuddy',
|
|
91
|
+
presenceDirs: ['~/.codebuddy/'],
|
|
102
92
|
},
|
|
103
93
|
'iflow-cli': {
|
|
104
94
|
name: 'iFlow CLI',
|
|
@@ -154,6 +144,15 @@ export const MCP_AGENTS = {
|
|
|
154
144
|
presenceCli: 'qwen',
|
|
155
145
|
presenceDirs: ['~/.qwen/'],
|
|
156
146
|
},
|
|
147
|
+
'qoder': {
|
|
148
|
+
name: 'Qoder',
|
|
149
|
+
project: null,
|
|
150
|
+
global: '~/.qoder.json',
|
|
151
|
+
key: 'mcpServers',
|
|
152
|
+
preferredTransport: 'stdio',
|
|
153
|
+
presenceCli: 'qoder',
|
|
154
|
+
presenceDirs: ['~/.qoder/', '~/.qoder.json'],
|
|
155
|
+
},
|
|
157
156
|
'trae-cn': {
|
|
158
157
|
name: 'Trae CN',
|
|
159
158
|
project: '.trae/mcp.json',
|
|
@@ -183,6 +182,32 @@ export const MCP_AGENTS = {
|
|
|
183
182
|
},
|
|
184
183
|
};
|
|
185
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Skill-install registry keyed by MCP agent key.
|
|
187
|
+
* Keep in sync with app/lib/mcp-agents.ts.
|
|
188
|
+
*/
|
|
189
|
+
export const SKILL_AGENT_REGISTRY = {
|
|
190
|
+
'claude-code': { mode: 'additional', skillAgentName: 'claude-code' },
|
|
191
|
+
'cursor': { mode: 'universal' },
|
|
192
|
+
'windsurf': { mode: 'additional', skillAgentName: 'windsurf' },
|
|
193
|
+
'cline': { mode: 'universal' },
|
|
194
|
+
'trae': { mode: 'additional', skillAgentName: 'trae' },
|
|
195
|
+
'gemini-cli': { mode: 'universal' },
|
|
196
|
+
'openclaw': { mode: 'additional', skillAgentName: 'openclaw' },
|
|
197
|
+
'codebuddy': { mode: 'additional', skillAgentName: 'codebuddy' },
|
|
198
|
+
'iflow-cli': { mode: 'additional', skillAgentName: 'iflow-cli' },
|
|
199
|
+
'kimi-cli': { mode: 'universal' },
|
|
200
|
+
'opencode': { mode: 'universal' },
|
|
201
|
+
'pi': { mode: 'additional', skillAgentName: 'pi' },
|
|
202
|
+
'augment': { mode: 'additional', skillAgentName: 'augment' },
|
|
203
|
+
'qwen-code': { mode: 'additional', skillAgentName: 'qwen-code' },
|
|
204
|
+
'qoder': { mode: 'additional', skillAgentName: 'qoder' },
|
|
205
|
+
'trae-cn': { mode: 'additional', skillAgentName: 'trae-cn' },
|
|
206
|
+
'roo': { mode: 'additional', skillAgentName: 'roo' },
|
|
207
|
+
'vscode': { mode: 'universal' },
|
|
208
|
+
'codex': { mode: 'universal' },
|
|
209
|
+
};
|
|
210
|
+
|
|
186
211
|
export function detectAgentPresence(agentKey) {
|
|
187
212
|
const agent = MCP_AGENTS[agentKey];
|
|
188
213
|
if (!agent) return false;
|
package/package.json
CHANGED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-shot migration helper:
|
|
4
|
+
* Import legacy Agent-Audit.md and .agent-log.json into .mindos/agent-audit-log.json.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/migrate-agent-audit-log.js --mind-root /abs/path/to/mindRoot
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
|
|
13
|
+
const MAX_EVENTS = 1000;
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
let mindRoot = '';
|
|
17
|
+
for (let i = 0; i < argv.length; i++) {
|
|
18
|
+
if (argv[i] === '--mind-root') {
|
|
19
|
+
mindRoot = argv[i + 1] || '';
|
|
20
|
+
i += 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { mindRoot };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function nowIso() {
|
|
27
|
+
return new Date().toISOString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function validIso(ts) {
|
|
31
|
+
if (typeof ts !== 'string') return nowIso();
|
|
32
|
+
const ms = new Date(ts).getTime();
|
|
33
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : nowIso();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeMessage(message) {
|
|
37
|
+
if (typeof message !== 'string') return undefined;
|
|
38
|
+
return message.slice(0, 2000);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readJson(file, fallback) {
|
|
42
|
+
try {
|
|
43
|
+
if (!fs.existsSync(file)) return fallback;
|
|
44
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
45
|
+
} catch {
|
|
46
|
+
return fallback;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseMdBlocks(raw) {
|
|
51
|
+
const blocks = [];
|
|
52
|
+
const re = /```agent-op\s*\n([\s\S]*?)```/g;
|
|
53
|
+
let match;
|
|
54
|
+
while ((match = re.exec(raw)) !== null) {
|
|
55
|
+
try {
|
|
56
|
+
blocks.push(JSON.parse(match[1].trim()));
|
|
57
|
+
} catch {
|
|
58
|
+
// Keep migration best-effort.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return blocks;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseJsonLines(raw) {
|
|
65
|
+
const lines = [];
|
|
66
|
+
for (const line of raw.split('\n')) {
|
|
67
|
+
const trimmed = line.trim();
|
|
68
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//')) continue;
|
|
69
|
+
try {
|
|
70
|
+
lines.push(JSON.parse(trimmed));
|
|
71
|
+
} catch {
|
|
72
|
+
// Ignore malformed line.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return lines;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function toEvent(entry, op, idx) {
|
|
79
|
+
const tool = typeof entry?.tool === 'string' && entry.tool.trim() ? entry.tool.trim() : 'unknown-tool';
|
|
80
|
+
return {
|
|
81
|
+
id: `legacy-script-${Date.now().toString(36)}-${idx.toString(36)}`,
|
|
82
|
+
ts: validIso(entry?.ts),
|
|
83
|
+
tool,
|
|
84
|
+
params: entry?.params && typeof entry.params === 'object' ? entry.params : {},
|
|
85
|
+
result: entry?.result === 'error' ? 'error' : 'ok',
|
|
86
|
+
message: normalizeMessage(entry?.message),
|
|
87
|
+
durationMs: typeof entry?.durationMs === 'number' ? entry.durationMs : undefined,
|
|
88
|
+
op,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function main() {
|
|
93
|
+
const { mindRoot } = parseArgs(process.argv.slice(2));
|
|
94
|
+
if (!mindRoot) {
|
|
95
|
+
console.error('Missing --mind-root');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
const root = path.resolve(mindRoot);
|
|
99
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
100
|
+
console.error('Invalid mind root:', root);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const legacyMd = path.join(root, 'Agent-Audit.md');
|
|
105
|
+
const legacyJsonl = path.join(root, '.agent-log.json');
|
|
106
|
+
if (!fs.existsSync(legacyMd) && !fs.existsSync(legacyJsonl)) {
|
|
107
|
+
console.log('No legacy Agent-Audit.md or .agent-log.json found. Nothing to migrate.');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const logDir = path.join(root, '.mindos');
|
|
112
|
+
const logFile = path.join(logDir, 'agent-audit-log.json');
|
|
113
|
+
const state = readJson(logFile, {
|
|
114
|
+
version: 1,
|
|
115
|
+
events: [],
|
|
116
|
+
legacy: { mdImportedCount: 0, jsonlImportedCount: 0, lastImportedAt: null },
|
|
117
|
+
});
|
|
118
|
+
const baseEvents = Array.isArray(state.events) ? state.events : [];
|
|
119
|
+
const legacy = {
|
|
120
|
+
mdImportedCount: Number(state?.legacy?.mdImportedCount || 0),
|
|
121
|
+
jsonlImportedCount: Number(state?.legacy?.jsonlImportedCount || 0),
|
|
122
|
+
lastImportedAt: typeof state?.legacy?.lastImportedAt === 'string' ? state.legacy.lastImportedAt : null,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const imported = [];
|
|
126
|
+
|
|
127
|
+
if (fs.existsSync(legacyMd)) {
|
|
128
|
+
const blocks = parseMdBlocks(fs.readFileSync(legacyMd, 'utf-8'));
|
|
129
|
+
if (blocks.length > legacy.mdImportedCount) {
|
|
130
|
+
const incoming = blocks.slice(legacy.mdImportedCount);
|
|
131
|
+
imported.push(...incoming.map((entry, idx) => toEvent(entry, 'legacy_agent_audit_md_import', idx)));
|
|
132
|
+
legacy.mdImportedCount = blocks.length;
|
|
133
|
+
legacy.lastImportedAt = nowIso();
|
|
134
|
+
}
|
|
135
|
+
if (blocks.length > 0) fs.rmSync(legacyMd, { force: true });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (fs.existsSync(legacyJsonl)) {
|
|
139
|
+
const lines = parseJsonLines(fs.readFileSync(legacyJsonl, 'utf-8'));
|
|
140
|
+
if (lines.length > legacy.jsonlImportedCount) {
|
|
141
|
+
const incoming = lines.slice(legacy.jsonlImportedCount);
|
|
142
|
+
imported.push(...incoming.map((entry, idx) => toEvent(entry, 'legacy_agent_log_jsonl_import', idx)));
|
|
143
|
+
legacy.jsonlImportedCount = lines.length;
|
|
144
|
+
legacy.lastImportedAt = nowIso();
|
|
145
|
+
}
|
|
146
|
+
if (lines.length > 0) fs.rmSync(legacyJsonl, { force: true });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (imported.length === 0) {
|
|
150
|
+
console.log('No new legacy entries to import.');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const merged = [...baseEvents, ...imported]
|
|
155
|
+
.sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime())
|
|
156
|
+
.slice(0, MAX_EVENTS);
|
|
157
|
+
|
|
158
|
+
const next = {
|
|
159
|
+
version: 1,
|
|
160
|
+
events: merged,
|
|
161
|
+
legacy,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
165
|
+
fs.writeFileSync(logFile, JSON.stringify(next, null, 2), 'utf-8');
|
|
166
|
+
console.log(`Imported ${imported.length} legacy entry(s) into ${logFile}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
main();
|
|
170
|
+
|