@geminilight/mindos 0.5.64 → 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.
Files changed (85) hide show
  1. package/README.md +4 -0
  2. package/README_zh.md +4 -0
  3. package/app/app/api/ask/route.ts +12 -0
  4. package/app/app/api/file/route.ts +9 -0
  5. package/app/app/api/mcp/agents/route.ts +27 -1
  6. package/app/app/api/skills/route.ts +18 -2
  7. package/app/app/api/tree-version/route.ts +8 -0
  8. package/app/components/ActivityBar.tsx +2 -2
  9. package/app/components/Backlinks.tsx +5 -5
  10. package/app/components/CreateSpaceModal.tsx +3 -2
  11. package/app/components/DirPicker.tsx +1 -1
  12. package/app/components/DirView.tsx +2 -3
  13. package/app/components/EditorWrapper.tsx +3 -3
  14. package/app/components/FileTree.tsx +25 -10
  15. package/app/components/GuideCard.tsx +4 -4
  16. package/app/components/HomeContent.tsx +6 -11
  17. package/app/components/MarkdownView.tsx +2 -2
  18. package/app/components/OnboardingView.tsx +1 -1
  19. package/app/components/Panel.tsx +1 -1
  20. package/app/components/RightAgentDetailPanel.tsx +1 -1
  21. package/app/components/RightAskPanel.tsx +1 -1
  22. package/app/components/SearchModal.tsx +10 -2
  23. package/app/components/SidebarLayout.tsx +35 -10
  24. package/app/components/ThemeToggle.tsx +1 -1
  25. package/app/components/agents/AgentDetailContent.tsx +454 -59
  26. package/app/components/agents/AgentsContentPage.tsx +70 -5
  27. package/app/components/agents/AgentsMcpSection.tsx +474 -159
  28. package/app/components/agents/AgentsOverviewSection.tsx +418 -59
  29. package/app/components/agents/AgentsPrimitives.tsx +335 -0
  30. package/app/components/agents/AgentsSkillsSection.tsx +739 -121
  31. package/app/components/agents/SkillDetailPopover.tsx +416 -0
  32. package/app/components/agents/agents-content-model.ts +292 -10
  33. package/app/components/ask/AskContent.tsx +34 -5
  34. package/app/components/ask/FileChip.tsx +1 -0
  35. package/app/components/ask/MentionPopover.tsx +13 -1
  36. package/app/components/ask/MessageList.tsx +5 -7
  37. package/app/components/ask/ToolCallBlock.tsx +4 -4
  38. package/app/components/changes/ChangesBanner.tsx +1 -2
  39. package/app/components/echo/EchoHero.tsx +10 -24
  40. package/app/components/echo/EchoInsightCollapsible.tsx +52 -43
  41. package/app/components/echo/EchoPageSections.tsx +13 -9
  42. package/app/components/echo/EchoSegmentNav.tsx +14 -11
  43. package/app/components/echo/EchoSegmentPageClient.tsx +64 -43
  44. package/app/components/explore/ExploreContent.tsx +3 -7
  45. package/app/components/explore/UseCaseCard.tsx +4 -15
  46. package/app/components/panels/AgentsPanel.tsx +12 -104
  47. package/app/components/panels/AgentsPanelAgentDetail.tsx +2 -2
  48. package/app/components/panels/AgentsPanelAgentGroups.tsx +3 -7
  49. package/app/components/panels/AgentsPanelAgentListRow.tsx +9 -11
  50. package/app/components/panels/EchoPanel.tsx +8 -10
  51. package/app/components/panels/PanelNavRow.tsx +9 -2
  52. package/app/components/panels/PluginsPanel.tsx +2 -2
  53. package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +30 -8
  54. package/app/components/renderers/agent-inspector/manifest.ts +3 -3
  55. package/app/components/renderers/todo/manifest.ts +1 -0
  56. package/app/components/settings/AiTab.tsx +3 -3
  57. package/app/components/settings/AppearanceTab.tsx +2 -2
  58. package/app/components/settings/KnowledgeTab.tsx +3 -3
  59. package/app/components/settings/McpAgentInstall.tsx +3 -6
  60. package/app/components/settings/McpSkillCreateForm.tsx +2 -3
  61. package/app/components/settings/McpSkillRow.tsx +2 -3
  62. package/app/components/settings/McpSkillsSection.tsx +2 -2
  63. package/app/components/settings/McpTab.tsx +12 -13
  64. package/app/components/settings/MonitoringTab.tsx +13 -13
  65. package/app/components/settings/PluginsTab.tsx +2 -2
  66. package/app/components/settings/Primitives.tsx +3 -4
  67. package/app/components/settings/SettingsContent.tsx +3 -3
  68. package/app/components/settings/SyncTab.tsx +11 -17
  69. package/app/components/settings/UpdateTab.tsx +18 -21
  70. package/app/components/settings/types.ts +14 -0
  71. package/app/components/setup/StepKB.tsx +1 -1
  72. package/app/hooks/useMcpData.tsx +4 -2
  73. package/app/hooks/useMention.ts +25 -8
  74. package/app/lib/agent/log.ts +15 -18
  75. package/app/lib/agent/stream-consumer.ts +3 -0
  76. package/app/lib/agent/to-agent-messages.ts +6 -4
  77. package/app/lib/core/agent-audit-log.ts +280 -0
  78. package/app/lib/core/index.ts +11 -0
  79. package/app/lib/fs.ts +9 -0
  80. package/app/lib/i18n-en.ts +259 -33
  81. package/app/lib/i18n-zh.ts +258 -32
  82. package/app/lib/mcp-agents.ts +231 -2
  83. package/app/lib/types.ts +2 -0
  84. package/package.json +1 -1
  85. package/scripts/migrate-agent-audit-log.js +170 -0
