@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/agent/log.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
|
|
@@ -32,11 +32,18 @@ interface ChangeLogState {
|
|
|
32
32
|
version: 1;
|
|
33
33
|
lastSeenAt: string | null;
|
|
34
34
|
events: ContentChangeEvent[];
|
|
35
|
+
legacy?: {
|
|
36
|
+
agentDiffImportedCount?: number;
|
|
37
|
+
lastImportedAt?: string | null;
|
|
38
|
+
};
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
interface ListOptions {
|
|
38
42
|
path?: string;
|
|
39
43
|
limit?: number;
|
|
44
|
+
source?: ContentChangeSource;
|
|
45
|
+
op?: string;
|
|
46
|
+
q?: string;
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
export interface ContentChangeSummary {
|
|
@@ -64,6 +71,10 @@ function defaultState(): ChangeLogState {
|
|
|
64
71
|
version: 1,
|
|
65
72
|
lastSeenAt: null,
|
|
66
73
|
events: [],
|
|
74
|
+
legacy: {
|
|
75
|
+
agentDiffImportedCount: 0,
|
|
76
|
+
lastImportedAt: null,
|
|
77
|
+
},
|
|
67
78
|
};
|
|
68
79
|
}
|
|
69
80
|
|
|
@@ -87,6 +98,16 @@ function readState(mindRoot: string): ChangeLogState {
|
|
|
87
98
|
version: 1,
|
|
88
99
|
lastSeenAt: typeof parsed.lastSeenAt === 'string' ? parsed.lastSeenAt : null,
|
|
89
100
|
events: parsed.events,
|
|
101
|
+
legacy: {
|
|
102
|
+
agentDiffImportedCount:
|
|
103
|
+
typeof parsed.legacy?.agentDiffImportedCount === 'number'
|
|
104
|
+
? parsed.legacy.agentDiffImportedCount
|
|
105
|
+
: 0,
|
|
106
|
+
lastImportedAt:
|
|
107
|
+
typeof parsed.legacy?.lastImportedAt === 'string'
|
|
108
|
+
? parsed.legacy.lastImportedAt
|
|
109
|
+
: null,
|
|
110
|
+
},
|
|
90
111
|
};
|
|
91
112
|
} catch {
|
|
92
113
|
return defaultState();
|
|
@@ -99,8 +120,115 @@ function writeState(mindRoot: string, state: ChangeLogState): void {
|
|
|
99
120
|
fs.writeFileSync(file, JSON.stringify(state, null, 2), 'utf-8');
|
|
100
121
|
}
|
|
101
122
|
|
|
102
|
-
|
|
123
|
+
interface LegacyAgentDiffEntry {
|
|
124
|
+
ts?: string;
|
|
125
|
+
path?: string;
|
|
126
|
+
tool?: string;
|
|
127
|
+
before?: string;
|
|
128
|
+
after?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function parseLegacyAgentDiffBlocks(content: string): LegacyAgentDiffEntry[] {
|
|
132
|
+
const blocks: LegacyAgentDiffEntry[] = [];
|
|
133
|
+
const re = /```agent-diff\s*\n([\s\S]*?)```/g;
|
|
134
|
+
let m: RegExpExecArray | null;
|
|
135
|
+
while ((m = re.exec(content)) !== null) {
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(m[1].trim()) as LegacyAgentDiffEntry;
|
|
138
|
+
blocks.push(parsed);
|
|
139
|
+
} catch {
|
|
140
|
+
// Skip malformed block, keep import best-effort.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return blocks;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function toValidIso(ts: string | undefined): string {
|
|
147
|
+
if (!ts) return nowIso();
|
|
148
|
+
const ms = new Date(ts).getTime();
|
|
149
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : nowIso();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function removeLegacyFile(filePath: string): void {
|
|
153
|
+
try {
|
|
154
|
+
fs.rmSync(filePath, { force: true });
|
|
155
|
+
} catch {
|
|
156
|
+
// keep best-effort; migration should not fail main flow.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function importLegacyAgentDiffIfNeeded(mindRoot: string, state: ChangeLogState): ChangeLogState {
|
|
161
|
+
const legacyPath = path.join(mindRoot, 'Agent-Diff.md');
|
|
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 = parseLegacyAgentDiffBlocks(raw);
|
|
172
|
+
const importedCount = state.legacy?.agentDiffImportedCount ?? 0;
|
|
173
|
+
if (blocks.length <= importedCount) {
|
|
174
|
+
// Already migrated before: remove legacy file to avoid stale duplicate source.
|
|
175
|
+
if (blocks.length > 0) removeLegacyFile(legacyPath);
|
|
176
|
+
return state;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const incoming = blocks.slice(importedCount);
|
|
180
|
+
const importedEvents: ContentChangeEvent[] = incoming.map((entry, idx) => {
|
|
181
|
+
const before = normalizeText(entry.before);
|
|
182
|
+
const after = normalizeText(entry.after);
|
|
183
|
+
const toolName = typeof entry.tool === 'string' && entry.tool.trim()
|
|
184
|
+
? entry.tool.trim()
|
|
185
|
+
: 'unknown-tool';
|
|
186
|
+
const targetPath = typeof entry.path === 'string' && entry.path.trim()
|
|
187
|
+
? entry.path
|
|
188
|
+
: 'Agent-Diff.md';
|
|
189
|
+
return {
|
|
190
|
+
id: `legacy-${Date.now().toString(36)}-${idx.toString(36)}`,
|
|
191
|
+
ts: toValidIso(entry.ts),
|
|
192
|
+
op: 'legacy_agent_diff_import',
|
|
193
|
+
path: targetPath,
|
|
194
|
+
source: 'agent',
|
|
195
|
+
summary: `Imported legacy agent diff (${toolName})`,
|
|
196
|
+
before: before.value,
|
|
197
|
+
after: after.value,
|
|
198
|
+
truncated: before.truncated || after.truncated || undefined,
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const merged = [...state.events, ...importedEvents].sort(
|
|
203
|
+
(a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime(),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const nextState = {
|
|
207
|
+
...state,
|
|
208
|
+
events: merged.slice(0, MAX_EVENTS),
|
|
209
|
+
legacy: {
|
|
210
|
+
agentDiffImportedCount: blocks.length,
|
|
211
|
+
lastImportedAt: nowIso(),
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
removeLegacyFile(legacyPath);
|
|
215
|
+
return nextState;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function loadState(mindRoot: string): ChangeLogState {
|
|
103
219
|
const state = readState(mindRoot);
|
|
220
|
+
const migrated = importLegacyAgentDiffIfNeeded(mindRoot, state);
|
|
221
|
+
const changed =
|
|
222
|
+
(state.legacy?.agentDiffImportedCount ?? 0) !== (migrated.legacy?.agentDiffImportedCount ?? 0) ||
|
|
223
|
+
state.events.length !== migrated.events.length;
|
|
224
|
+
if (changed) {
|
|
225
|
+
writeState(mindRoot, migrated);
|
|
226
|
+
}
|
|
227
|
+
return migrated;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function appendContentChange(mindRoot: string, input: ContentChangeInput): ContentChangeEvent {
|
|
231
|
+
const state = loadState(mindRoot);
|
|
104
232
|
const before = normalizeText(input.before);
|
|
105
233
|
const after = normalizeText(input.after);
|
|
106
234
|
const event: ContentChangeEvent = {
|
|
@@ -125,23 +253,35 @@ export function appendContentChange(mindRoot: string, input: ContentChangeInput)
|
|
|
125
253
|
}
|
|
126
254
|
|
|
127
255
|
export function listContentChanges(mindRoot: string, options: ListOptions = {}): ContentChangeEvent[] {
|
|
128
|
-
const state =
|
|
256
|
+
const state = loadState(mindRoot);
|
|
129
257
|
const limit = Math.max(1, Math.min(options.limit ?? 50, 200));
|
|
130
|
-
const pathFilter = options.path;
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
258
|
+
const pathFilter = options.path?.trim();
|
|
259
|
+
const sourceFilter = options.source;
|
|
260
|
+
const opFilter = options.op?.trim();
|
|
261
|
+
const q = options.q?.trim().toLowerCase();
|
|
262
|
+
const events = state.events.filter((event) => {
|
|
263
|
+
if (pathFilter && event.path !== pathFilter && event.beforePath !== pathFilter && event.afterPath !== pathFilter) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (sourceFilter && event.source !== sourceFilter) return false;
|
|
267
|
+
if (opFilter && event.op !== opFilter) return false;
|
|
268
|
+
if (q) {
|
|
269
|
+
const haystack = `${event.path} ${event.beforePath ?? ''} ${event.afterPath ?? ''} ${event.summary} ${event.op} ${event.source}`.toLowerCase();
|
|
270
|
+
if (!haystack.includes(q)) return false;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
});
|
|
134
274
|
return events.slice(0, limit);
|
|
135
275
|
}
|
|
136
276
|
|
|
137
277
|
export function markContentChangesSeen(mindRoot: string): void {
|
|
138
|
-
const state =
|
|
278
|
+
const state = loadState(mindRoot);
|
|
139
279
|
state.lastSeenAt = nowIso();
|
|
140
280
|
writeState(mindRoot, state);
|
|
141
281
|
}
|
|
142
282
|
|
|
143
283
|
export function getContentChangeSummary(mindRoot: string): ContentChangeSummary {
|
|
144
|
-
const state =
|
|
284
|
+
const state = loadState(mindRoot);
|
|
145
285
|
const lastSeenAtMs = state.lastSeenAt ? new Date(state.lastSeenAt).getTime() : 0;
|
|
146
286
|
const unreadCount = state.events.filter((event) => new Date(event.ts).getTime() > lastSeenAtMs).length;
|
|
147
287
|
return {
|
package/app/lib/core/index.ts
CHANGED
|
@@ -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
|
|
|
@@ -199,7 +208,13 @@ export function appendContentChange(input: ContentChangeInput): ContentChangeEve
|
|
|
199
208
|
return coreAppendContentChange(getMindRoot(), input);
|
|
200
209
|
}
|
|
201
210
|
|
|
202
|
-
export function listContentChanges(options: {
|
|
211
|
+
export function listContentChanges(options: {
|
|
212
|
+
path?: string;
|
|
213
|
+
limit?: number;
|
|
214
|
+
source?: 'user' | 'agent' | 'system';
|
|
215
|
+
op?: string;
|
|
216
|
+
q?: string;
|
|
217
|
+
} = {}): ContentChangeEvent[] {
|
|
203
218
|
return coreListContentChanges(getMindRoot(), options);
|
|
204
219
|
}
|
|
205
220
|
|