@geminilight/mindos 1.1.8 → 1.1.10
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/dist/agent/agent-run-context.d.ts +8 -0
- package/dist/agent/agent-run-context.d.ts.map +1 -0
- package/dist/agent/agent-run-context.js +9 -0
- package/dist/agent/agent-run-context.js.map +1 -0
- package/dist/agent/capability-registry.d.ts +60 -0
- package/dist/agent/capability-registry.d.ts.map +1 -0
- package/dist/agent/capability-registry.js +323 -0
- package/dist/agent/capability-registry.js.map +1 -0
- package/dist/agent/file-write-lock.d.ts +22 -0
- package/dist/agent/file-write-lock.d.ts.map +1 -0
- package/dist/agent/file-write-lock.js +86 -0
- package/dist/agent/file-write-lock.js.map +1 -0
- package/dist/agent/global-state.d.ts +109 -0
- package/dist/agent/global-state.d.ts.map +1 -0
- package/dist/agent/global-state.js +118 -0
- package/dist/agent/global-state.js.map +1 -0
- package/dist/agent/index.d.ts +17 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +17 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/kb-extension.d.ts +38 -0
- package/dist/agent/kb-extension.d.ts.map +1 -0
- package/dist/agent/kb-extension.js +140 -0
- package/dist/agent/kb-extension.js.map +1 -0
- package/dist/agent/kb-tools.d.ts +158 -0
- package/dist/agent/kb-tools.d.ts.map +1 -0
- package/dist/agent/kb-tools.js +707 -0
- package/dist/agent/kb-tools.js.map +1 -0
- package/dist/agent/line-diff.d.ts +13 -0
- package/dist/agent/line-diff.d.ts.map +1 -0
- package/dist/agent/line-diff.js +66 -0
- package/dist/agent/line-diff.js.map +1 -0
- package/dist/agent/paragraph-extract.d.ts +25 -0
- package/dist/agent/paragraph-extract.d.ts.map +1 -0
- package/dist/agent/paragraph-extract.js +106 -0
- package/dist/agent/paragraph-extract.js.map +1 -0
- package/dist/agent/permission-policy.d.ts +37 -0
- package/dist/agent/permission-policy.d.ts.map +1 -0
- package/dist/agent/permission-policy.js +153 -0
- package/dist/agent/permission-policy.js.map +1 -0
- package/dist/agent/redaction.d.ts +3 -0
- package/dist/agent/redaction.d.ts.map +1 -0
- package/dist/agent/redaction.js +52 -0
- package/dist/agent/redaction.js.map +1 -0
- package/dist/agent/result-reducer.d.ts +36 -0
- package/dist/agent/result-reducer.d.ts.map +1 -0
- package/dist/agent/result-reducer.js +101 -0
- package/dist/agent/result-reducer.js.map +1 -0
- package/dist/agent/run-cancellation.d.ts +13 -0
- package/dist/agent/run-cancellation.d.ts.map +1 -0
- package/dist/agent/run-cancellation.js +97 -0
- package/dist/agent/run-cancellation.js.map +1 -0
- package/dist/agent/run-ledger-types.d.ts +197 -0
- package/dist/agent/run-ledger-types.d.ts.map +1 -0
- package/dist/agent/run-ledger-types.js +9 -0
- package/dist/agent/run-ledger-types.js.map +1 -0
- package/dist/agent/run-ledger.d.ts +44 -0
- package/dist/agent/run-ledger.d.ts.map +1 -0
- package/dist/agent/run-ledger.js +814 -0
- package/dist/agent/run-ledger.js.map +1 -0
- package/dist/agent/run-timeline-events.d.ts +3 -0
- package/dist/agent/run-timeline-events.d.ts.map +1 -0
- package/dist/agent/run-timeline-events.js +239 -0
- package/dist/agent/run-timeline-events.js.map +1 -0
- package/dist/agent/runtime-permission-bridge.d.ts +34 -0
- package/dist/agent/runtime-permission-bridge.d.ts.map +1 -0
- package/dist/agent/runtime-permission-bridge.js +284 -0
- package/dist/agent/runtime-permission-bridge.js.map +1 -0
- package/dist/agent/stream-consumer.d.ts +65 -0
- package/dist/agent/stream-consumer.d.ts.map +1 -0
- package/dist/agent/stream-consumer.js +664 -0
- package/dist/agent/stream-consumer.js.map +1 -0
- package/dist/agent/stream-message-types.d.ts +138 -0
- package/dist/agent/stream-message-types.d.ts.map +1 -0
- package/dist/agent/stream-message-types.js +14 -0
- package/dist/agent/stream-message-types.js.map +1 -0
- package/dist/agent/subagent-ledger-extension.d.ts +22 -0
- package/dist/agent/subagent-ledger-extension.d.ts.map +1 -0
- package/dist/agent/subagent-ledger-extension.js +533 -0
- package/dist/agent/subagent-ledger-extension.js.map +1 -0
- package/dist/agent/subagent-orchestrator.d.ts +62 -0
- package/dist/agent/subagent-orchestrator.d.ts.map +1 -0
- package/dist/agent/subagent-orchestrator.js +350 -0
- package/dist/agent/subagent-orchestrator.js.map +1 -0
- package/dist/agent/user-question-bridge.d.ts +90 -0
- package/dist/agent/user-question-bridge.d.ts.map +1 -0
- package/dist/agent/user-question-bridge.js +260 -0
- package/dist/agent/user-question-bridge.js.map +1 -0
- package/dist/agent-runtime/capabilities.d.ts +10 -0
- package/dist/agent-runtime/capabilities.d.ts.map +1 -0
- package/dist/agent-runtime/capabilities.js +100 -0
- package/dist/agent-runtime/capabilities.js.map +1 -0
- package/dist/agent-runtime/child-process.d.ts +21 -0
- package/dist/agent-runtime/child-process.d.ts.map +1 -0
- package/dist/agent-runtime/child-process.js +29 -0
- package/dist/agent-runtime/child-process.js.map +1 -0
- package/dist/agent-runtime/claude-code-cli.d.ts.map +1 -1
- package/dist/agent-runtime/claude-code-cli.js +31 -4
- package/dist/agent-runtime/claude-code-cli.js.map +1 -1
- package/dist/agent-runtime/claude-code-sdk.d.ts.map +1 -1
- package/dist/agent-runtime/claude-code-sdk.js +22 -5
- package/dist/agent-runtime/claude-code-sdk.js.map +1 -1
- package/dist/agent-runtime/codex-app-server.d.ts.map +1 -1
- package/dist/agent-runtime/codex-app-server.js +27 -3
- package/dist/agent-runtime/codex-app-server.js.map +1 -1
- package/dist/agent-runtime/codex-env.d.ts +2 -1
- package/dist/agent-runtime/codex-env.d.ts.map +1 -1
- package/dist/agent-runtime/codex-env.js +14 -2
- package/dist/agent-runtime/codex-env.js.map +1 -1
- package/dist/agent-runtime/descriptors.d.ts +19 -0
- package/dist/agent-runtime/descriptors.d.ts.map +1 -0
- package/dist/agent-runtime/descriptors.js +143 -0
- package/dist/agent-runtime/descriptors.js.map +1 -0
- package/dist/agent-runtime/detection.d.ts +15 -0
- package/dist/agent-runtime/detection.d.ts.map +1 -0
- package/dist/agent-runtime/detection.js +124 -0
- package/dist/agent-runtime/detection.js.map +1 -0
- package/dist/agent-runtime/index.d.ts +2 -0
- package/dist/agent-runtime/index.d.ts.map +1 -1
- package/dist/agent-runtime/index.js +2 -0
- package/dist/agent-runtime/index.js.map +1 -1
- package/dist/agent-runtime/registry.d.ts +152 -0
- package/dist/agent-runtime/registry.d.ts.map +1 -0
- package/dist/agent-runtime/registry.js +84 -0
- package/dist/agent-runtime/registry.js.map +1 -0
- package/dist/agent-runtime/run.d.ts +12 -0
- package/dist/agent-runtime/run.d.ts.map +1 -1
- package/dist/agent-runtime/run.js +16 -1
- package/dist/agent-runtime/run.js.map +1 -1
- package/dist/foundation/config/schema.d.ts +1 -1
- package/dist/foundation/mind-root/index.d.ts +5 -0
- package/dist/foundation/mind-root/index.d.ts.map +1 -0
- package/dist/foundation/mind-root/index.js +67 -0
- package/dist/foundation/mind-root/index.js.map +1 -0
- package/dist/foundation/native-import.d.ts +22 -0
- package/dist/foundation/native-import.d.ts.map +1 -0
- package/dist/foundation/native-import.js +22 -0
- package/dist/foundation/native-import.js.map +1 -0
- package/dist/foundation.d.ts +1 -0
- package/dist/foundation.d.ts.map +1 -1
- package/dist/foundation.js +1 -0
- package/dist/foundation.js.map +1 -1
- package/dist/protocols/mcp-server/index.cjs +24 -23
- package/dist/protocols/mcp-server/index.js +45 -0
- package/dist/protocols/mcp-server/index.js.map +1 -1
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +1 -0
- package/dist/server/contract.js.map +1 -1
- package/dist/server/handlers/agent-runtimes.d.ts +3 -125
- package/dist/server/handlers/agent-runtimes.d.ts.map +1 -1
- package/dist/server/handlers/agent-runtimes.js +6 -414
- package/dist/server/handlers/agent-runtimes.js.map +1 -1
- package/dist/server/handlers/assistants.d.ts.map +1 -1
- package/dist/server/handlers/assistants.js +1 -0
- package/dist/server/handlers/assistants.js.map +1 -1
- package/dist/server/handlers/mcp-agents.d.ts +8 -0
- package/dist/server/handlers/mcp-agents.d.ts.map +1 -1
- package/dist/server/handlers/mcp-agents.js +39 -0
- package/dist/server/handlers/mcp-agents.js.map +1 -1
- package/dist/server/handlers/mcp-install.d.ts +0 -6
- package/dist/server/handlers/mcp-install.d.ts.map +1 -1
- package/dist/server/handlers/mcp-install.js +1 -31
- package/dist/server/handlers/mcp-install.js.map +1 -1
- package/dist/server/handlers/skill-links.d.ts +130 -0
- package/dist/server/handlers/skill-links.d.ts.map +1 -0
- package/dist/server/handlers/skill-links.js +534 -0
- package/dist/server/handlers/skill-links.js.map +1 -0
- package/dist/server/handlers/skills.d.ts +12 -1
- package/dist/server/handlers/skills.d.ts.map +1 -1
- package/dist/server/handlers/skills.js +48 -5
- package/dist/server/handlers/skills.js.map +1 -1
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +16 -3
- package/dist/server/http.js.map +1 -1
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/route-ownership.d.ts.map +1 -1
- package/dist/server/route-ownership.js +3 -0
- package/dist/server/route-ownership.js.map +1 -1
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/runtime.js +8 -4
- package/dist/server/runtime.js.map +1 -1
- package/dist/session/index.d.ts +43 -2
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +42 -5
- package/dist/session/index.js.map +1 -1
- package/dist/session/pi-coding-agent-runtime.d.ts +4 -2
- package/dist/session/pi-coding-agent-runtime.d.ts.map +1 -1
- package/dist/session/pi-coding-agent-runtime.js +18 -11
- package/dist/session/pi-coding-agent-runtime.js.map +1 -1
- package/package.json +15 -9
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
// Sunk from packages/web/lib/agent/tools.ts (Wave 3, spec-agent-core-consolidation).
|
|
2
|
+
// The MindOS knowledge-base tool set, host-injected: file IO, search, skills,
|
|
3
|
+
// lint/compile, and async diff are provided by the host (web app, headless CLI)
|
|
4
|
+
// through MindosKbToolsHost; write locks, run-ledger file events, permission
|
|
5
|
+
// filtering, and output shaping live here so every host gets identical tools.
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { Type } from '@sinclair/typebox';
|
|
8
|
+
import { createMindosAgentPermissionPolicy, getMindosKbToolNameSet, MINDOS_CHAT_KB_TOOL_NAMES, MINDOS_ORGANIZE_KB_TOOL_NAMES, MINDOS_WRITE_TOOL_NAMES, } from './permission-policy.js';
|
|
9
|
+
import { withAgentFileWriteLock, withAgentFileWriteLocks } from './file-write-lock.js';
|
|
10
|
+
import { getCurrentAgentRunContext } from './agent-run-context.js';
|
|
11
|
+
import { appendAgentRunEvent } from './run-ledger.js';
|
|
12
|
+
import { buildLineDiff, collapseDiffContext } from './line-diff.js';
|
|
13
|
+
import { extractRelevantContent } from './paragraph-extract.js';
|
|
14
|
+
// Max chars per file to avoid token overflow (~100k chars ≈ ~25k tokens)
|
|
15
|
+
const MAX_FILE_CHARS = 20_000;
|
|
16
|
+
export function truncate(content, query) {
|
|
17
|
+
const { result } = extractRelevantContent(content, MAX_FILE_CHARS, query);
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
// ─── Helper: format tool error consistently ────────────────────────────────
|
|
21
|
+
function formatToolError(error) {
|
|
22
|
+
return error instanceof Error ? error.message : String(error);
|
|
23
|
+
}
|
|
24
|
+
// ─── Helper: build a text-only AgentToolResult ──────────────────────────────
|
|
25
|
+
function textResult(text) {
|
|
26
|
+
return { content: [{ type: 'text', text }], details: {} };
|
|
27
|
+
}
|
|
28
|
+
/** Format DiffLine[] into a compact string */
|
|
29
|
+
function formatDiff(raw) {
|
|
30
|
+
const inserts = raw.filter(r => r.type === 'insert').length;
|
|
31
|
+
const deletes = raw.filter(r => r.type === 'delete').length;
|
|
32
|
+
const stats = `+${inserts} −${deletes}`;
|
|
33
|
+
const collapsed = collapseDiffContext(raw, 2);
|
|
34
|
+
const MAX_DIFF_LINES = 30;
|
|
35
|
+
const lines = [];
|
|
36
|
+
for (const row of collapsed) {
|
|
37
|
+
if (lines.length >= MAX_DIFF_LINES) {
|
|
38
|
+
lines.push('... (diff truncated)');
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
if (row.type === 'gap') {
|
|
42
|
+
lines.push(` ... ${row.count} lines unchanged ...`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const prefix = row.type === 'insert' ? '+' : row.type === 'delete' ? '-' : ' ';
|
|
46
|
+
lines.push(`${prefix} ${row.text}`);
|
|
47
|
+
}
|
|
48
|
+
return `(${stats})\n\n--- changes ---\n${lines.join('\n')}`;
|
|
49
|
+
}
|
|
50
|
+
function currentRunIdForToolEvent() {
|
|
51
|
+
const context = getCurrentAgentRunContext();
|
|
52
|
+
return context?.parentRunId ?? context?.rootRunId;
|
|
53
|
+
}
|
|
54
|
+
function appendFileChangedEvent(input) {
|
|
55
|
+
const runId = currentRunIdForToolEvent();
|
|
56
|
+
if (!runId)
|
|
57
|
+
return;
|
|
58
|
+
appendAgentRunEvent(runId, {
|
|
59
|
+
type: 'file_changed',
|
|
60
|
+
category: 'file',
|
|
61
|
+
filePath: input.path,
|
|
62
|
+
message: input.summary,
|
|
63
|
+
data: {
|
|
64
|
+
kind: 'file',
|
|
65
|
+
path: input.path,
|
|
66
|
+
action: input.action,
|
|
67
|
+
status: input.status ?? 'completed',
|
|
68
|
+
summary: input.summary,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Safe execute wrapper — catches all errors, returns error text (never throws) */
|
|
73
|
+
function safeExecute(
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
fn) {
|
|
76
|
+
return async (toolCallId, params, signal) => {
|
|
77
|
+
try {
|
|
78
|
+
return await fn(toolCallId, params, signal);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return textResult(`Error: ${formatToolError(e)}`);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// ─── TypeBox Schemas ────────────────────────────────────────────────────────
|
|
86
|
+
const ListFilesParams = Type.Object({
|
|
87
|
+
path: Type.Optional(Type.String({ description: 'Optional subdirectory to list (e.g. "Projects/Products"). Omit to list everything.' })),
|
|
88
|
+
depth: Type.Optional(Type.Number({ description: 'Max tree depth to expand (default 3). Directories deeper than this show item count only.', minimum: 1, maximum: 10 })),
|
|
89
|
+
});
|
|
90
|
+
const PathParam = Type.Object({
|
|
91
|
+
path: Type.String({ description: 'Relative file path' }),
|
|
92
|
+
});
|
|
93
|
+
const ReadFileChunkParams = Type.Object({
|
|
94
|
+
path: Type.String({ description: 'Relative file path' }),
|
|
95
|
+
start_line: Type.Number({ description: 'Line number to start reading from (1-indexed)' }),
|
|
96
|
+
end_line: Type.Number({ description: 'Line number to stop reading at (1-indexed)' }),
|
|
97
|
+
});
|
|
98
|
+
const QueryParam = Type.Object({
|
|
99
|
+
query: Type.String({ description: 'Search query (case-insensitive)' }),
|
|
100
|
+
});
|
|
101
|
+
const LimitParam = Type.Object({
|
|
102
|
+
limit: Type.Optional(Type.Number({ description: 'Number of files to return (default 10)', minimum: 1, maximum: 50 })),
|
|
103
|
+
});
|
|
104
|
+
const WriteFileParams = Type.Object({
|
|
105
|
+
path: Type.String({ description: 'Relative file path' }),
|
|
106
|
+
content: Type.String({ description: 'New full content' }),
|
|
107
|
+
});
|
|
108
|
+
const CreateFileParams = Type.Object({
|
|
109
|
+
path: Type.String({ description: 'Relative file path (must end in .md or .csv)' }),
|
|
110
|
+
content: Type.Optional(Type.String({ description: 'Initial file content' })),
|
|
111
|
+
});
|
|
112
|
+
const BatchCreateFileParams = Type.Object({
|
|
113
|
+
files: Type.Array(Type.Object({
|
|
114
|
+
path: Type.String({ description: 'Relative file path (must end in .md or .csv)' }),
|
|
115
|
+
content: Type.String({ description: 'Initial file content' }),
|
|
116
|
+
}), { description: 'List of files to create' }),
|
|
117
|
+
});
|
|
118
|
+
const AppendParams = Type.Object({
|
|
119
|
+
path: Type.String({ description: 'Relative file path' }),
|
|
120
|
+
content: Type.String({ description: 'Content to append' }),
|
|
121
|
+
});
|
|
122
|
+
const InsertHeadingParams = Type.Object({
|
|
123
|
+
path: Type.String({ description: 'Relative file path' }),
|
|
124
|
+
heading: Type.String({ description: 'Heading text to find (e.g. "## Tasks" or just "Tasks")' }),
|
|
125
|
+
content: Type.String({ description: 'Content to insert after the heading' }),
|
|
126
|
+
});
|
|
127
|
+
const UpdateSectionParams = Type.Object({
|
|
128
|
+
path: Type.String({ description: 'Relative file path' }),
|
|
129
|
+
heading: Type.String({ description: 'Heading text to find (e.g. "## Status")' }),
|
|
130
|
+
content: Type.String({ description: 'New content for the section' }),
|
|
131
|
+
});
|
|
132
|
+
const EditLinesParams = Type.Object({
|
|
133
|
+
path: Type.String({ description: 'Relative file path' }),
|
|
134
|
+
start_line: Type.Number({ description: '1-indexed line number to start replacing' }),
|
|
135
|
+
end_line: Type.Number({ description: '1-indexed line number to stop replacing (inclusive)' }),
|
|
136
|
+
content: Type.String({ description: 'New content to insert in place of those lines' }),
|
|
137
|
+
});
|
|
138
|
+
const RenameParams = Type.Object({
|
|
139
|
+
path: Type.String({ description: 'Current relative file path' }),
|
|
140
|
+
new_name: Type.String({ description: 'New filename (no path separators, e.g. "new-name.md")' }),
|
|
141
|
+
});
|
|
142
|
+
const MoveParams = Type.Object({
|
|
143
|
+
from_path: Type.String({ description: 'Current relative file path' }),
|
|
144
|
+
to_path: Type.String({ description: 'New relative file path' }),
|
|
145
|
+
});
|
|
146
|
+
const HistoryParams = Type.Object({
|
|
147
|
+
path: Type.String({ description: 'Relative file path' }),
|
|
148
|
+
limit: Type.Optional(Type.Number({ description: 'Number of commits to return (default 10)', minimum: 1, maximum: 50 })),
|
|
149
|
+
});
|
|
150
|
+
const FileAtVersionParams = Type.Object({
|
|
151
|
+
path: Type.String({ description: 'Relative file path' }),
|
|
152
|
+
commit: Type.String({ description: 'Git commit hash (full or abbreviated)' }),
|
|
153
|
+
});
|
|
154
|
+
const CsvAppendParams = Type.Object({
|
|
155
|
+
path: Type.String({ description: 'Relative path to .csv file' }),
|
|
156
|
+
row: Type.Array(Type.String(), { description: 'Array of cell values for the new row' }),
|
|
157
|
+
});
|
|
158
|
+
const DreamingParams = Type.Object({
|
|
159
|
+
space: Type.Optional(Type.String({ description: 'Optional space name to scope the Dreaming run (e.g. "Projects"). Omit for full KB scan.' })),
|
|
160
|
+
dryRun: Type.Optional(Type.Boolean({ description: 'When true, return proposals without writing .mindos/dreaming artifacts.' })),
|
|
161
|
+
});
|
|
162
|
+
const LoadSkillParams = Type.Object({
|
|
163
|
+
name: Type.String({ description: 'Skill name, e.g. "mindos" or "context7"' }),
|
|
164
|
+
});
|
|
165
|
+
// ─── Tool name sets (shared across hosts) ────────────────────────────────────
|
|
166
|
+
// Write-operation tool names — used by beforeToolCall for write-protection
|
|
167
|
+
export const WRITE_TOOLS = new Set(MINDOS_WRITE_TOOL_NAMES);
|
|
168
|
+
/** Tool names sufficient for the "organize uploaded files" task. */
|
|
169
|
+
export const ORGANIZE_TOOL_NAMES = new Set(MINDOS_ORGANIZE_KB_TOOL_NAMES);
|
|
170
|
+
/**
|
|
171
|
+
* Read-only tool set for Chat mode.
|
|
172
|
+
*
|
|
173
|
+
* Allows searching and reading the knowledge base + web access,
|
|
174
|
+
* but blocks all write operations. Extensible: add tool names here
|
|
175
|
+
* to grant more read-only capabilities to Chat mode.
|
|
176
|
+
*/
|
|
177
|
+
export const CHAT_TOOL_NAMES = new Set(MINDOS_CHAT_KB_TOOL_NAMES);
|
|
178
|
+
export function createMindosKbToolkit(host) {
|
|
179
|
+
const knowledgeBaseTools = buildMindosKnowledgeBaseTools(host);
|
|
180
|
+
function getToolsForPolicy(policy) {
|
|
181
|
+
const kbToolNameSet = getMindosKbToolNameSet(policy);
|
|
182
|
+
const baseTools = kbToolNameSet
|
|
183
|
+
? knowledgeBaseTools.filter(t => kbToolNameSet.has(t.name))
|
|
184
|
+
: [...knowledgeBaseTools];
|
|
185
|
+
if (policy.toolScope.a2aDelegation && host.delegationTools?.a2a) {
|
|
186
|
+
baseTools.push(...host.delegationTools.a2a);
|
|
187
|
+
}
|
|
188
|
+
if (policy.toolScope.acpDelegation && host.delegationTools?.acp) {
|
|
189
|
+
baseTools.push(...host.delegationTools.acp);
|
|
190
|
+
}
|
|
191
|
+
// IM tools are provided by the host's im extension via pi.registerTool().
|
|
192
|
+
// MCP tools are provided by pi-mcp-adapter and included by the framework.
|
|
193
|
+
return baseTools;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
knowledgeBaseTools,
|
|
197
|
+
getToolsForPolicy,
|
|
198
|
+
getOrganizeTools: () => getToolsForPolicy(createMindosAgentPermissionPolicy('organize')),
|
|
199
|
+
getChatTools: () => getToolsForPolicy(createMindosAgentPermissionPolicy('chat')),
|
|
200
|
+
getRequestScopedTools: () => getToolsForPolicy(createMindosAgentPermissionPolicy('agent')),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// ─── Tool Definitions (AgentTool interface) ─────────────────────────────────
|
|
204
|
+
export function buildMindosKnowledgeBaseTools(host) {
|
|
205
|
+
const { files } = host;
|
|
206
|
+
/** Safe read — returns empty string if file doesn't exist */
|
|
207
|
+
function safeReadContent(filePath) {
|
|
208
|
+
try {
|
|
209
|
+
return files.getFileContent(filePath);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return '';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function writeLock(operation, filePath, fn) {
|
|
216
|
+
return withAgentFileWriteLock({ operation, filePath }, fn);
|
|
217
|
+
}
|
|
218
|
+
/** Build a compact diff summary for tool output. Max 30 diff lines to avoid bloating agent context. */
|
|
219
|
+
async function buildDiffSummaryAsync(before, after) {
|
|
220
|
+
if (before === after)
|
|
221
|
+
return '';
|
|
222
|
+
const beforeLines = before.split('\n').length;
|
|
223
|
+
const afterLines = after.split('\n').length;
|
|
224
|
+
// For very large files, skip sync LCS (O(n*m) would block) and offload to the host worker.
|
|
225
|
+
if (beforeLines <= 2000 && afterLines <= 2000) {
|
|
226
|
+
return formatDiff(buildLineDiff(before, after));
|
|
227
|
+
}
|
|
228
|
+
const raw = host.computeDiffAsync ? await host.computeDiffAsync(before, after) : null;
|
|
229
|
+
if (!raw) {
|
|
230
|
+
// Worker failed/timed out — fallback to line count summary
|
|
231
|
+
const added = Math.max(0, afterLines - beforeLines);
|
|
232
|
+
const removed = Math.max(0, beforeLines - afterLines);
|
|
233
|
+
return `(~+${added} ~−${removed}, ${afterLines} lines total)\n\n--- changes ---\n (diff timed out)`;
|
|
234
|
+
}
|
|
235
|
+
return formatDiff(raw);
|
|
236
|
+
}
|
|
237
|
+
return [
|
|
238
|
+
{
|
|
239
|
+
name: 'list_files',
|
|
240
|
+
label: 'List Files',
|
|
241
|
+
description: 'List files in the knowledge base as an indented tree. Directories beyond `depth` show "... (N items)". Pass `path` to list only a subdirectory, or `depth` to control how deep to expand (default 3).',
|
|
242
|
+
parameters: ListFilesParams,
|
|
243
|
+
execute: safeExecute(async (_id, params) => {
|
|
244
|
+
const { path: subdir, depth: maxDepth } = params;
|
|
245
|
+
const tree = files.getFileTree();
|
|
246
|
+
if (tree.length === 0 && !subdir) {
|
|
247
|
+
const root = files.getMindRoot();
|
|
248
|
+
return textResult(`(empty — no .md or .csv files found under mind_root: ${root})`);
|
|
249
|
+
}
|
|
250
|
+
const limit = maxDepth ?? 3;
|
|
251
|
+
const lines = [];
|
|
252
|
+
function walk(nodes, depth) {
|
|
253
|
+
for (const n of nodes) {
|
|
254
|
+
lines.push(' '.repeat(depth) + (n.type === 'directory' ? `${n.name}/` : n.name));
|
|
255
|
+
if (n.type === 'directory' && Array.isArray(n.children)) {
|
|
256
|
+
if (depth + 1 < limit) {
|
|
257
|
+
walk(n.children, depth + 1);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
lines.push(' '.repeat(depth + 1) + `... (${n.children.length} items)`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (subdir) {
|
|
266
|
+
const segments = subdir.replace(/[\\/]+$/, '').split(/[/\\]/).filter(Boolean);
|
|
267
|
+
let current = tree;
|
|
268
|
+
for (const seg of segments) {
|
|
269
|
+
const found = current.find(n => n.name === seg && n.type === 'directory');
|
|
270
|
+
if (!found || !Array.isArray(found.children)) {
|
|
271
|
+
return textResult(`Directory not found: ${subdir}`);
|
|
272
|
+
}
|
|
273
|
+
current = found.children;
|
|
274
|
+
}
|
|
275
|
+
walk(current, 0);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
walk(tree, 0);
|
|
279
|
+
}
|
|
280
|
+
return textResult(lines.length > 0 ? lines.join('\n') : '(empty directory)');
|
|
281
|
+
}),
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'read_file',
|
|
285
|
+
label: 'Read File',
|
|
286
|
+
description: 'Read the content of a file by its relative path. Always read a file before modifying it. If the file is too large, it will be truncated. Use read_file_chunk to read specific parts of large files.',
|
|
287
|
+
parameters: PathParam,
|
|
288
|
+
execute: safeExecute(async (_id, params) => {
|
|
289
|
+
return textResult(truncate(files.getFileContent(params.path)));
|
|
290
|
+
}),
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: 'read_file_chunk',
|
|
294
|
+
label: 'Read File Chunk',
|
|
295
|
+
description: 'Read a specific range of lines from a file. Highly recommended for reading large files that were truncated by read_file.',
|
|
296
|
+
parameters: ReadFileChunkParams,
|
|
297
|
+
execute: safeExecute(async (_id, params) => {
|
|
298
|
+
const content = files.getFileContent(params.path);
|
|
299
|
+
const lines = content.split('\n');
|
|
300
|
+
const start = Math.max(1, params.start_line);
|
|
301
|
+
const end = Math.min(lines.length, params.end_line);
|
|
302
|
+
if (start > end) {
|
|
303
|
+
return textResult(`Error: start_line (${start}) is greater than end_line (${end}) or file has fewer lines.`);
|
|
304
|
+
}
|
|
305
|
+
// Prefix each line with its line number (1-indexed)
|
|
306
|
+
const pad = String(lines.length).length;
|
|
307
|
+
const chunk = lines
|
|
308
|
+
.slice(start - 1, end)
|
|
309
|
+
.map((l, i) => `${String(start + i).padStart(pad, ' ')} | ${l}`)
|
|
310
|
+
.join('\n');
|
|
311
|
+
return textResult(`Showing lines ${start} to ${end} of ${lines.length}:\n\n${chunk}`);
|
|
312
|
+
}),
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: 'search',
|
|
316
|
+
label: 'Search',
|
|
317
|
+
description: 'Full-text search across all files in the knowledge base. Returns matching files with context snippets.',
|
|
318
|
+
parameters: QueryParam,
|
|
319
|
+
execute: safeExecute(async (_id, params) => {
|
|
320
|
+
const results = await host.hybridSearch(files.getMindRoot(), params.query);
|
|
321
|
+
if (results.length === 0)
|
|
322
|
+
return textResult('No results found.');
|
|
323
|
+
return textResult(results.map(r => `- **${r.path}** (score: ${r.score.toFixed(1)}): ${r.snippet}`).join('\n'));
|
|
324
|
+
}),
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: 'load_skill',
|
|
328
|
+
label: 'Load Skill',
|
|
329
|
+
description: 'Load the full content of a specific skill by name. Available skills are listed in the system prompt under <available_skills>.',
|
|
330
|
+
parameters: LoadSkillParams,
|
|
331
|
+
execute: safeExecute(async (_id, params) => {
|
|
332
|
+
const content = await host.readSkillContent(params.name);
|
|
333
|
+
if (!content)
|
|
334
|
+
return textResult(`Skill not found: ${params.name}`);
|
|
335
|
+
return textResult(truncate(content));
|
|
336
|
+
}),
|
|
337
|
+
},
|
|
338
|
+
// web_search and fetch_content are provided by the pi-web-access extension
|
|
339
|
+
// (registered via additionalExtensionPaths in the host's route/headless wiring)
|
|
340
|
+
{
|
|
341
|
+
name: 'get_recent',
|
|
342
|
+
label: 'Recent Files',
|
|
343
|
+
description: 'Get the most recently modified files in the knowledge base.',
|
|
344
|
+
parameters: LimitParam,
|
|
345
|
+
execute: safeExecute(async (_id, params) => {
|
|
346
|
+
const recent = files.getRecentlyModified(params.limit ?? 10);
|
|
347
|
+
return textResult(recent.map(f => `- ${f.path} (${new Date(f.mtime).toISOString()})`).join('\n'));
|
|
348
|
+
}),
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'write_file',
|
|
352
|
+
label: 'Write File',
|
|
353
|
+
description: 'Overwrite the entire content of an existing file. Use read_file first to see current content. Prefer update_section or insert_after_heading for partial edits.',
|
|
354
|
+
parameters: WriteFileParams,
|
|
355
|
+
execute: safeExecute(async (_id, params) => {
|
|
356
|
+
return writeLock('write_file', params.path, async () => {
|
|
357
|
+
const before = safeReadContent(params.path);
|
|
358
|
+
files.saveFileContent(params.path, params.content);
|
|
359
|
+
const diff = await buildDiffSummaryAsync(before, params.content);
|
|
360
|
+
appendFileChangedEvent({
|
|
361
|
+
path: params.path,
|
|
362
|
+
action: 'updated',
|
|
363
|
+
summary: `Updated ${params.path}`,
|
|
364
|
+
});
|
|
365
|
+
return textResult(`File written: ${params.path}${diff ? ' ' + diff : ''}`);
|
|
366
|
+
});
|
|
367
|
+
}),
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: 'create_file',
|
|
371
|
+
label: 'Create File',
|
|
372
|
+
description: 'Create a new file. Only .md and .csv files are allowed. Parent directories are created automatically. Does NOT create Space scaffolding (INSTRUCTION.md/README.md). Use create_space to create a Space.',
|
|
373
|
+
parameters: CreateFileParams,
|
|
374
|
+
execute: safeExecute(async (_id, params) => {
|
|
375
|
+
return writeLock('create_file', params.path, () => {
|
|
376
|
+
const content = params.content ?? '';
|
|
377
|
+
files.createFile(params.path, content);
|
|
378
|
+
appendFileChangedEvent({
|
|
379
|
+
path: params.path,
|
|
380
|
+
action: 'created',
|
|
381
|
+
summary: `Created ${params.path}`,
|
|
382
|
+
});
|
|
383
|
+
const lineCount = content.split('\n').length;
|
|
384
|
+
return textResult(`File created: ${params.path} (+${lineCount})\n\n--- changes ---\n${content.split('\n').slice(0, 30).map(l => '+ ' + l).join('\n')}${lineCount > 30 ? '\n... (truncated)' : ''}`);
|
|
385
|
+
});
|
|
386
|
+
}),
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: 'batch_create_files',
|
|
390
|
+
label: 'Batch Create Files',
|
|
391
|
+
description: 'Create multiple new files in a single operation. Highly recommended when scaffolding new features or projects.',
|
|
392
|
+
parameters: BatchCreateFileParams,
|
|
393
|
+
execute: safeExecute(async (_id, params) => {
|
|
394
|
+
return withAgentFileWriteLocks(params.files.map((file) => ({ operation: 'batch_create_files', filePath: file.path })), () => {
|
|
395
|
+
const created = [];
|
|
396
|
+
const errors = [];
|
|
397
|
+
for (const file of params.files) {
|
|
398
|
+
try {
|
|
399
|
+
files.createFile(file.path, file.content);
|
|
400
|
+
created.push(file.path);
|
|
401
|
+
appendFileChangedEvent({
|
|
402
|
+
path: file.path,
|
|
403
|
+
action: 'created',
|
|
404
|
+
summary: `Created ${file.path}`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
catch (e) {
|
|
408
|
+
errors.push(`${file.path}: ${formatToolError(e)}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
let msg = `Batch creation complete.\nCreated ${created.length} files: ${created.join(', ')}`;
|
|
412
|
+
if (errors.length > 0)
|
|
413
|
+
msg += `\n\nFailed to create ${errors.length} files:\n${errors.join('\n')}`;
|
|
414
|
+
return textResult(msg);
|
|
415
|
+
});
|
|
416
|
+
}),
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: 'append_to_file',
|
|
420
|
+
label: 'Append to File',
|
|
421
|
+
description: 'Append text to the end of an existing file. A blank line separator is added automatically.',
|
|
422
|
+
parameters: AppendParams,
|
|
423
|
+
execute: safeExecute(async (_id, params) => {
|
|
424
|
+
return writeLock('append_to_file', params.path, async () => {
|
|
425
|
+
const before = safeReadContent(params.path);
|
|
426
|
+
files.appendToFile(params.path, params.content);
|
|
427
|
+
const after = safeReadContent(params.path);
|
|
428
|
+
const diff = await buildDiffSummaryAsync(before, after);
|
|
429
|
+
appendFileChangedEvent({
|
|
430
|
+
path: params.path,
|
|
431
|
+
action: 'updated',
|
|
432
|
+
summary: `Appended to ${params.path}`,
|
|
433
|
+
});
|
|
434
|
+
return textResult(`Content appended to: ${params.path}${diff ? ' ' + diff : ''}`);
|
|
435
|
+
});
|
|
436
|
+
}),
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: 'insert_after_heading',
|
|
440
|
+
label: 'Insert After Heading',
|
|
441
|
+
description: 'Insert content right after a Markdown heading. Useful for adding items under a specific section. If heading matches fail, use edit_lines instead.',
|
|
442
|
+
parameters: InsertHeadingParams,
|
|
443
|
+
execute: safeExecute(async (_id, params) => {
|
|
444
|
+
return writeLock('insert_after_heading', params.path, async () => {
|
|
445
|
+
const before = safeReadContent(params.path);
|
|
446
|
+
files.insertAfterHeading(params.path, params.heading, params.content);
|
|
447
|
+
const after = safeReadContent(params.path);
|
|
448
|
+
const diff = await buildDiffSummaryAsync(before, after);
|
|
449
|
+
appendFileChangedEvent({
|
|
450
|
+
path: params.path,
|
|
451
|
+
action: 'updated',
|
|
452
|
+
summary: `Inserted content in ${params.path}`,
|
|
453
|
+
});
|
|
454
|
+
return textResult(`Content inserted after heading "${params.heading}" in ${params.path}${diff ? ' ' + diff : ''}`);
|
|
455
|
+
});
|
|
456
|
+
}),
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: 'update_section',
|
|
460
|
+
label: 'Update Section',
|
|
461
|
+
description: 'Replace the content of a Markdown section identified by its heading. The section spans from the heading to the next heading of equal or higher level. If heading matches fail, use edit_lines instead.',
|
|
462
|
+
parameters: UpdateSectionParams,
|
|
463
|
+
execute: safeExecute(async (_id, params) => {
|
|
464
|
+
return writeLock('update_section', params.path, async () => {
|
|
465
|
+
const before = safeReadContent(params.path);
|
|
466
|
+
files.updateSection(params.path, params.heading, params.content);
|
|
467
|
+
const after = safeReadContent(params.path);
|
|
468
|
+
const diff = await buildDiffSummaryAsync(before, after);
|
|
469
|
+
appendFileChangedEvent({
|
|
470
|
+
path: params.path,
|
|
471
|
+
action: 'updated',
|
|
472
|
+
summary: `Updated section in ${params.path}`,
|
|
473
|
+
});
|
|
474
|
+
return textResult(`Section "${params.heading}" updated in ${params.path}${diff ? ' ' + diff : ''}`);
|
|
475
|
+
});
|
|
476
|
+
}),
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: 'edit_lines',
|
|
480
|
+
label: 'Edit Lines',
|
|
481
|
+
description: 'Replace a specific range of lines with new content. Extremely reliable for precise edits. You must know the exact line numbers (use read_file_chunk to get them).',
|
|
482
|
+
parameters: EditLinesParams,
|
|
483
|
+
execute: safeExecute(async (_id, params) => {
|
|
484
|
+
const { path: fp, start_line, end_line, content } = params;
|
|
485
|
+
const start = Math.max(0, start_line - 1);
|
|
486
|
+
const end = Math.max(0, end_line - 1);
|
|
487
|
+
return writeLock('edit_lines', fp, async () => {
|
|
488
|
+
const before = safeReadContent(fp);
|
|
489
|
+
const mindRoot = files.getMindRoot();
|
|
490
|
+
await files.updateLines(mindRoot, fp, start, end, content.split('\n'));
|
|
491
|
+
const after = safeReadContent(fp);
|
|
492
|
+
const diff = await buildDiffSummaryAsync(before, after);
|
|
493
|
+
appendFileChangedEvent({
|
|
494
|
+
path: fp,
|
|
495
|
+
action: 'updated',
|
|
496
|
+
summary: `Edited lines ${start_line}-${end_line} in ${fp}`,
|
|
497
|
+
});
|
|
498
|
+
return textResult(`Lines ${start_line}-${end_line} replaced in ${fp}${diff ? ' ' + diff : ''}`);
|
|
499
|
+
});
|
|
500
|
+
}),
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'delete_file',
|
|
504
|
+
label: 'Delete File',
|
|
505
|
+
description: 'Delete a file from the knowledge base. The file is moved to trash and can be recovered within 30 days.',
|
|
506
|
+
parameters: PathParam,
|
|
507
|
+
execute: safeExecute(async (_id, params) => {
|
|
508
|
+
return writeLock('delete_file', params.path, () => {
|
|
509
|
+
const meta = files.moveToTrashFile(params.path);
|
|
510
|
+
appendFileChangedEvent({
|
|
511
|
+
path: params.path,
|
|
512
|
+
action: 'deleted',
|
|
513
|
+
summary: `Moved ${params.path} to trash`,
|
|
514
|
+
});
|
|
515
|
+
return textResult(`Moved to trash: ${params.path} (recoverable for 30 days, trashId: ${meta.id})`);
|
|
516
|
+
});
|
|
517
|
+
}),
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
name: 'rename_file',
|
|
521
|
+
label: 'Rename File',
|
|
522
|
+
description: 'Rename a file within its current directory. Only the filename changes, not the directory.',
|
|
523
|
+
parameters: RenameParams,
|
|
524
|
+
execute: safeExecute(async (_id, params) => {
|
|
525
|
+
return writeLock('rename_file', params.path, () => {
|
|
526
|
+
const newPath = files.renameFile(params.path, params.new_name);
|
|
527
|
+
appendFileChangedEvent({
|
|
528
|
+
path: newPath,
|
|
529
|
+
action: 'renamed',
|
|
530
|
+
summary: `Renamed ${params.path} to ${newPath}`,
|
|
531
|
+
});
|
|
532
|
+
return textResult(`File renamed: ${params.path} → ${newPath}`);
|
|
533
|
+
});
|
|
534
|
+
}),
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
name: 'move_file',
|
|
538
|
+
label: 'Move File',
|
|
539
|
+
description: 'Move a file to a new location. Also returns any files that had backlinks affected by the move.',
|
|
540
|
+
parameters: MoveParams,
|
|
541
|
+
execute: safeExecute(async (_id, params) => {
|
|
542
|
+
return withAgentFileWriteLocks([
|
|
543
|
+
{ operation: 'move_file', filePath: params.from_path },
|
|
544
|
+
{ operation: 'move_file', filePath: params.to_path },
|
|
545
|
+
], () => {
|
|
546
|
+
const result = files.moveFile(params.from_path, params.to_path);
|
|
547
|
+
const affected = result.affectedFiles.length > 0
|
|
548
|
+
? `\nAffected backlinks in: ${result.affectedFiles.join(', ')}`
|
|
549
|
+
: '';
|
|
550
|
+
appendFileChangedEvent({
|
|
551
|
+
path: result.newPath,
|
|
552
|
+
action: 'renamed',
|
|
553
|
+
summary: `Moved ${params.from_path} to ${result.newPath}`,
|
|
554
|
+
});
|
|
555
|
+
return textResult(`File moved: ${params.from_path} → ${result.newPath}${affected}`);
|
|
556
|
+
});
|
|
557
|
+
}),
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
name: 'get_backlinks',
|
|
561
|
+
label: 'Backlinks',
|
|
562
|
+
description: 'Find all files that reference a given file path. Useful for understanding connections between notes.',
|
|
563
|
+
parameters: PathParam,
|
|
564
|
+
execute: safeExecute(async (_id, params) => {
|
|
565
|
+
const backlinks = files.findBacklinks(params.path);
|
|
566
|
+
if (backlinks.length === 0)
|
|
567
|
+
return textResult(`No backlinks found for: ${params.path}`);
|
|
568
|
+
return textResult(backlinks.map(b => `- **${b.source}** (L${b.line}): ${b.context}`).join('\n'));
|
|
569
|
+
}),
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: 'get_history',
|
|
573
|
+
label: 'History',
|
|
574
|
+
description: 'Get git commit history for a file. Shows recent commits that modified this file.',
|
|
575
|
+
parameters: HistoryParams,
|
|
576
|
+
execute: safeExecute(async (_id, params) => {
|
|
577
|
+
const commits = files.gitLog(params.path, params.limit ?? 10);
|
|
578
|
+
if (commits.length === 0)
|
|
579
|
+
return textResult(`No git history found for: ${params.path}`);
|
|
580
|
+
return textResult(commits.map(c => `- \`${c.hash.slice(0, 7)}\` ${c.date} — ${c.message} (${c.author})`).join('\n'));
|
|
581
|
+
}),
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: 'get_file_at_version',
|
|
585
|
+
label: 'File at Version',
|
|
586
|
+
description: 'Read the content of a file at a specific git commit. Use get_history first to find commit hashes.',
|
|
587
|
+
parameters: FileAtVersionParams,
|
|
588
|
+
execute: safeExecute(async (_id, params) => {
|
|
589
|
+
return textResult(truncate(files.gitShowFile(params.path, params.commit)));
|
|
590
|
+
}),
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: 'append_csv',
|
|
594
|
+
label: 'Append CSV Row',
|
|
595
|
+
description: 'Append a row to a CSV file. Values are automatically escaped per RFC 4180.',
|
|
596
|
+
parameters: CsvAppendParams,
|
|
597
|
+
execute: safeExecute(async (_id, params) => {
|
|
598
|
+
return writeLock('append_csv', params.path, () => {
|
|
599
|
+
const result = files.appendCsvRow(params.path, params.row);
|
|
600
|
+
appendFileChangedEvent({
|
|
601
|
+
path: params.path,
|
|
602
|
+
action: 'updated',
|
|
603
|
+
summary: `Appended CSV row to ${params.path}`,
|
|
604
|
+
});
|
|
605
|
+
return textResult(`Row appended to ${params.path} (now ${result.newRowCount} rows)`);
|
|
606
|
+
});
|
|
607
|
+
}),
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
name: 'lint',
|
|
611
|
+
label: 'Knowledge Base Health Check',
|
|
612
|
+
description: 'Run a health check on the knowledge base. Detects orphan files, stale files, broken links, and empty files. Returns a health score (0-100) and detailed issue lists.',
|
|
613
|
+
parameters: Type.Object({
|
|
614
|
+
space: Type.Optional(Type.String({ description: 'Optional space name to scope the analysis (e.g. "Projects"). Omit for full KB scan.' })),
|
|
615
|
+
}),
|
|
616
|
+
execute: safeExecute(async (_id, params) => {
|
|
617
|
+
if (!host.runLint) {
|
|
618
|
+
return textResult('Error: the lint tool is not available in this host.');
|
|
619
|
+
}
|
|
620
|
+
const report = await host.runLint(files.getMindRoot(), params.space);
|
|
621
|
+
const lines = [
|
|
622
|
+
`## KB Health Check — Score: ${report.healthScore}/100`,
|
|
623
|
+
`Scope: ${report.scope} | Files: ${report.stats.totalFiles}`,
|
|
624
|
+
'',
|
|
625
|
+
];
|
|
626
|
+
if (report.orphans.length > 0) {
|
|
627
|
+
lines.push(`### Orphan Files (${report.orphans.length})`);
|
|
628
|
+
for (const o of report.orphans.slice(0, 20))
|
|
629
|
+
lines.push(`- ${o.path}`);
|
|
630
|
+
if (report.orphans.length > 20)
|
|
631
|
+
lines.push(`... and ${report.orphans.length - 20} more`);
|
|
632
|
+
lines.push('');
|
|
633
|
+
}
|
|
634
|
+
if (report.brokenLinks.length > 0) {
|
|
635
|
+
lines.push(`### Broken Links (${report.brokenLinks.length})`);
|
|
636
|
+
for (const b of report.brokenLinks.slice(0, 20))
|
|
637
|
+
lines.push(`- ${b.source}:${b.line} → [[${b.target}]]`);
|
|
638
|
+
if (report.brokenLinks.length > 20)
|
|
639
|
+
lines.push(`... and ${report.brokenLinks.length - 20} more`);
|
|
640
|
+
lines.push('');
|
|
641
|
+
}
|
|
642
|
+
if (report.stale.length > 0) {
|
|
643
|
+
lines.push(`### Stale Files (${report.stale.length})`);
|
|
644
|
+
for (const s of report.stale.slice(0, 20))
|
|
645
|
+
lines.push(`- ${s.path} (${s.daysSinceUpdate}d ago)`);
|
|
646
|
+
if (report.stale.length > 20)
|
|
647
|
+
lines.push(`... and ${report.stale.length - 20} more`);
|
|
648
|
+
lines.push('');
|
|
649
|
+
}
|
|
650
|
+
if (report.empty.length > 0) {
|
|
651
|
+
lines.push(`### Empty Files (${report.empty.length})`);
|
|
652
|
+
for (const e of report.empty.slice(0, 20))
|
|
653
|
+
lines.push(`- ${e}`);
|
|
654
|
+
if (report.empty.length > 20)
|
|
655
|
+
lines.push(`... and ${report.empty.length - 20} more`);
|
|
656
|
+
lines.push('');
|
|
657
|
+
}
|
|
658
|
+
if (report.healthScore === 100)
|
|
659
|
+
lines.push('All clear — your knowledge base is in great shape!');
|
|
660
|
+
return textResult(lines.join('\n'));
|
|
661
|
+
}),
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
name: 'dreaming',
|
|
665
|
+
label: 'Run Dreaming',
|
|
666
|
+
description: 'Run a conservative background knowledge-maintenance pass. It captures local signals, groups them into maintenance themes, and writes review-first proposals under .mindos/dreaming without changing user notes.',
|
|
667
|
+
parameters: DreamingParams,
|
|
668
|
+
execute: safeExecute(async (_id, params) => {
|
|
669
|
+
if (!host.runDreaming) {
|
|
670
|
+
return textResult('Error: the dreaming tool is not available in this host.');
|
|
671
|
+
}
|
|
672
|
+
const result = await host.runDreaming(files.getMindRoot(), {
|
|
673
|
+
space: params.space,
|
|
674
|
+
writeArtifacts: params.dryRun !== true,
|
|
675
|
+
});
|
|
676
|
+
return textResult(result.report);
|
|
677
|
+
}),
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
name: 'compile',
|
|
681
|
+
label: 'Compile Space Overview',
|
|
682
|
+
description: 'Generate or regenerate a Space overview README using AI. Reads all files in the Space, analyzes their content, and produces a structured summary saved as README.md.',
|
|
683
|
+
parameters: Type.Object({
|
|
684
|
+
space: Type.String({ description: 'Space path to compile (e.g. "Research", "Projects/ML")' }),
|
|
685
|
+
}),
|
|
686
|
+
execute: safeExecute(async (_id, params) => {
|
|
687
|
+
return writeLock('compile', path.posix.join(params.space, 'README.md'), async () => {
|
|
688
|
+
if (!host.compileSpaceOverview) {
|
|
689
|
+
return textResult('Error: the compile tool is not available in this host.');
|
|
690
|
+
}
|
|
691
|
+
const result = await host.compileSpaceOverview(params.space);
|
|
692
|
+
if (!result.ok) {
|
|
693
|
+
return textResult(`Error: ${result.message}`);
|
|
694
|
+
}
|
|
695
|
+
const readmePath = path.posix.join(params.space, 'README.md');
|
|
696
|
+
appendFileChangedEvent({
|
|
697
|
+
path: readmePath,
|
|
698
|
+
action: 'updated',
|
|
699
|
+
summary: `Generated overview ${readmePath}`,
|
|
700
|
+
});
|
|
701
|
+
return textResult(`Overview generated for "${result.stats.spaceName}" (${result.stats.fileCount} files analyzed).\n\nSaved to ${params.space}/README.md`);
|
|
702
|
+
});
|
|
703
|
+
}),
|
|
704
|
+
},
|
|
705
|
+
];
|
|
706
|
+
}
|
|
707
|
+
//# sourceMappingURL=kb-tools.js.map
|