@@ -1,9 +1,9 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getMindRoot } from '@/lib/fs';
4
+ import { appendAgentAuditEvent } from '@/lib/core/agent-audit-log';
4
5
 
5
- const LOG_FILE = '.agent-log.json';
6
- const MAX_SIZE = 500 * 1024; // 500KB
6
+ const LEGACY_LOG_FILE = '.agent-log.json';
7
7
 
8
8
  interface AgentOpEntry {
9
9
  ts: string;
@@ -22,23 +22,20 @@ interface AgentOpEntry {
22
22
  export function logAgentOp(entry: AgentOpEntry): void {
23
23
  try {
24
24
  const root = getMindRoot();
25
- const logPath = path.join(root, LOG_FILE);
26
-
27
- const line = JSON.stringify(entry) + '\n';
28
-
29
- // Check size and truncate if needed
30
- if (fs.existsSync(logPath)) {
31
- const stat = fs.statSync(logPath);
32
- if (stat.size > MAX_SIZE) {
33
- const content = fs.readFileSync(logPath, 'utf-8');
34
- const lines = content.trimEnd().split('\n');
35
- // Keep the newer half
36
- const kept = lines.slice(Math.floor(lines.length / 2));
37
- fs.writeFileSync(logPath, kept.join('\n') + '\n');
38
- }
25
+ appendAgentAuditEvent(root, {
26
+ ts: entry.ts,
27
+ tool: entry.tool,
28
+ params: entry.params,
29
+ result: entry.result,
30
+ message: entry.message,
31
+ durationMs: entry.durationMs,
32
+ });
33
+ // Best-effort cleanup of legacy JSONL path.
34
+ try {
35
+ fs.rmSync(path.join(root, LEGACY_LOG_FILE), { force: true });
36
+ } catch {
37
+ // ignore
39
38
  }
40
-
41
- fs.appendFileSync(logPath, line);
42
39
  } catch {
43
40
  // Logging should never break tool execution
44
41
  }
@@ -31,6 +31,8 @@ export async function consumeUIMessageStream(
31
31
  let currentTextId: string | null = null;
32
32
  let currentReasoningPart: ReasoningPart | null = null;
33
33
 
34
+ const startedAt = Date.now();
35
+
34
36
  /** Build an immutable Message snapshot from current parts */
35
37
  function buildMessage(): Message {
36
38
  const clonedParts: MessagePart[] = parts.map(p => {
@@ -47,6 +49,7 @@ export async function consumeUIMessageStream(
47
49
  role: 'assistant',
48
50
  content: textContent,
49
51
  parts: clonedParts,
52
+ timestamp: startedAt,
50
53
  };
51
54
  }
52
55
 
@@ -24,11 +24,13 @@ export function toAgentMessages(messages: FrontendMessage[]): AgentMessage[] {
24
24
  const result: AgentMessage[] = [];
25
25
 
26
26
  for (const msg of messages) {
27
+ const timestamp = msg.timestamp ?? Date.now();
28
+
27
29
  if (msg.role === 'user') {
28
30
  result.push({
29
31
  role: 'user',
30
32
  content: msg.content,
31
- timestamp: Date.now(),
33
+ timestamp,
32
34
  } satisfies UserMessage as AgentMessage);
33
35
  continue;
34
36
  }
@@ -49,7 +51,7 @@ export function toAgentMessages(messages: FrontendMessage[]): AgentMessage[] {
49
51
  model: '',
50
52
  usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
51
53
  stopReason: 'stop',
52
- timestamp: Date.now(),
54
+ timestamp,
53
55
  } satisfies AssistantMessage as AgentMessage);
54
56
  }
55
57
  continue;
@@ -85,7 +87,7 @@ export function toAgentMessages(messages: FrontendMessage[]): AgentMessage[] {
85
87
  model: '',
86
88
  usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
87
89
  stopReason: toolCalls.length > 0 ? 'toolUse' : 'stop',
88
- timestamp: Date.now(),
90
+ timestamp,
89
91
  } satisfies AssistantMessage as AgentMessage);
90
92
  }
91
93
 
@@ -97,7 +99,7 @@ export function toAgentMessages(messages: FrontendMessage[]): AgentMessage[] {
97
99
  toolName: tc.toolName,
98
100
  content: [{ type: 'text', text: tc.output ?? '' }],
99
101
  isError: tc.state === 'error',
100
- timestamp: Date.now(),
102
+ timestamp,
101
103
  } satisfies ToolResultMessage as AgentMessage);
102
104
  }
103
105
  }
@@ -0,0 +1,280 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export interface AgentAuditEvent {
5
+ id: string;
6
+ ts: string;
7
+ tool: string;
8
+ params: Record<string, unknown>;
9
+ result: 'ok' | 'error';
10
+ message?: string;
11
+ durationMs?: number;
12
+ op?: 'append' | 'legacy_agent_audit_md_import' | 'legacy_agent_log_jsonl_import';
13
+ }
14
+
15
+ export interface AgentAuditInput {
16
+ ts: string;
17
+ tool: string;
18
+ params: Record<string, unknown>;
19
+ result: 'ok' | 'error';
20
+ message?: string;
21
+ durationMs?: number;
22
+ }
23
+
24
+ interface AgentAuditState {
25
+ version: 1;
26
+ events: AgentAuditEvent[];
27
+ legacy?: {
28
+ mdImportedCount?: number;
29
+ jsonlImportedCount?: number;
30
+ lastImportedAt?: string | null;
31
+ };
32
+ }
33
+
34
+ const LOG_DIR_NAME = '.mindos';
35
+ const LOG_FILE_NAME = 'agent-audit-log.json';
36
+ const LEGACY_MD_FILE = 'Agent-Audit.md';
37
+ const LEGACY_JSONL_FILE = '.agent-log.json';
38
+ const MAX_EVENTS = 1000;
39
+ const MAX_MESSAGE_CHARS = 2000;
40
+
41
+ function nowIso() {
42
+ return new Date().toISOString();
43
+ }
44
+
45
+ function validIso(ts: string | undefined): string {
46
+ if (!ts) return nowIso();
47
+ const ms = new Date(ts).getTime();
48
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : nowIso();
49
+ }
50
+
51
+ function normalizeMessage(message: string | undefined): string | undefined {
52
+ if (typeof message !== 'string') return undefined;
53
+ if (message.length <= MAX_MESSAGE_CHARS) return message;
54
+ return message.slice(0, MAX_MESSAGE_CHARS);
55
+ }
56
+
57
+ function defaultState(): AgentAuditState {
58
+ return {
59
+ version: 1,
60
+ events: [],
61
+ legacy: {
62
+ mdImportedCount: 0,
63
+ jsonlImportedCount: 0,
64
+ lastImportedAt: null,
65
+ },
66
+ };
67
+ }
68
+
69
+ function logPath(mindRoot: string) {
70
+ return path.join(mindRoot, LOG_DIR_NAME, LOG_FILE_NAME);
71
+ }
72
+
73
+ function readState(mindRoot: string): AgentAuditState {
74
+ const file = logPath(mindRoot);
75
+ try {
76
+ if (!fs.existsSync(file)) return defaultState();
77
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf-8')) as Partial<AgentAuditState>;
78
+ if (!Array.isArray(parsed.events)) return defaultState();
79
+ return {
80
+ version: 1,
81
+ events: parsed.events,
82
+ legacy: {
83
+ mdImportedCount: typeof parsed.legacy?.mdImportedCount === 'number' ? parsed.legacy.mdImportedCount : 0,
84
+ jsonlImportedCount: typeof parsed.legacy?.jsonlImportedCount === 'number' ? parsed.legacy.jsonlImportedCount : 0,
85
+ lastImportedAt: typeof parsed.legacy?.lastImportedAt === 'string' ? parsed.legacy.lastImportedAt : null,
86
+ },
87
+ };
88
+ } catch {
89
+ return defaultState();
90
+ }
91
+ }
92
+
93
+ function writeState(mindRoot: string, state: AgentAuditState): void {
94
+ const file = logPath(mindRoot);
95
+ fs.mkdirSync(path.dirname(file), { recursive: true });
96
+ fs.writeFileSync(file, JSON.stringify(state, null, 2), 'utf-8');
97
+ }
98
+
99
+ function removeLegacyFile(filePath: string): void {
100
+ try {
101
+ fs.rmSync(filePath, { force: true });
102
+ } catch {
103
+ // Keep migration best-effort.
104
+ }
105
+ }
106
+
107
+ interface LegacyAgentOp {
108
+ ts?: string;
109
+ tool?: string;
110
+ params?: Record<string, unknown>;
111
+ result?: 'ok' | 'error';
112
+ message?: string;
113
+ durationMs?: number;
114
+ }
115
+
116
+ function parseLegacyMdBlocks(raw: string): LegacyAgentOp[] {
117
+ const blocks: LegacyAgentOp[] = [];
118
+ const re = /```agent-op\s*\n([\s\S]*?)```/g;
119
+ let match: RegExpExecArray | null;
120
+ while ((match = re.exec(raw)) !== null) {
121
+ try {
122
+ blocks.push(JSON.parse(match[1].trim()) as LegacyAgentOp);
123
+ } catch {
124
+ // Ignore malformed blocks.
125
+ }
126
+ }
127
+ return blocks;
128
+ }
129
+
130
+ function parseJsonLines(raw: string): LegacyAgentOp[] {
131
+ const entries: LegacyAgentOp[] = [];
132
+ for (const line of raw.split('\n')) {
133
+ const trimmed = line.trim();
134
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//')) continue;
135
+ try {
136
+ entries.push(JSON.parse(trimmed) as LegacyAgentOp);
137
+ } catch {
138
+ // Ignore malformed lines.
139
+ }
140
+ }
141
+ return entries;
142
+ }
143
+
144
+ function toEvent(entry: LegacyAgentOp, op: AgentAuditEvent['op'], idx: number): AgentAuditEvent {
145
+ const tool = typeof entry.tool === 'string' && entry.tool.trim() ? entry.tool.trim() : 'unknown-tool';
146
+ const result = entry.result === 'error' ? 'error' : 'ok';
147
+ const params = entry.params && typeof entry.params === 'object' ? entry.params : {};
148
+ return {
149
+ id: `legacy-${Date.now().toString(36)}-${idx.toString(36)}`,
150
+ ts: validIso(entry.ts),
151
+ tool,
152
+ params,
153
+ result,
154
+ message: normalizeMessage(entry.message),
155
+ durationMs: typeof entry.durationMs === 'number' ? entry.durationMs : undefined,
156
+ op,
157
+ };
158
+ }
159
+
160
+ function importLegacyMdIfNeeded(mindRoot: string, state: AgentAuditState): AgentAuditState {
161
+ const legacyPath = path.join(mindRoot, LEGACY_MD_FILE);
162
+ if (!fs.existsSync(legacyPath)) return state;
163
+
164
+ let raw = '';
165
+ try {
166
+ raw = fs.readFileSync(legacyPath, 'utf-8');
167
+ } catch {
168
+ return state;
169
+ }
170
+
171
+ const blocks = parseLegacyMdBlocks(raw);
172
+ const importedCount = state.legacy?.mdImportedCount ?? 0;
173
+ if (blocks.length <= importedCount) {
174
+ if (blocks.length > 0) removeLegacyFile(legacyPath);
175
+ return state;
176
+ }
177
+
178
+ const incoming = blocks.slice(importedCount);
179
+ const imported = incoming.map((entry, idx) => toEvent(entry, 'legacy_agent_audit_md_import', idx));
180
+ const merged = [...state.events, ...imported]
181
+ .sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime())
182
+ .slice(0, MAX_EVENTS);
183
+
184
+ const next = {
185
+ ...state,
186
+ events: merged,
187
+ legacy: {
188
+ mdImportedCount: blocks.length,
189
+ jsonlImportedCount: state.legacy?.jsonlImportedCount ?? 0,
190
+ lastImportedAt: nowIso(),
191
+ },
192
+ };
193
+ removeLegacyFile(legacyPath);
194
+ return next;
195
+ }
196
+
197
+ function importLegacyJsonlIfNeeded(mindRoot: string, state: AgentAuditState): AgentAuditState {
198
+ const legacyPath = path.join(mindRoot, LEGACY_JSONL_FILE);
199
+ if (!fs.existsSync(legacyPath)) return state;
200
+
201
+ let raw = '';
202
+ try {
203
+ raw = fs.readFileSync(legacyPath, 'utf-8');
204
+ } catch {
205
+ return state;
206
+ }
207
+
208
+ const lines = parseJsonLines(raw);
209
+ const importedCount = state.legacy?.jsonlImportedCount ?? 0;
210
+ if (lines.length <= importedCount) {
211
+ if (lines.length > 0) removeLegacyFile(legacyPath);
212
+ return state;
213
+ }
214
+
215
+ const incoming = lines.slice(importedCount);
216
+ const imported = incoming.map((entry, idx) => toEvent(entry, 'legacy_agent_log_jsonl_import', idx));
217
+ const merged = [...state.events, ...imported]
218
+ .sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime())
219
+ .slice(0, MAX_EVENTS);
220
+
221
+ const next = {
222
+ ...state,
223
+ events: merged,
224
+ legacy: {
225
+ mdImportedCount: state.legacy?.mdImportedCount ?? 0,
226
+ jsonlImportedCount: lines.length,
227
+ lastImportedAt: nowIso(),
228
+ },
229
+ };
230
+ removeLegacyFile(legacyPath);
231
+ return next;
232
+ }
233
+
234
+ function loadState(mindRoot: string): AgentAuditState {
235
+ const base = readState(mindRoot);
236
+ const mdMigrated = importLegacyMdIfNeeded(mindRoot, base);
237
+ const migrated = importLegacyJsonlIfNeeded(mindRoot, mdMigrated);
238
+ const changed =
239
+ base.events.length !== migrated.events.length ||
240
+ (base.legacy?.mdImportedCount ?? 0) !== (migrated.legacy?.mdImportedCount ?? 0) ||
241
+ (base.legacy?.jsonlImportedCount ?? 0) !== (migrated.legacy?.jsonlImportedCount ?? 0);
242
+ if (changed) writeState(mindRoot, migrated);
243
+ return migrated;
244
+ }
245
+
246
+ export function appendAgentAuditEvent(mindRoot: string, input: AgentAuditInput): AgentAuditEvent {
247
+ const state = loadState(mindRoot);
248
+ const event: AgentAuditEvent = {
249
+ id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
250
+ ts: validIso(input.ts),
251
+ tool: input.tool,
252
+ params: input.params && typeof input.params === 'object' ? input.params : {},
253
+ result: input.result === 'error' ? 'error' : 'ok',
254
+ message: normalizeMessage(input.message),
255
+ durationMs: typeof input.durationMs === 'number' ? input.durationMs : undefined,
256
+ op: 'append',
257
+ };
258
+ state.events.unshift(event);
259
+ if (state.events.length > MAX_EVENTS) state.events = state.events.slice(0, MAX_EVENTS);
260
+ writeState(mindRoot, state);
261
+ return event;
262
+ }
263
+
264
+ export function listAgentAuditEvents(mindRoot: string, limit = 100): AgentAuditEvent[] {
265
+ const state = loadState(mindRoot);
266
+ const safeLimit = Math.max(1, Math.min(limit, 1000));
267
+ return state.events.slice(0, safeLimit);
268
+ }
269
+
270
+ export function parseAgentAuditJsonLines(raw: string): AgentAuditInput[] {
271
+ return parseJsonLines(raw).map((entry) => ({
272
+ ts: validIso(entry.ts),
273
+ tool: typeof entry.tool === 'string' && entry.tool.trim() ? entry.tool.trim() : 'unknown-tool',
274
+ params: entry.params && typeof entry.params === 'object' ? entry.params : {},
275
+ result: entry.result === 'error' ? 'error' : 'ok',
276
+ message: normalizeMessage(entry.message),
277
+ durationMs: typeof entry.durationMs === 'number' ? entry.durationMs : undefined,
278
+ }));
279
+ }
280
+
@@ -78,3 +78,14 @@ export type {
78
78
  ContentChangeSummary,
79
79
  ContentChangeSource,
80
80
  } from './content-changes';
