pi-vault-mind 0.8.0 → 0.8.3
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/CHANGELOG.md +20 -0
- package/README.md +44 -44
- package/agents/broadcaster.agent.md +30 -0
- package/agents/heavy-lifter.agent.md +30 -0
- package/agents/manager.agent.md +29 -0
- package/agents/miner.agent.md +29 -0
- package/dist/src/agent-bus/Agent.d.ts +49 -0
- package/dist/src/agent-bus/Agent.js +97 -0
- package/dist/src/agent-bus/AgentLoader.d.ts +33 -0
- package/dist/src/agent-bus/AgentLoader.js +102 -0
- package/dist/src/agent-bus/AgentRegistry.d.ts +17 -0
- package/dist/src/agent-bus/AgentRegistry.js +20 -0
- package/dist/src/agent-bus/LLMProvider.d.ts +36 -0
- package/dist/src/agent-bus/LLMProvider.js +125 -0
- package/dist/src/agent-bus/MessageBus.d.ts +47 -0
- package/dist/src/agent-bus/MessageBus.js +81 -0
- package/dist/src/agent-bus/TaskQueue.d.ts +27 -0
- package/dist/src/agent-bus/TaskQueue.js +57 -0
- package/dist/src/agent-bus/index.d.ts +6 -0
- package/dist/src/agent-bus/index.js +6 -0
- package/dist/src/agents/BroadcasterAgent.d.ts +19 -0
- package/dist/src/agents/BroadcasterAgent.js +70 -0
- package/dist/src/agents/HeavyLifterAgent.d.ts +26 -0
- package/dist/src/agents/HeavyLifterAgent.js +73 -0
- package/dist/src/agents/ManagerAgent.d.ts +20 -0
- package/dist/src/agents/ManagerAgent.js +71 -0
- package/dist/src/agents/MinerAgent.d.ts +19 -0
- package/dist/src/agents/MinerAgent.js +70 -0
- package/dist/src/agents/index.d.ts +4 -0
- package/dist/src/agents/index.js +4 -0
- package/dist/src/auth.d.ts +49 -2
- package/dist/src/auth.js +149 -7
- package/dist/src/autosync.d.ts +1 -1
- package/dist/src/autosync.js +3 -3
- package/dist/src/bridge.d.ts +45 -0
- package/dist/src/bridge.js +189 -0
- package/dist/src/commands.d.ts +1 -1
- package/dist/src/commands.js +419 -295
- package/dist/src/context-capture.d.ts +22 -0
- package/dist/src/context-capture.js +41 -0
- package/dist/src/discover-schema.d.ts +1 -1
- package/dist/src/discover-schema.js +4 -4
- package/dist/src/embed-queue.d.ts +1 -1
- package/dist/src/embed-queue.js +1 -1
- package/dist/src/engine.d.ts +65 -0
- package/dist/src/engine.js +244 -0
- package/dist/src/events.js +1 -1
- package/dist/src/graph.d.ts +3 -3
- package/dist/src/identity-injector.d.ts +15 -0
- package/dist/src/identity-injector.js +102 -0
- package/dist/src/index.js +88 -49
- package/dist/src/intake.js +4 -4
- package/dist/src/lance.d.ts +11 -37
- package/dist/src/lance.js +183 -240
- package/dist/src/modal-client.d.ts +14 -4
- package/dist/src/modal-client.js +50 -34
- package/dist/src/modal-config.d.ts +26 -10
- package/dist/src/modal-config.js +36 -5
- package/dist/src/personalize.d.ts +47 -0
- package/dist/src/personalize.js +352 -0
- package/dist/src/server.d.ts +30 -4
- package/dist/src/server.js +146 -64
- package/dist/src/settings-ui.d.ts +3 -1
- package/dist/src/settings-ui.js +84 -77
- package/dist/src/status.d.ts +25 -0
- package/dist/src/status.js +49 -0
- package/dist/src/sync.d.ts +1 -1
- package/dist/src/sync.js +19 -19
- package/dist/src/tombstone.js +2 -2
- package/dist/src/tools.js +112 -130
- package/dist/src/types.d.ts +86 -18
- package/dist/src/types.js +7 -4
- package/dist/src/utils.d.ts +5 -1
- package/dist/src/utils.js +24 -8
- package/dist/src/vault-tools.d.ts +29 -0
- package/dist/src/vault-tools.js +314 -0
- package/dist/src/vault-writer.d.ts +4 -5
- package/dist/src/vault-writer.js +13 -13
- package/dist/src/vm-handlers.d.ts +15 -0
- package/dist/src/vm-handlers.js +469 -0
- package/dist/src/watcher.d.ts +18 -4
- package/dist/src/watcher.js +109 -98
- package/dist/test/auth.test.js +57 -7
- package/dist/test/bridge-identity.test.js +388 -0
- package/dist/test/discover-schema.test.js +2 -2
- package/dist/test/engine-identity.test.js +119 -0
- package/dist/test/index.test.js +5 -6
- package/dist/test/lance-modal.test.js +7 -7
- package/dist/test/modal-config.test.js +51 -29
- package/dist/test/personalize.test.js +212 -0
- package/dist/test/rest-queue.test.js +1 -1
- package/dist/test/rest-setup.test.js +21 -21
- package/dist/test/{rest-wiki.test.js → rest-vm.test.js} +27 -23
- package/dist/test/server-ws.test.js +4 -4
- package/dist/test/sync.test.js +2 -2
- package/dist/test/tombstone.test.js +1 -1
- package/dist/test/vault-tools.test.js +224 -0
- package/package.json +2 -1
- package/scripts/check-deps.sh +207 -0
- package/scripts/e2e-ws.mjs +4 -4
- package/scripts/modal-e2e-smoke.mjs +1 -1
- package/scripts/setup-test-config.sh +1 -5
- package/scripts/test-slash-commands.mjs +197 -0
- package/skills/.agents/broadcaster.agent.md +31 -0
- package/skills/.agents/heavy-lifter.agent.md +31 -0
- package/skills/.agents/manager.agent.md +30 -0
- package/skills/.agents/miner.agent.md +30 -0
- package/skills/vault-mind/SKILL.md +50 -50
- package/skills/vault-mind/references/tool-reference.md +25 -25
- package/skills/vault-mind-broadcaster/SKILL.md +1 -1
- package/skills/vault-mind-heavy-lifter/SKILL.md +3 -3
- package/skills/vault-mind-manager/SKILL.md +7 -7
- package/skills/vault-mind-miner/SKILL.md +11 -11
- package/skills/vault-mind-setup/SKILL.md +65 -265
- package/skills/vault-mind-setup/references/obsidian-vault-structure.md +6 -6
- package/skills/vault-mind-setup/references/pi-extension-wiring.md +24 -33
- package/skills/vault-mind-setup/references/troubleshooting-tree.md +25 -26
- package/dist/src/auto-index.d.ts +0 -47
- package/dist/src/auto-index.js +0 -122
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { getStatus } from "./lance.js";
|
|
5
|
+
import { expandHome, loadConfig } from "./utils.js";
|
|
6
|
+
/** Resolve the vault path: configured default vault wins, otherwise ctx.cwd. */
|
|
7
|
+
export const resolveVaultPath = (ctx) => {
|
|
8
|
+
const cfg = loadConfig(ctx.cwd);
|
|
9
|
+
const configured = cfg.vaultMind.vaults?.default?.path;
|
|
10
|
+
return configured ? expandHome(configured) : ctx.cwd;
|
|
11
|
+
};
|
|
12
|
+
/** Load current agent configuration files if they exist. */
|
|
13
|
+
export const loadCurrentConfig = (vaultPath) => {
|
|
14
|
+
const systemMdPath = path.join(vaultPath, ".pi", "agent", "system.md");
|
|
15
|
+
const agentsMdPath = path.join(vaultPath, "AGENTS.md");
|
|
16
|
+
return {
|
|
17
|
+
systemMd: fs.existsSync(systemMdPath) ? fs.readFileSync(systemMdPath, "utf-8") : null,
|
|
18
|
+
agentsMd: fs.existsSync(agentsMdPath) ? fs.readFileSync(agentsMdPath, "utf-8") : null,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
const findMdFiles = (dir, maxDepth, currentDepth = 0) => {
|
|
22
|
+
if (currentDepth > maxDepth)
|
|
23
|
+
return [];
|
|
24
|
+
const result = [];
|
|
25
|
+
let entries = [];
|
|
26
|
+
try {
|
|
27
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (entry.name.startsWith("."))
|
|
34
|
+
continue;
|
|
35
|
+
if (entry.name === "node_modules")
|
|
36
|
+
continue;
|
|
37
|
+
const fullPath = path.join(dir, entry.name);
|
|
38
|
+
if (entry.isDirectory()) {
|
|
39
|
+
result.push(...findMdFiles(fullPath, maxDepth, currentDepth + 1));
|
|
40
|
+
}
|
|
41
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
42
|
+
result.push(fullPath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
const normalizeTags = (value) => {
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
return value.map((v) => (typeof v === "string" ? v.trim() : "")).filter((v) => v.length > 0);
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
return value
|
|
53
|
+
.split(/[,\s]+/)
|
|
54
|
+
.map((t) => t.trim())
|
|
55
|
+
.filter((t) => t.length > 0 && !t.startsWith("#"));
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
};
|
|
59
|
+
const extractFrontmatterTags = (content) => {
|
|
60
|
+
try {
|
|
61
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
62
|
+
return normalizeTags(frontmatter.tags);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const inferDomains = (topFolders, commonTags) => {
|
|
69
|
+
const domains = new Set();
|
|
70
|
+
for (const folder of topFolders) {
|
|
71
|
+
if (folder.noteCount > 0)
|
|
72
|
+
domains.add(folder.name);
|
|
73
|
+
}
|
|
74
|
+
for (const tag of commonTags.slice(0, 15)) {
|
|
75
|
+
domains.add(tag.tag);
|
|
76
|
+
}
|
|
77
|
+
return [...domains].slice(0, 12);
|
|
78
|
+
};
|
|
79
|
+
const scanVaultNotes = (vaultPath) => {
|
|
80
|
+
let topEntries = [];
|
|
81
|
+
try {
|
|
82
|
+
topEntries = fs.readdirSync(vaultPath, { withFileTypes: true });
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return { topFolders: [], totalNotes: 0, tagCounts: {} };
|
|
86
|
+
}
|
|
87
|
+
const topFolders = topEntries
|
|
88
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules")
|
|
89
|
+
.map((e) => e.name)
|
|
90
|
+
.sort();
|
|
91
|
+
const tagCounts = {};
|
|
92
|
+
const topFoldersStats = [];
|
|
93
|
+
let totalNotes = 0;
|
|
94
|
+
for (const folder of topFolders) {
|
|
95
|
+
const folderPath = path.join(vaultPath, folder);
|
|
96
|
+
const notePaths = findMdFiles(folderPath, 1);
|
|
97
|
+
topFoldersStats.push({ name: folder, noteCount: notePaths.length });
|
|
98
|
+
totalNotes += notePaths.length;
|
|
99
|
+
for (const notePath of notePaths.slice(0, 30)) {
|
|
100
|
+
try {
|
|
101
|
+
const content = fs.readFileSync(notePath, "utf-8");
|
|
102
|
+
for (const tag of extractFrontmatterTags(content)) {
|
|
103
|
+
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* ignore unreadable notes */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const rootNotes = findMdFiles(vaultPath, 0);
|
|
112
|
+
totalNotes += rootNotes.length;
|
|
113
|
+
return { topFolders: topFoldersStats, totalNotes, tagCounts };
|
|
114
|
+
};
|
|
115
|
+
/** Analyze the vault structure, tags, and LanceDB state. */
|
|
116
|
+
export const analyzeVaultProfile = async (ctx) => {
|
|
117
|
+
const cfg = loadConfig(ctx.cwd);
|
|
118
|
+
const vaultPath = resolveVaultPath(ctx);
|
|
119
|
+
const { topFolders, totalNotes, tagCounts } = scanVaultNotes(vaultPath);
|
|
120
|
+
const commonTags = Object.entries(tagCounts)
|
|
121
|
+
.map(([tag, count]) => ({ tag, count }))
|
|
122
|
+
.sort((a, b) => b.count - a.count)
|
|
123
|
+
.slice(0, 20);
|
|
124
|
+
const domains = inferDomains(topFolders, commonTags);
|
|
125
|
+
let lancedbSummary;
|
|
126
|
+
try {
|
|
127
|
+
const status = await getStatus(cfg.vaultMind.dataDir);
|
|
128
|
+
const tableNames = Object.keys(status.tables);
|
|
129
|
+
const totalRows = Object.values(status.tables).reduce((sum, t) => sum + (typeof t.rows === "number" ? t.rows : 0), 0);
|
|
130
|
+
lancedbSummary = `${tableNames.length} table(s), ${totalRows} row(s)`;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
lancedbSummary = undefined;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
vaultPath,
|
|
137
|
+
topFolders,
|
|
138
|
+
totalNotes,
|
|
139
|
+
commonTags,
|
|
140
|
+
domains,
|
|
141
|
+
lancedbSummary,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const getAssistantText = (message) => message.content
|
|
145
|
+
?.filter((c) => c.type === "text")
|
|
146
|
+
.map((c) => c.text ?? "")
|
|
147
|
+
.join("\n") ?? "";
|
|
148
|
+
const buildPrompt = (profile, currentConfig) => {
|
|
149
|
+
const schemaExample = {
|
|
150
|
+
commentary: "Brief rationale for the suggested personality tuning.",
|
|
151
|
+
files: [
|
|
152
|
+
{
|
|
153
|
+
path: ".pi/agent/system.md",
|
|
154
|
+
action: "update | create | skip",
|
|
155
|
+
content: "Full suggested markdown content (omit or empty when action is skip).",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
path: "AGENTS.md",
|
|
159
|
+
action: "update | create | skip",
|
|
160
|
+
content: "Full suggested markdown content (omit or empty when action is skip).",
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
const lines = [
|
|
165
|
+
"# Vault Personality Tuning",
|
|
166
|
+
"",
|
|
167
|
+
"Analyze the following vault profile and current agent configuration files, then suggest personalized improvements to `.pi/agent/system.md` and `AGENTS.md`.",
|
|
168
|
+
"",
|
|
169
|
+
"## Vault Profile",
|
|
170
|
+
"",
|
|
171
|
+
`- Vault path: ${profile.vaultPath}`,
|
|
172
|
+
`- Total notes: ${profile.totalNotes}`,
|
|
173
|
+
"",
|
|
174
|
+
"### Top-level folders",
|
|
175
|
+
...profile.topFolders.map((f) => `- ${f.name}: ${f.noteCount} notes`),
|
|
176
|
+
"",
|
|
177
|
+
"### Common tags",
|
|
178
|
+
...profile.commonTags.map((t) => `- ${t.tag}: ${t.count}`),
|
|
179
|
+
"",
|
|
180
|
+
"### Primary domains/topics",
|
|
181
|
+
...profile.domains.map((d) => `- ${d}`),
|
|
182
|
+
"",
|
|
183
|
+
profile.lancedbSummary ? `### LanceDB summary\n\n- ${profile.lancedbSummary}\n` : "",
|
|
184
|
+
"",
|
|
185
|
+
"## Current Configuration",
|
|
186
|
+
"",
|
|
187
|
+
"### .pi/agent/system.md",
|
|
188
|
+
"```markdown",
|
|
189
|
+
currentConfig.systemMd ?? "(file does not exist)",
|
|
190
|
+
"```",
|
|
191
|
+
"",
|
|
192
|
+
"### AGENTS.md",
|
|
193
|
+
"```markdown",
|
|
194
|
+
currentConfig.agentsMd ?? "(file does not exist)",
|
|
195
|
+
"```",
|
|
196
|
+
"",
|
|
197
|
+
"## Instructions",
|
|
198
|
+
"",
|
|
199
|
+
"Return a single JSON object (no commentary outside the JSON) with this exact schema:",
|
|
200
|
+
"",
|
|
201
|
+
"```json",
|
|
202
|
+
JSON.stringify(schemaExample, null, 2),
|
|
203
|
+
"```",
|
|
204
|
+
"",
|
|
205
|
+
"Rules:",
|
|
206
|
+
"- If a file does not currently exist, use action: create.",
|
|
207
|
+
"- If a file exists but needs changes, use action: update and provide the full new content.",
|
|
208
|
+
"- If a file is fine as-is, use action: skip and omit content.",
|
|
209
|
+
"- Tailor the voice, priorities, and conventions to the vault's domains and tags.",
|
|
210
|
+
"- Keep files concise and actionable.",
|
|
211
|
+
];
|
|
212
|
+
return lines.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
213
|
+
};
|
|
214
|
+
export const parseSuggestionJson = (text) => {
|
|
215
|
+
const withoutFences = text
|
|
216
|
+
.replace(/```json\s*/gi, "")
|
|
217
|
+
.replace(/```\s*/g, "")
|
|
218
|
+
.trim();
|
|
219
|
+
const start = withoutFences.indexOf("{");
|
|
220
|
+
const end = withoutFences.lastIndexOf("}");
|
|
221
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
222
|
+
throw new Error("No JSON object found in LLM response");
|
|
223
|
+
}
|
|
224
|
+
const parsed = JSON.parse(withoutFences.slice(start, end + 1));
|
|
225
|
+
if (!parsed.files || !Array.isArray(parsed.files)) {
|
|
226
|
+
throw new Error("Missing files array in suggestion JSON");
|
|
227
|
+
}
|
|
228
|
+
const files = parsed.files.map((f) => ({
|
|
229
|
+
path: String(f.path ?? ""),
|
|
230
|
+
current: null,
|
|
231
|
+
proposed: String(f.content ?? ""),
|
|
232
|
+
action: ["create", "update", "skip"].includes(f.action) ? f.action : "skip",
|
|
233
|
+
}));
|
|
234
|
+
return {
|
|
235
|
+
commentary: typeof parsed.commentary === "string" ? parsed.commentary : undefined,
|
|
236
|
+
files,
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
/** Ask the current session model to suggest personalized config updates. */
|
|
240
|
+
export const generateSuggestions = async (pi, profile, currentConfig) => {
|
|
241
|
+
const prompt = buildPrompt(profile, currentConfig);
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
let settled = false;
|
|
244
|
+
const timeout = setTimeout(() => {
|
|
245
|
+
if (!settled) {
|
|
246
|
+
settled = true;
|
|
247
|
+
reject(new Error("Timed out waiting for LLM suggestions"));
|
|
248
|
+
}
|
|
249
|
+
}, 120_000);
|
|
250
|
+
const handler = (event) => {
|
|
251
|
+
if (settled)
|
|
252
|
+
return;
|
|
253
|
+
const lastAssistant = [...event.messages].reverse().find((m) => m.role === "assistant");
|
|
254
|
+
if (!lastAssistant)
|
|
255
|
+
return;
|
|
256
|
+
const text = getAssistantText(lastAssistant);
|
|
257
|
+
try {
|
|
258
|
+
const suggestions = parseSuggestionJson(text);
|
|
259
|
+
settled = true;
|
|
260
|
+
clearTimeout(timeout);
|
|
261
|
+
resolve(suggestions);
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
settled = true;
|
|
265
|
+
clearTimeout(timeout);
|
|
266
|
+
reject(new Error(`Failed to parse LLM response as JSON: ${err.message}`));
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
pi.on("agent_end", handler);
|
|
270
|
+
pi.sendMessage({ customType: "vault-personalize-prompt", content: prompt, display: false }, { triggerTurn: true });
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
/** Format old vs new as a markdown diff block. */
|
|
274
|
+
export const formatDiff = (current, proposed) => {
|
|
275
|
+
const currentLines = current?.split("\n") ?? ["(file does not exist)"];
|
|
276
|
+
const proposedLines = proposed.split("\n");
|
|
277
|
+
const maxLen = Math.max(currentLines.length, proposedLines.length);
|
|
278
|
+
const lines = [];
|
|
279
|
+
for (let i = 0; i < maxLen; i++) {
|
|
280
|
+
const oldLine = currentLines[i];
|
|
281
|
+
const newLine = proposedLines[i];
|
|
282
|
+
if (oldLine === undefined) {
|
|
283
|
+
lines.push(`+ ${newLine}`);
|
|
284
|
+
}
|
|
285
|
+
else if (newLine === undefined) {
|
|
286
|
+
lines.push(`- ${oldLine}`);
|
|
287
|
+
}
|
|
288
|
+
else if (oldLine !== newLine) {
|
|
289
|
+
lines.push(`- ${oldLine}`);
|
|
290
|
+
lines.push(`+ ${newLine}`);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
lines.push(` ${oldLine}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return ["```diff", ...lines, "```"].join("\n");
|
|
297
|
+
};
|
|
298
|
+
const resolveCurrentContent = (filePath, currentConfig) => {
|
|
299
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
300
|
+
if (normalized === ".pi/agent/system.md" || normalized.endsWith("/.pi/agent/system.md")) {
|
|
301
|
+
return currentConfig.systemMd;
|
|
302
|
+
}
|
|
303
|
+
if (normalized === "AGENTS.md" || normalized.endsWith("/AGENTS.md")) {
|
|
304
|
+
return currentConfig.agentsMd;
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
};
|
|
308
|
+
/** Present each suggestion diff to the user and write approved files. */
|
|
309
|
+
export const presentAndApplyDiff = async (suggestions, ctx, currentConfig) => {
|
|
310
|
+
const approved = [];
|
|
311
|
+
const rejected = [];
|
|
312
|
+
const vaultPath = resolveVaultPath(ctx);
|
|
313
|
+
for (const file of suggestions.files) {
|
|
314
|
+
if (file.action === "skip" || !file.proposed) {
|
|
315
|
+
rejected.push(file.path);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const current = resolveCurrentContent(file.path, currentConfig);
|
|
319
|
+
const diff = formatDiff(current, file.proposed);
|
|
320
|
+
const title = `${file.action === "create" ? "Create" : "Update"} ${file.path}?`;
|
|
321
|
+
const message = [suggestions.commentary, diff].filter(Boolean).join("\n\n");
|
|
322
|
+
const ok = await ctx.ui.confirm(title, message);
|
|
323
|
+
if (ok) {
|
|
324
|
+
const fullPath = path.isAbsolute(file.path) ? file.path : path.join(vaultPath, file.path);
|
|
325
|
+
const dir = path.dirname(fullPath);
|
|
326
|
+
if (!fs.existsSync(dir))
|
|
327
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
328
|
+
fs.writeFileSync(fullPath, file.proposed, "utf-8");
|
|
329
|
+
approved.push(file.path);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
rejected.push(file.path);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
ctx.ui.notify(`Personalization complete. Approved ${approved.length} file(s), rejected/skipped ${rejected.length}.`, "info");
|
|
336
|
+
return { approved, rejected };
|
|
337
|
+
};
|
|
338
|
+
/** Orchestrate /vm personalize: analyze, suggest, confirm, apply. */
|
|
339
|
+
export const runPersonalize = async (ctx, pi) => {
|
|
340
|
+
const vaultPath = resolveVaultPath(ctx);
|
|
341
|
+
const currentConfig = loadCurrentConfig(vaultPath);
|
|
342
|
+
ctx.ui.notify("Analyzing vault profile...", "info");
|
|
343
|
+
const profile = await analyzeVaultProfile(ctx);
|
|
344
|
+
ctx.ui.notify(`Vault profile: ${profile.totalNotes} notes across ${profile.topFolders.length} top-level folders, ${profile.commonTags.length} unique tags.`, "info");
|
|
345
|
+
try {
|
|
346
|
+
const suggestions = await generateSuggestions(pi, profile, currentConfig);
|
|
347
|
+
await presentAndApplyDiff(suggestions, ctx, currentConfig);
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
ctx.ui.notify(`Personalization failed: ${err.message}`, "error");
|
|
351
|
+
}
|
|
352
|
+
};
|
package/dist/src/server.d.ts
CHANGED
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
* GET /vault-mind/context → latest editor context (file, cursor, selection) for pi agents
|
|
10
10
|
*
|
|
11
11
|
* Binds 127.0.0.1 only — no network exposure. Port defaults to 11435,
|
|
12
|
-
* configurable via
|
|
12
|
+
* configurable via vaultMind.httpPort in pi-vault-mind.config.json.
|
|
13
13
|
*
|
|
14
14
|
* Used by the obsidian-shellcommands plugin to notify pi of file saves,
|
|
15
15
|
* replacing fs.watch reliance with explicit push notifications.
|
|
16
16
|
*
|
|
17
17
|
* New Stage-1 routes (auth-gated by `PVM_API_TOKEN`):
|
|
18
|
-
* GET /
|
|
18
|
+
* GET /vm/status, POST /vm/init, POST /vm/setup, POST /server/start,
|
|
19
19
|
* GET /vault-mind/config, GET /agent/queue, GET /agent/jobs/:id,
|
|
20
20
|
* POST /agent/jobs/:id/retry, POST /agent/jobs/:id/cancel,
|
|
21
|
-
* POST /
|
|
21
|
+
* POST /vm/search, POST /vm/append, WS /agent/stream.
|
|
22
22
|
*/
|
|
23
23
|
import * as http from "node:http";
|
|
24
24
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -42,15 +42,41 @@ export interface ServerState {
|
|
|
42
42
|
};
|
|
43
43
|
/** The vault path this server is serving (default vault from global config, or cwd). */
|
|
44
44
|
vaultPath?: string;
|
|
45
|
+
/** Unsubscribe from queue notifications when stopping the server. */
|
|
46
|
+
unsubQueue?: () => void;
|
|
45
47
|
}
|
|
46
48
|
export declare function createServerState(port?: number): ServerState;
|
|
49
|
+
export type ServerEvent = {
|
|
50
|
+
type: "vault-edit-proposed";
|
|
51
|
+
path: string;
|
|
52
|
+
oldContent: string;
|
|
53
|
+
newContent: string;
|
|
54
|
+
jobId?: string;
|
|
55
|
+
} | {
|
|
56
|
+
type: "job-notification";
|
|
57
|
+
jobId: string;
|
|
58
|
+
status: string;
|
|
59
|
+
message: string;
|
|
60
|
+
} | {
|
|
61
|
+
type: "context-request";
|
|
62
|
+
requestId: string;
|
|
63
|
+
};
|
|
64
|
+
export declare const emitVaultEditProposed: (wss: WebSocketServer | null, path: string, oldContent: string, newContent: string, jobId?: string) => void;
|
|
65
|
+
export declare const emitJobNotification: (wss: WebSocketServer | null, jobId: string, status: string, message: string) => void;
|
|
66
|
+
export declare const emitContextRequest: (wss: WebSocketServer | null, requestId: string) => void;
|
|
47
67
|
export declare function startServer(pi: ExtensionAPI, serverState: ServerState, watcherState: WatcherState): void;
|
|
48
68
|
export declare function stopServer(serverState: ServerState): void;
|
|
49
69
|
/**
|
|
50
70
|
* Wrap a request handler with bearer-token auth. Use this for every
|
|
51
71
|
* new Stage-1 route so auth is consistent and testable in one place.
|
|
52
72
|
* The handler is only invoked if `authoriseRequest` passes.
|
|
73
|
+
*
|
|
74
|
+
* @param opts.requireWrite — when `true`, only the `manager` role (or a
|
|
75
|
+
* legacy `PVM_API_TOKEN` bearer with no identity) is allowed through.
|
|
76
|
+
* Miner / other roles receive 403.
|
|
53
77
|
*/
|
|
54
|
-
export declare const withAuth: (req: http.IncomingMessage, res: http.ServerResponse, handler: () => void
|
|
78
|
+
export declare const withAuth: (req: http.IncomingMessage, res: http.ServerResponse, handler: () => void, opts?: {
|
|
79
|
+
requireWrite?: boolean;
|
|
80
|
+
}) => void;
|
|
55
81
|
/** Read the full request body as UTF-8. Already used by handleScan; exposed for new routes. */
|
|
56
82
|
export declare const readJsonBody: (req: http.IncomingMessage) => Promise<unknown>;
|