pi-vault-mind 0.14.2 → 0.16.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 +293 -0
- package/README.md +5 -5
- package/agents/broadcaster.agent.md +2 -2
- package/agents/heavy-lifter.agent.md +1 -1
- package/agents/main.agent.md +29 -0
- package/agents/manager.agent.md +1 -1
- package/agents/miner.agent.md +1 -1
- package/dist/packages/obsidian/src/client.js +331 -0
- package/dist/packages/obsidian/src/types.js +1 -0
- package/dist/src/activity.d.ts +49 -0
- package/dist/src/activity.js +175 -0
- package/dist/src/agent-bus/Agent.d.ts +9 -1
- package/dist/src/agent-bus/Agent.js +18 -2
- package/dist/src/agent-bus/index.d.ts +5 -5
- package/dist/src/agent-bus/index.js +1 -1
- package/dist/src/agent-queue.js +12 -3
- package/dist/src/agents/BroadcasterAgent.d.ts +1 -1
- package/dist/src/agents/BroadcasterAgent.js +2 -2
- package/dist/src/agents/HeavyLifterAgent.d.ts +1 -1
- package/dist/src/agents/HeavyLifterAgent.js +2 -2
- package/dist/src/agents/ManagerAgent.js +1 -1
- package/dist/src/agents/MinerAgent.d.ts +1 -1
- package/dist/src/agents/MinerAgent.js +2 -2
- package/dist/src/agents/index.d.ts +2 -2
- package/dist/src/agents/index.js +2 -2
- package/dist/src/auth.d.ts +17 -15
- package/dist/src/auth.js +26 -37
- package/dist/src/autosync.js +2 -2
- package/dist/src/commands.d.ts +2 -19
- package/dist/src/commands.js +301 -231
- package/dist/src/config-keys.d.ts +16 -0
- package/dist/src/config-keys.js +45 -0
- package/dist/src/discover-schema.js +4 -4
- package/dist/src/edits.d.ts +21 -0
- package/dist/src/edits.js +75 -0
- package/dist/src/embedding-probe.d.ts +34 -0
- package/dist/src/embedding-probe.js +142 -0
- package/dist/src/embedding-providers.d.ts +133 -0
- package/dist/src/embedding-providers.js +173 -0
- package/dist/src/embedding-secrets.d.ts +38 -0
- package/dist/src/embedding-secrets.js +291 -0
- package/dist/src/engine.d.ts +6 -0
- package/dist/src/engine.js +78 -6
- package/dist/src/events.js +7 -10
- package/dist/src/extension-packages.d.ts +9 -0
- package/dist/src/extension-packages.js +14 -0
- package/dist/src/git.d.ts +19 -0
- package/dist/src/git.js +78 -0
- package/dist/src/graph.d.ts +2 -1
- package/dist/src/graph.js +47 -13
- package/dist/src/identities-config.d.ts +6 -0
- package/dist/src/identities-config.js +39 -0
- package/dist/src/identity-injector.js +1 -1
- package/dist/src/index.js +9 -26
- package/dist/src/lance.d.ts +9 -0
- package/dist/src/lance.js +143 -203
- package/dist/src/modal-client.d.ts +16 -0
- package/dist/src/modal-client.js +16 -0
- package/dist/src/modal-config.d.ts +37 -14
- package/dist/src/modal-config.js +59 -32
- package/dist/src/model-router.d.ts +27 -0
- package/dist/src/model-router.js +67 -0
- package/dist/src/models.d.ts +42 -0
- package/dist/src/models.js +78 -0
- package/dist/src/personalize.js +17 -3
- package/dist/src/scaffold.d.ts +48 -0
- package/dist/src/scaffold.js +246 -0
- package/dist/src/server.d.ts +47 -4
- package/dist/src/server.js +988 -221
- package/dist/src/session-search.d.ts +63 -0
- package/dist/src/session-search.js +379 -0
- package/dist/src/settings-ui.d.ts +2 -2
- package/dist/src/settings-ui.js +76 -135
- package/dist/src/sync.d.ts +3 -3
- package/dist/src/sync.js +19 -15
- package/dist/src/tool-catalog.d.ts +18 -0
- package/dist/src/tool-catalog.js +232 -0
- package/dist/src/tools/marksman.js +1 -1
- package/dist/src/tools.js +111 -65
- package/dist/src/types.d.ts +127 -71
- package/dist/src/utils.d.ts +57 -6
- package/dist/src/utils.js +114 -28
- package/dist/src/vault-tools.d.ts +34 -0
- package/dist/src/vault-tools.js +99 -0
- package/dist/src/vault-writer.js +4 -1
- package/dist/src/vm-handlers.js +140 -43
- package/dist/src/watcher.js +9 -9
- package/dist/test/activity-tool.test.js +54 -0
- package/dist/test/agent-bus/Agent.test.js +174 -0
- package/dist/test/agent-bus/AgentLoader.test.js +180 -0
- package/dist/test/agent-bus/AgentRegistry.test.js +86 -0
- package/dist/test/agent-bus/LLMProvider.test.js +162 -0
- package/dist/test/agent-bus/TaskQueue.test.js +256 -0
- package/dist/test/agent-dispatch-context.test.js +73 -0
- package/dist/test/agent-queue.test.js +1 -1
- package/dist/test/agents/Agents.test.js +282 -0
- package/dist/test/auth.test.js +57 -56
- package/dist/test/bridge-identity.test.js +34 -3
- package/dist/test/classify-search-mode.test.js +51 -0
- package/dist/test/commands.test.js +410 -0
- package/dist/test/config-merge.test.js +98 -0
- package/dist/test/context-capture.test.js +56 -0
- package/dist/test/discover-schema.test.js +8 -5
- package/dist/test/dispatch.test.js +45 -91
- package/dist/test/edits.test.js +144 -0
- package/dist/test/embedding-probe.test.js +424 -0
- package/dist/test/embedding-providers.test.js +157 -0
- package/dist/test/embedding-secrets.test.js +210 -0
- package/dist/test/engine-identity.test.js +1 -1
- package/dist/test/events.test.js +675 -0
- package/dist/test/git-bridge.test.js +160 -0
- package/dist/test/graph.test.js +464 -0
- package/dist/test/helpers/server.js +81 -0
- package/dist/test/identity-injector.test.js +197 -0
- package/dist/test/index.test.js +5 -7
- package/dist/test/lance-modal.test.js +67 -2
- package/dist/test/lance.test.js +429 -0
- package/dist/test/modal-client.test.js +1 -1
- package/dist/test/modal-config.test.js +92 -79
- package/dist/test/model-readiness.test.js +122 -0
- package/dist/test/model-router.test.js +99 -0
- package/dist/test/obsidian-client.test.js +42 -0
- package/dist/test/personalize.test.js +1 -1
- package/dist/test/rest-models.test.js +153 -0
- package/dist/test/rest-queue.test.js +135 -87
- package/dist/test/rest-setup.test.js +625 -115
- package/dist/test/rest-vm.test.js +355 -94
- package/dist/test/server-cors.test.js +48 -0
- package/dist/test/server-ws.test.js +61 -34
- package/dist/test/session-crud.test.js +323 -0
- package/dist/test/session-search.test.js +139 -0
- package/dist/test/settings-ui.test.js +113 -4
- package/dist/test/sync.test.js +12 -12
- package/dist/test/tombstone.test.js +8 -10
- package/dist/test/tool-catalog.test.js +86 -0
- package/dist/test/utils.test.js +588 -0
- package/dist/test/vault-tools.test.js +103 -12
- package/dist/test/vault-writer.test.js +371 -0
- package/dist/test/vm-handlers.test.js +620 -0
- package/extension-packages.json +9 -0
- package/package.json +107 -90
- package/pi-vault-mind.config.example.json +24 -2
- package/scripts/check-deps.sh +35 -32
- package/scripts/cli-e2e.mjs +215 -0
- package/scripts/configuration-e2e.mjs +783 -0
- package/scripts/e2e-commands.mjs +391 -0
- package/scripts/e2e-ws.mjs +3 -2
- package/scripts/fetch-modal-token.sh +4 -3
- package/scripts/generate-config-keys-json.mjs +45 -0
- package/scripts/generate-extension-packages-json.mjs +55 -0
- package/scripts/pi-session-query +0 -0
- package/scripts/publish-obsidian-plugin.sh +0 -0
- package/scripts/reset-test-vault.sh +2 -2
- package/scripts/setup-test-config.sh +0 -0
- package/scripts/setup-vault-pi.sh +48 -9
- package/scripts/test-slash-commands.mjs +2 -2
- package/scripts/vault-pi.sh +12 -3
- package/skills/github-release-notes-file/SKILL.md +42 -0
- package/skills/obsidian-guided-test-session/SKILL.md +110 -0
- package/skills/obsidian-interactive-test/SKILL.md +88 -0
- package/skills/obsidian-plugin-publish/SKILL.md +58 -0
- package/skills/pi-vault-mind-release/SKILL.md +141 -0
- package/skills/pre-test-release/SKILL.md +116 -0
- package/dist/test/auto-indexer.test.js +0 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding provider secret contract.
|
|
3
|
+
*
|
|
4
|
+
* Bridge authentication (`PVM_API_TOKEN`) and provider credentials are fully
|
|
5
|
+
* decoupled. This module owns reading, writing, resolving, masking, and
|
|
6
|
+
* status reporting for the four embedding secret kinds. All four variables are
|
|
7
|
+
* stored in `<vault>/.vault-mind/vault-mind.env`; legacy JSON config copies are
|
|
8
|
+
* removed on write.
|
|
9
|
+
*/
|
|
10
|
+
import type { EmbeddingSecretKind, EmbeddingSecretResolutionOptions, EmbeddingSecretStatus, EmbeddingSecretStatusResponse, PutEmbeddingSecretsRequest, PutEmbeddingSecretsResponse, UniversalConfig, VaultMindConfig } from "./types.js";
|
|
11
|
+
export type { EmbeddingSecretKind, EmbeddingSecretResolutionOptions, EmbeddingSecretStatus, EmbeddingSecretStatusResponse, PutEmbeddingSecretsRequest, PutEmbeddingSecretsResponse, };
|
|
12
|
+
export declare const EMBEDDING_SECRET_MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
13
|
+
/**
|
|
14
|
+
* Resolve one embedding secret. Precedence:
|
|
15
|
+
* 1. `options.environment` if supplied, else `process.env`
|
|
16
|
+
* 2. `<vault>/.vault-mind/vault-mind.env`
|
|
17
|
+
* 3. Matching legacy field in `cfg.embedding`
|
|
18
|
+
*/
|
|
19
|
+
export declare const resolveEmbeddingSecret: (cfg: VaultMindConfig, kind: EmbeddingSecretKind, opts?: EmbeddingSecretResolutionOptions) => string | undefined;
|
|
20
|
+
/** Return the status of all four embedding secret kinds in declaration order. */
|
|
21
|
+
export declare const readEmbeddingSecretStatus: (cfg: VaultMindConfig, opts?: EmbeddingSecretResolutionOptions) => EmbeddingSecretStatusResponse;
|
|
22
|
+
/**
|
|
23
|
+
* Update a single env entry in `<vault>/.vault-mind/vault-mind.env` atomically,
|
|
24
|
+
* preserving all other entries and comments. This is the entry-preserving
|
|
25
|
+
* primitive reused by callers that only need to write one variable.
|
|
26
|
+
*/
|
|
27
|
+
export declare const writeEnvEntry: (key: string, value: string, opts?: EmbeddingSecretResolutionOptions) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Update the embedding secret store. Validation runs before any mutation. The
|
|
30
|
+
* env file is replaced atomically; if the matching legacy JSON config cleanup
|
|
31
|
+
* fails, the previous env file bytes are restored.
|
|
32
|
+
*/
|
|
33
|
+
export declare const writeEmbeddingSecrets: (request: PutEmbeddingSecretsRequest, opts?: EmbeddingSecretResolutionOptions) => PutEmbeddingSecretsResponse;
|
|
34
|
+
/**
|
|
35
|
+
* Return a deep copy of `config` with all legacy provider credential fields
|
|
36
|
+
* replaced by the fixed mask. The original config is not mutated.
|
|
37
|
+
*/
|
|
38
|
+
export declare const redactEmbeddingSecrets: (config: UniversalConfig) => UniversalConfig;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding provider secret contract.
|
|
3
|
+
*
|
|
4
|
+
* Bridge authentication (`PVM_API_TOKEN`) and provider credentials are fully
|
|
5
|
+
* decoupled. This module owns reading, writing, resolving, masking, and
|
|
6
|
+
* status reporting for the four embedding secret kinds. All four variables are
|
|
7
|
+
* stored in `<vault>/.vault-mind/vault-mind.env`; legacy JSON config copies are
|
|
8
|
+
* removed on write.
|
|
9
|
+
*/
|
|
10
|
+
import * as crypto from "node:crypto";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { getTokenEnvPath } from "./utils.js";
|
|
14
|
+
const DEFINITIONS = {
|
|
15
|
+
localApiKey: { env: "PVM_LOCAL_EMBEDDING_API_KEY", legacy: "apiKey" },
|
|
16
|
+
remoteApiKey: { env: "PVM_REMOTE_EMBEDDING_API_KEY", legacy: "remoteApiKey" },
|
|
17
|
+
remoteReadApiKey: { env: "PVM_MODAL_READ_TOKEN", legacy: "remoteReadApiKey" },
|
|
18
|
+
remoteWriteApiKey: { env: "PVM_MODAL_WRITE_TOKEN", legacy: "remoteWriteApiKey" },
|
|
19
|
+
};
|
|
20
|
+
export const EMBEDDING_SECRET_MASK = "••••••••";
|
|
21
|
+
const getEnv = (opts, key) => {
|
|
22
|
+
const env = opts?.environment ?? process.env;
|
|
23
|
+
return env[key];
|
|
24
|
+
};
|
|
25
|
+
const getEnvPath = (opts) => getTokenEnvPath(opts?.cwd ?? process.cwd());
|
|
26
|
+
/**
|
|
27
|
+
* Parse a dotenv-style file into a record while preserving the original byte
|
|
28
|
+
* content so writes can keep comments, blank lines, and unrelated variables.
|
|
29
|
+
*/
|
|
30
|
+
const readDotEnv = (filePath) => {
|
|
31
|
+
if (!fs.existsSync(filePath))
|
|
32
|
+
return {};
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const line of fs.readFileSync(filePath, "utf-8").split(/\r?\n/)) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
37
|
+
continue;
|
|
38
|
+
const eq = trimmed.indexOf("=");
|
|
39
|
+
if (eq === -1)
|
|
40
|
+
continue;
|
|
41
|
+
const key = trimmed.slice(0, eq).trim();
|
|
42
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
43
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
44
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
45
|
+
value = value.slice(1, -1);
|
|
46
|
+
}
|
|
47
|
+
out[key] = value;
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Resolve one embedding secret. Precedence:
|
|
53
|
+
* 1. `options.environment` if supplied, else `process.env`
|
|
54
|
+
* 2. `<vault>/.vault-mind/vault-mind.env`
|
|
55
|
+
* 3. Matching legacy field in `cfg.embedding`
|
|
56
|
+
*/
|
|
57
|
+
export const resolveEmbeddingSecret = (cfg, kind, opts) => {
|
|
58
|
+
const def = DEFINITIONS[kind];
|
|
59
|
+
const fromEnv = getEnv(opts, def.env);
|
|
60
|
+
if (fromEnv)
|
|
61
|
+
return fromEnv;
|
|
62
|
+
const fromStore = readDotEnv(getEnvPath(opts))[def.env];
|
|
63
|
+
if (fromStore)
|
|
64
|
+
return fromStore;
|
|
65
|
+
return cfg.embedding[def.legacy];
|
|
66
|
+
};
|
|
67
|
+
const getSecretSource = (cfg, kind, envRecord, opts) => {
|
|
68
|
+
const def = DEFINITIONS[kind];
|
|
69
|
+
if (getEnv(opts, def.env))
|
|
70
|
+
return "extension-environment";
|
|
71
|
+
if (envRecord[def.env])
|
|
72
|
+
return "extension-secret-store";
|
|
73
|
+
if (cfg.embedding[def.legacy])
|
|
74
|
+
return "legacy-config";
|
|
75
|
+
return "none";
|
|
76
|
+
};
|
|
77
|
+
/** Return the status of all four embedding secret kinds in declaration order. */
|
|
78
|
+
export const readEmbeddingSecretStatus = (cfg, opts) => {
|
|
79
|
+
const kinds = [
|
|
80
|
+
"localApiKey",
|
|
81
|
+
"remoteApiKey",
|
|
82
|
+
"remoteReadApiKey",
|
|
83
|
+
"remoteWriteApiKey",
|
|
84
|
+
];
|
|
85
|
+
const envRecord = readDotEnv(getEnvPath(opts));
|
|
86
|
+
return {
|
|
87
|
+
secrets: kinds.map((kind) => {
|
|
88
|
+
const source = getSecretSource(cfg, kind, envRecord, opts);
|
|
89
|
+
const configured = source !== "none";
|
|
90
|
+
return {
|
|
91
|
+
kind,
|
|
92
|
+
configured,
|
|
93
|
+
masked: configured ? EMBEDDING_SECRET_MASK : null,
|
|
94
|
+
source,
|
|
95
|
+
};
|
|
96
|
+
}),
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
/** Read the vault config object to use as the status baseline. */
|
|
100
|
+
const readVaultMindConfig = (configPath) => {
|
|
101
|
+
try {
|
|
102
|
+
if (fs.existsSync(configPath)) {
|
|
103
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
104
|
+
return raw.vaultMind ?? { dataDir: "", embedding: {} };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// fall through to empty config
|
|
109
|
+
}
|
|
110
|
+
return { dataDir: "", embedding: {} };
|
|
111
|
+
};
|
|
112
|
+
/** True when the value is an empty string; empty values are rejected before mutation. */
|
|
113
|
+
const isEmptyString = (value) => value === "";
|
|
114
|
+
/** Read a dotenv-style file as a list of lines, returning an empty list when absent. */
|
|
115
|
+
const readEnvLines = (filePath) => {
|
|
116
|
+
if (!fs.existsSync(filePath))
|
|
117
|
+
return [];
|
|
118
|
+
return fs.readFileSync(filePath, "utf-8").split(/\r?\n/);
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Update or remove a single env entry while preserving all other lines
|
|
122
|
+
* (comments, blanks, unrelated variables). If the key is absent and `value` is
|
|
123
|
+
* a string, it is appended. Duplicate occurrences collapse into one update.
|
|
124
|
+
*/
|
|
125
|
+
const updateEnvLine = (lines, key, value) => {
|
|
126
|
+
const out = [];
|
|
127
|
+
let handled = false;
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
const trimmed = line.trim();
|
|
130
|
+
const match = trimmed.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=/);
|
|
131
|
+
if (!match || match[1] !== key) {
|
|
132
|
+
out.push(line);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (handled) {
|
|
136
|
+
// Drop duplicate occurrences of the same key.
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
handled = true;
|
|
140
|
+
if (value === null) {
|
|
141
|
+
// Drop the line for a null deletion.
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
out.push(`${key}="${value}"`);
|
|
145
|
+
}
|
|
146
|
+
if (!handled && value !== null) {
|
|
147
|
+
out.push(`${key}="${value}"`);
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
};
|
|
151
|
+
/** Atomically replace the env file using a same-directory temporary file. */
|
|
152
|
+
const atomicWriteEnvFile = (envPath, lines) => {
|
|
153
|
+
let content = lines.join("\n");
|
|
154
|
+
if (!content.endsWith("\n"))
|
|
155
|
+
content += "\n";
|
|
156
|
+
const newBytes = Buffer.from(content, "utf-8");
|
|
157
|
+
const dir = path.dirname(envPath);
|
|
158
|
+
if (!fs.existsSync(dir))
|
|
159
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
160
|
+
const tmp = path.join(dir, `.vault-mind.env.tmp-${crypto.randomBytes(8).toString("hex")}`);
|
|
161
|
+
try {
|
|
162
|
+
fs.writeFileSync(tmp, newBytes, { mode: process.platform === "win32" ? undefined : 0o600 });
|
|
163
|
+
fs.renameSync(tmp, envPath);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
try {
|
|
167
|
+
fs.unlinkSync(tmp);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// best-effort cleanup
|
|
171
|
+
}
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
if (process.platform !== "win32")
|
|
176
|
+
fs.chmodSync(envPath, 0o600);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// best-effort; no-op on win32 or when chmod is unavailable
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
/** Best-effort restore of previous env bytes on rollback. */
|
|
183
|
+
const restoreEnv = (envPath, bytes) => {
|
|
184
|
+
try {
|
|
185
|
+
fs.writeFileSync(envPath, bytes);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// rollback is best-effort; if it fails the caller sees the original error
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Update a single env entry in `<vault>/.vault-mind/vault-mind.env` atomically,
|
|
193
|
+
* preserving all other entries and comments. This is the entry-preserving
|
|
194
|
+
* primitive reused by callers that only need to write one variable.
|
|
195
|
+
*/
|
|
196
|
+
export const writeEnvEntry = (key, value, opts) => {
|
|
197
|
+
const envPath = getEnvPath(opts);
|
|
198
|
+
const lines = updateEnvLine(readEnvLines(envPath), key, value);
|
|
199
|
+
atomicWriteEnvFile(envPath, lines);
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Update the embedding secret store. Validation runs before any mutation. The
|
|
203
|
+
* env file is replaced atomically; if the matching legacy JSON config cleanup
|
|
204
|
+
* fails, the previous env file bytes are restored.
|
|
205
|
+
*/
|
|
206
|
+
export const writeEmbeddingSecrets = (request, opts) => {
|
|
207
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
208
|
+
const envPath = getTokenEnvPath(cwd);
|
|
209
|
+
const configPath = path.join(path.dirname(envPath), "vault-mind.config.json");
|
|
210
|
+
// Reject empty-string values before touching disk.
|
|
211
|
+
for (const [kind, value] of Object.entries(request)) {
|
|
212
|
+
if (isEmptyString(value)) {
|
|
213
|
+
return {
|
|
214
|
+
ok: false,
|
|
215
|
+
secrets: readEmbeddingSecretStatus(readVaultMindConfig(configPath), opts).secrets,
|
|
216
|
+
error: `Empty value rejected for ${kind}`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const previousBytes = fs.existsSync(envPath) ? fs.readFileSync(envPath) : Buffer.from("");
|
|
221
|
+
let currentConfig = readVaultMindConfig(configPath);
|
|
222
|
+
// Apply each requested mutation while preserving unrelated lines.
|
|
223
|
+
let updatedLines = previousBytes.toString("utf-8").split(/\r?\n/);
|
|
224
|
+
for (const kind of Object.keys(DEFINITIONS)) {
|
|
225
|
+
const value = request[kind];
|
|
226
|
+
if (value !== undefined) {
|
|
227
|
+
updatedLines = updateEnvLine(updatedLines, DEFINITIONS[kind].env, value);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
atomicWriteEnvFile(envPath, updatedLines);
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
secrets: readEmbeddingSecretStatus(currentConfig, opts).secrets,
|
|
237
|
+
error: `Failed to write secret store: ${err}`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// Clean matching legacy fields from the JSON config, if a config file exists.
|
|
241
|
+
try {
|
|
242
|
+
if (fs.existsSync(configPath)) {
|
|
243
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
244
|
+
let dirty = false;
|
|
245
|
+
for (const kind of Object.keys(DEFINITIONS)) {
|
|
246
|
+
const value = request[kind];
|
|
247
|
+
if (value === undefined)
|
|
248
|
+
continue;
|
|
249
|
+
const legacyKey = DEFINITIONS[kind].legacy;
|
|
250
|
+
if (raw.vaultMind?.embedding?.[legacyKey] !== undefined) {
|
|
251
|
+
delete raw.vaultMind.embedding[legacyKey];
|
|
252
|
+
dirty = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (dirty) {
|
|
256
|
+
fs.writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
|
|
257
|
+
}
|
|
258
|
+
if (raw.vaultMind) {
|
|
259
|
+
currentConfig = raw.vaultMind;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
restoreEnv(envPath, previousBytes);
|
|
265
|
+
return {
|
|
266
|
+
ok: false,
|
|
267
|
+
secrets: readEmbeddingSecretStatus(currentConfig, opts).secrets,
|
|
268
|
+
error: `Failed to clean legacy config: ${err}`,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
ok: true,
|
|
273
|
+
secrets: readEmbeddingSecretStatus(currentConfig, opts).secrets,
|
|
274
|
+
error: null,
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* Return a deep copy of `config` with all legacy provider credential fields
|
|
279
|
+
* replaced by the fixed mask. The original config is not mutated.
|
|
280
|
+
*/
|
|
281
|
+
export const redactEmbeddingSecrets = (config) => {
|
|
282
|
+
const redacted = JSON.parse(JSON.stringify(config));
|
|
283
|
+
const emb = redacted.vaultMind.embedding;
|
|
284
|
+
for (const kind of Object.keys(DEFINITIONS)) {
|
|
285
|
+
const legacyKey = DEFINITIONS[kind].legacy;
|
|
286
|
+
if (emb[legacyKey] !== undefined) {
|
|
287
|
+
emb[legacyKey] = EMBEDDING_SECRET_MASK;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return redacted;
|
|
291
|
+
};
|
package/dist/src/engine.d.ts
CHANGED
|
@@ -63,3 +63,9 @@ export declare function buildIdentityFromRole(role: string, id: string): AgentId
|
|
|
63
63
|
* an agent.
|
|
64
64
|
*/
|
|
65
65
|
export declare function mergeIdentityProfiles(base: AgentIdentity, defaultProfile: IdentityConfig | undefined, roleProfile: IdentityConfig | undefined): AgentIdentity;
|
|
66
|
+
/**
|
|
67
|
+
* Re-read identities config from disk, re-merge profiles, and re-register
|
|
68
|
+
* affected roles in the bridge's agentIdentities map. Call after config
|
|
69
|
+
* writes that touch identities.roles.
|
|
70
|
+
*/
|
|
71
|
+
export declare function refreshAgentIdentities(rolesToReload?: string[]): void;
|
package/dist/src/engine.js
CHANGED
|
@@ -140,7 +140,13 @@ export function buildIdentityFromRole(role, id) {
|
|
|
140
140
|
"ls",
|
|
141
141
|
"bash",
|
|
142
142
|
"vm_search",
|
|
143
|
-
"
|
|
143
|
+
"vault_read",
|
|
144
|
+
"vault_search",
|
|
145
|
+
"vault_list",
|
|
146
|
+
"vault_tags",
|
|
147
|
+
"vm_backlinks",
|
|
148
|
+
"vm_related",
|
|
149
|
+
"vm_codegraph_impact",
|
|
144
150
|
],
|
|
145
151
|
writeCollections: [],
|
|
146
152
|
canPublish: false,
|
|
@@ -157,8 +163,13 @@ export function buildIdentityFromRole(role, id) {
|
|
|
157
163
|
"find",
|
|
158
164
|
"ls",
|
|
159
165
|
"vm_search",
|
|
160
|
-
"vm_fts_search",
|
|
161
166
|
"vm_append",
|
|
167
|
+
"vault_read",
|
|
168
|
+
"vault_search",
|
|
169
|
+
"vault_list",
|
|
170
|
+
"vault_tags",
|
|
171
|
+
"vm_backlinks",
|
|
172
|
+
"vm_related",
|
|
162
173
|
],
|
|
163
174
|
writeCollections: ["main", "research"],
|
|
164
175
|
canPublish: false,
|
|
@@ -167,7 +178,7 @@ export function buildIdentityFromRole(role, id) {
|
|
|
167
178
|
return {
|
|
168
179
|
id,
|
|
169
180
|
role,
|
|
170
|
-
allowedTools: ["read", "write", "edit", "vm_search", "
|
|
181
|
+
allowedTools: ["read", "write", "edit", "vm_search", "vault_read", "vault_search"],
|
|
171
182
|
writeCollections: [],
|
|
172
183
|
canPublish: false,
|
|
173
184
|
};
|
|
@@ -183,7 +194,6 @@ export function buildIdentityFromRole(role, id) {
|
|
|
183
194
|
"find",
|
|
184
195
|
"ls",
|
|
185
196
|
"vm_search",
|
|
186
|
-
"vm_fts_search",
|
|
187
197
|
"vm_append",
|
|
188
198
|
"vm_sync",
|
|
189
199
|
"vm_promote",
|
|
@@ -194,16 +204,58 @@ export function buildIdentityFromRole(role, id) {
|
|
|
194
204
|
"vm_export",
|
|
195
205
|
"vm_ingest",
|
|
196
206
|
"vm_status",
|
|
197
|
-
"
|
|
207
|
+
"vault_read",
|
|
208
|
+
"vault_search",
|
|
209
|
+
"vault_list",
|
|
210
|
+
"vault_tags",
|
|
211
|
+
"vm_backlinks",
|
|
212
|
+
"vm_related",
|
|
213
|
+
"vm_broken_links",
|
|
214
|
+
"vm_codegraph_impact",
|
|
215
|
+
"vault_write",
|
|
216
|
+
"vault_append",
|
|
198
217
|
],
|
|
199
218
|
writeCollections: ["main", "research", "presentations"],
|
|
200
219
|
canPublish: true,
|
|
201
220
|
};
|
|
221
|
+
case "main":
|
|
222
|
+
return {
|
|
223
|
+
id,
|
|
224
|
+
role,
|
|
225
|
+
allowedTools: [
|
|
226
|
+
"read",
|
|
227
|
+
"write",
|
|
228
|
+
"edit",
|
|
229
|
+
"bash",
|
|
230
|
+
"grep",
|
|
231
|
+
"find",
|
|
232
|
+
"ls",
|
|
233
|
+
"vm_search",
|
|
234
|
+
"vm_query",
|
|
235
|
+
"vm_append",
|
|
236
|
+
"vm_stats",
|
|
237
|
+
"vm_status",
|
|
238
|
+
"vm_describe",
|
|
239
|
+
"vm_configure",
|
|
240
|
+
"vault_read",
|
|
241
|
+
"vault_search",
|
|
242
|
+
"vault_list",
|
|
243
|
+
"vault_tags",
|
|
244
|
+
"vm_backlinks",
|
|
245
|
+
"vm_related",
|
|
246
|
+
"vm_broken_links",
|
|
247
|
+
"vm_codegraph_impact",
|
|
248
|
+
],
|
|
249
|
+
readCollections: ["main"],
|
|
250
|
+
writeCollections: [],
|
|
251
|
+
canPublish: false,
|
|
252
|
+
allowedVaultPaths: [],
|
|
253
|
+
};
|
|
202
254
|
default:
|
|
203
255
|
return {
|
|
204
256
|
id,
|
|
205
257
|
role,
|
|
206
|
-
allowedTools: ["read", "vm_search", "
|
|
258
|
+
allowedTools: ["read", "vm_search", "vault_search"],
|
|
207
259
|
writeCollections: [],
|
|
208
260
|
canPublish: false,
|
|
209
261
|
};
|
|
@@ -242,3 +294,23 @@ export function mergeIdentityProfiles(base, defaultProfile, roleProfile) {
|
|
|
242
294
|
}
|
|
243
295
|
return merged;
|
|
244
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Re-read identities config from disk, re-merge profiles, and re-register
|
|
299
|
+
* affected roles in the bridge's agentIdentities map. Call after config
|
|
300
|
+
* writes that touch identities.roles.
|
|
301
|
+
*/
|
|
302
|
+
export function refreshAgentIdentities(rolesToReload) {
|
|
303
|
+
const cfg = loadConfig(process.cwd());
|
|
304
|
+
const identitiesCfg = cfg.vaultMind.identities ?? {};
|
|
305
|
+
const roles = identitiesCfg.roles ?? {};
|
|
306
|
+
const defaultProfile = identitiesCfg.defaultProfile;
|
|
307
|
+
const targetRoles = rolesToReload ?? Object.keys(roles);
|
|
308
|
+
for (const role of targetRoles) {
|
|
309
|
+
const roleProfile = roles[role];
|
|
310
|
+
if (!roleProfile)
|
|
311
|
+
continue;
|
|
312
|
+
const base = buildIdentityFromRole(role, `agent-${role}`);
|
|
313
|
+
const identity = mergeIdentityProfiles(base, defaultProfile, roleProfile);
|
|
314
|
+
registerAgentIdentity(identity);
|
|
315
|
+
}
|
|
316
|
+
}
|
package/dist/src/events.js
CHANGED
|
@@ -55,15 +55,12 @@ export const handleBeforeAgentStart = async (pi, event, ctx) => {
|
|
|
55
55
|
// pi-context is disabled or unavailable — skip integration silently.
|
|
56
56
|
}
|
|
57
57
|
else {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
deliverAs: "followUp",
|
|
65
|
-
});
|
|
66
|
-
}
|
|
58
|
+
// ACM auto-enable is handled exactly once at session_start (see index.ts)
|
|
59
|
+
// via pi.sendUserMessage("/acm"), gated on isVaultMindReady. A hidden
|
|
60
|
+
// sendMessage({ customType: "pi-context", content: "/acm" }) here does NOT
|
|
61
|
+
// enable compaction — pi-context only enables ACM from its own `/acm`
|
|
62
|
+
// command handler (which captures the command context), so this dead path
|
|
63
|
+
// was removed.
|
|
67
64
|
if (piContextCfg.tagPatterns && piContextCfg.tagPatterns.length > 0) {
|
|
68
65
|
try {
|
|
69
66
|
const allTools = pi.getAllTools();
|
|
@@ -150,7 +147,7 @@ export const handleTurnEnd = async (pi, _event, ctx) => {
|
|
|
150
147
|
}
|
|
151
148
|
// Get current session history
|
|
152
149
|
const logs = await contextLogTool.execute?.(null, { limit: 100, verbose: true }, null, null, ctx);
|
|
153
|
-
if (!logs
|
|
150
|
+
if (!logs?.content?.[0]?.text) {
|
|
154
151
|
return;
|
|
155
152
|
}
|
|
156
153
|
const logText = logs.content[0].text;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical list of pi extensions required/optional for vault-mind.
|
|
3
|
+
*
|
|
4
|
+
* This is the SINGLE SOURCE OF TRUTH. Shell scripts and the Obsidian plugin
|
|
5
|
+
* consume generated JSON (see scripts/generate-extension-packages-json.mjs).
|
|
6
|
+
* When adding/removing a package, edit ONLY this file and regenerate.
|
|
7
|
+
*/
|
|
8
|
+
export declare const PI_REQUIRED_EXTENSIONS: readonly ["npm:pi-vault-mind", "npm:pi-context", "npm:@kylebrodeur/pi-model-discovery", "npm:@kylebrodeur/pi-model-router"];
|
|
9
|
+
export declare const PI_OPTIONAL_EXTENSIONS: readonly ["npm:pi-1password"];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical list of pi extensions required/optional for vault-mind.
|
|
3
|
+
*
|
|
4
|
+
* This is the SINGLE SOURCE OF TRUTH. Shell scripts and the Obsidian plugin
|
|
5
|
+
* consume generated JSON (see scripts/generate-extension-packages-json.mjs).
|
|
6
|
+
* When adding/removing a package, edit ONLY this file and regenerate.
|
|
7
|
+
*/
|
|
8
|
+
export const PI_REQUIRED_EXTENSIONS = [
|
|
9
|
+
"npm:pi-vault-mind",
|
|
10
|
+
"npm:pi-context",
|
|
11
|
+
"npm:@kylebrodeur/pi-model-discovery",
|
|
12
|
+
"npm:@kylebrodeur/pi-model-router",
|
|
13
|
+
];
|
|
14
|
+
export const PI_OPTIONAL_EXTENSIONS = ["npm:pi-1password"];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface GitStatus {
|
|
2
|
+
isRepo: boolean;
|
|
3
|
+
branch: string | null;
|
|
4
|
+
dirty: boolean;
|
|
5
|
+
ahead: number;
|
|
6
|
+
behind: number;
|
|
7
|
+
detached: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface GitBranches {
|
|
10
|
+
isRepo: boolean;
|
|
11
|
+
branches: string[];
|
|
12
|
+
current: string | null;
|
|
13
|
+
}
|
|
14
|
+
export declare function getGitStatus(cwd: string): Promise<GitStatus>;
|
|
15
|
+
export declare function getGitBranches(cwd: string): Promise<GitBranches>;
|
|
16
|
+
export declare function gitCheckout(cwd: string, branch: string): Promise<{
|
|
17
|
+
ok: boolean;
|
|
18
|
+
branch: string;
|
|
19
|
+
}>;
|
package/dist/src/git.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
async function git(cwd, args) {
|
|
5
|
+
const { stdout } = await execFileAsync("git", args, { cwd, timeout: 5000 });
|
|
6
|
+
return stdout.trim();
|
|
7
|
+
}
|
|
8
|
+
export async function getGitStatus(cwd) {
|
|
9
|
+
let isRepo = false;
|
|
10
|
+
try {
|
|
11
|
+
const out = await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
12
|
+
isRepo = out === "true";
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
isRepo = false;
|
|
16
|
+
}
|
|
17
|
+
if (!isRepo) {
|
|
18
|
+
return { isRepo: false, branch: null, dirty: false, ahead: 0, behind: 0, detached: false };
|
|
19
|
+
}
|
|
20
|
+
const branch = await git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
21
|
+
const detached = branch === "HEAD";
|
|
22
|
+
let dirty = false;
|
|
23
|
+
try {
|
|
24
|
+
const status = await git(cwd, ["status", "--porcelain"]);
|
|
25
|
+
dirty = status.length > 0;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
dirty = false;
|
|
29
|
+
}
|
|
30
|
+
let ahead = 0;
|
|
31
|
+
let behind = 0;
|
|
32
|
+
try {
|
|
33
|
+
const counts = await git(cwd, ["rev-list", "--left-right", "--count", "HEAD...@{upstream}"]);
|
|
34
|
+
const parts = counts.split(/\s+/);
|
|
35
|
+
ahead = Number(parts[0]) || 0;
|
|
36
|
+
behind = Number(parts[1]) || 0;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
ahead = 0;
|
|
40
|
+
behind = 0;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
isRepo: true,
|
|
44
|
+
branch: detached ? null : branch,
|
|
45
|
+
dirty,
|
|
46
|
+
ahead,
|
|
47
|
+
behind,
|
|
48
|
+
detached,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function getGitBranches(cwd) {
|
|
52
|
+
let isRepo = false;
|
|
53
|
+
try {
|
|
54
|
+
const out = await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
55
|
+
isRepo = out === "true";
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
isRepo = false;
|
|
59
|
+
}
|
|
60
|
+
if (!isRepo) {
|
|
61
|
+
return { isRepo: false, branches: [], current: null };
|
|
62
|
+
}
|
|
63
|
+
const raw = await git(cwd, ["branch", "--format=%(refname:short)"]);
|
|
64
|
+
const branches = raw
|
|
65
|
+
.split("\n")
|
|
66
|
+
.map((b) => b.trim())
|
|
67
|
+
.filter((b) => b.length > 0);
|
|
68
|
+
const branch = await git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
69
|
+
const current = branch === "HEAD" ? null : branch;
|
|
70
|
+
return { isRepo: true, branches, current };
|
|
71
|
+
}
|
|
72
|
+
export async function gitCheckout(cwd, branch) {
|
|
73
|
+
if (!/^(?!-)[A-Za-z0-9._/-]+$/.test(branch)) {
|
|
74
|
+
throw new Error("Invalid branch name");
|
|
75
|
+
}
|
|
76
|
+
await git(cwd, ["checkout", branch]);
|
|
77
|
+
return { ok: true, branch };
|
|
78
|
+
}
|
package/dist/src/graph.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentIdentity } from "./bridge.js";
|
|
1
2
|
import type { VaultMindConfig } from "./types.js";
|
|
2
3
|
export declare const graphUpsert: (dataDir: string, cfg: VaultMindConfig, entry: Record<string, string>) => Promise<void>;
|
|
3
|
-
export declare const queryGraph: (dataDir: string, cfg: VaultMindConfig, entityQuery: string, depth: number) => Promise<unknown>;
|
|
4
|
+
export declare const queryGraph: (dataDir: string, cfg: VaultMindConfig, entityQuery: string, depth: number, identity?: AgentIdentity) => Promise<unknown>;
|