@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.
- package/README.md +4 -0
- package/README_zh.md +4 -0
- package/app/app/api/ask/route.ts +12 -0
- package/app/app/api/file/route.ts +9 -0
- package/app/app/api/mcp/agents/route.ts +27 -1
- package/app/app/api/skills/route.ts +18 -2
- package/app/app/api/tree-version/route.ts +8 -0
- 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 +6 -11
- 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 +1 -1
- package/app/components/RightAskPanel.tsx +1 -1
- package/app/components/SearchModal.tsx +10 -2
- package/app/components/SidebarLayout.tsx +35 -10
- package/app/components/ThemeToggle.tsx +1 -1
- package/app/components/agents/AgentDetailContent.tsx +454 -59
- package/app/components/agents/AgentsContentPage.tsx +70 -5
- package/app/components/agents/AgentsMcpSection.tsx +474 -159
- package/app/components/agents/AgentsOverviewSection.tsx +418 -59
- package/app/components/agents/AgentsPrimitives.tsx +335 -0
- package/app/components/agents/AgentsSkillsSection.tsx +739 -121
- package/app/components/agents/SkillDetailPopover.tsx +416 -0
- package/app/components/agents/agents-content-model.ts +292 -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 +1 -2
- 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 +12 -104
- package/app/components/panels/AgentsPanelAgentDetail.tsx +2 -2
- package/app/components/panels/AgentsPanelAgentGroups.tsx +3 -7
- package/app/components/panels/AgentsPanelAgentListRow.tsx +9 -11
- package/app/components/panels/EchoPanel.tsx +8 -10
- package/app/components/panels/PanelNavRow.tsx +9 -2
- package/app/components/panels/PluginsPanel.tsx +2 -2
- package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +30 -8
- package/app/components/renderers/agent-inspector/manifest.ts +3 -3
- 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 +2 -2
- 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 +4 -2
- 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/index.ts +11 -0
- package/app/lib/fs.ts +9 -0
- package/app/lib/i18n-en.ts +259 -33
- package/app/lib/i18n-zh.ts +258 -32
- package/app/lib/mcp-agents.ts +231 -2
- package/app/lib/types.ts +2 -0
- package/package.json +1 -1
- package/scripts/migrate-agent-audit-log.js +170 -0
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
|
+
|
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
|
|