81
+
82
+ // Agent audit log
83
+ export {
84
+ appendAgentAuditEvent,
85
+ listAgentAuditEvents,
86
+ parseAgentAuditJsonLines,
87
+ } from './agent-audit-log';
88
+ export type {
89
+ AgentAuditEvent,
90
+ AgentAuditInput,
91
+ } from './agent-audit-log';
package/app/lib/fs.ts CHANGED
@@ -57,6 +57,14 @@ interface FileTreeCache {
57
57
  let _cache: FileTreeCache | null = null;
58
58
  const CACHE_TTL_MS = 5_000; // 5 seconds
59
59
 
60
+ let _treeVersion = 0;
61
+
62
+ /** Monotonically increasing counter — bumped on every file mutation so the
63
+ * client can cheaply detect changes without rebuilding the full tree. */
64
+ export function getTreeVersion(): number {
65
+ return _treeVersion;
66
+ }
67
+
60
68
  function isCacheValid(): boolean {
61
69
  return _cache !== null && (Date.now() - _cache.timestamp) < CACHE_TTL_MS;
62
70
  }
@@ -65,6 +73,7 @@ function isCacheValid(): boolean {
65
73
  export function invalidateCache(): void {
66
74
  _cache = null;
67
75
  _searchIndex = null;
76
+ _treeVersion++;
68
77
  invalidateSearchIndex();
69
78
  }
70
79