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
package/dist/src/modal-config.js
CHANGED
|
@@ -9,16 +9,20 @@
|
|
|
9
9
|
* No HTTP is reimplemented here — `ModalEmbeddingClient` owns that.
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
|
-
import
|
|
12
|
+
import { EMBEDDING_FLAT_KEYS } from "./config-keys.js";
|
|
13
13
|
import { ModalEmbeddingClient } from "./modal-client.js";
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
import { deriveCollectionNameFromVaultPath, getTokenEnvPath, normalizeCollectionName, } from "./utils.js";
|
|
15
|
+
/** Env var name for the unscoped remote embedding API key (preferred over config). */
|
|
16
|
+
export const MODAL_TOKEN_ENV = "PVM_REMOTE_EMBEDDING_API_KEY";
|
|
16
17
|
/** Env var name for the read-scoped Modal token (search/export). */
|
|
17
18
|
export const MODAL_READ_TOKEN_ENV = "PVM_MODAL_READ_TOKEN";
|
|
18
19
|
/** Env var name for the write-scoped Modal token (embed/reindex). */
|
|
19
20
|
export const MODAL_WRITE_TOKEN_ENV = "PVM_MODAL_WRITE_TOKEN";
|
|
20
|
-
/**
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Dotenv fallback path for the Modal token, resolved from the vault cwd:
|
|
23
|
+
* `<vault>/.vault-mind/vault-mind.env`. Lazy so tests can override cwd.
|
|
24
|
+
*/
|
|
25
|
+
export const modalTokenEnvPath = (cwd = process.cwd()) => getTokenEnvPath(cwd);
|
|
22
26
|
/** Canonical deployed Modal app name. */
|
|
23
27
|
export const MODAL_APP_NAME = "pi-vault-mind-embed";
|
|
24
28
|
/**
|
|
@@ -49,58 +53,63 @@ const readDotEnv = (filePath) => {
|
|
|
49
53
|
/** Derive the Modal service URL from a workspace slug and canonical app name. */
|
|
50
54
|
export const modalUrl = (workspace) => `https://${workspace}--${MODAL_APP_NAME}-embeddingservice-fastapi-app.modal.run`;
|
|
51
55
|
/**
|
|
52
|
-
* Resolve the
|
|
53
|
-
* 1. `
|
|
54
|
-
* 2.
|
|
55
|
-
* 3. `vaultMind.embedding.
|
|
56
|
+
* Resolve the unscoped remote embedding API token. Resolution order:
|
|
57
|
+
* 1. `PVM_REMOTE_EMBEDDING_API_KEY` env var
|
|
58
|
+
* 2. `<vault>/.vault-mind/vault-mind.env`
|
|
59
|
+
* 3. `vaultMind.embedding.remoteApiKey` in config
|
|
60
|
+
*
|
|
61
|
+
* `PVM_API_TOKEN` is intentionally not consulted — it is reserved for the
|
|
62
|
+
* extension's bridge authentication.
|
|
56
63
|
*
|
|
57
64
|
* Never log the resolved token.
|
|
58
65
|
*/
|
|
59
66
|
export const resolveModalToken = (cfg) => {
|
|
60
67
|
if (process.env[MODAL_TOKEN_ENV])
|
|
61
68
|
return process.env[MODAL_TOKEN_ENV];
|
|
62
|
-
const dotenv = readDotEnv(
|
|
69
|
+
const dotenv = readDotEnv(modalTokenEnvPath());
|
|
63
70
|
if (dotenv[MODAL_TOKEN_ENV])
|
|
64
71
|
return dotenv[MODAL_TOKEN_ENV];
|
|
65
|
-
return cfg.embedding
|
|
72
|
+
return cfg.embedding[EMBEDDING_FLAT_KEYS[7]];
|
|
66
73
|
};
|
|
67
74
|
/**
|
|
68
75
|
* Resolve a scoped Modal token for the requested operation.
|
|
69
76
|
* Resolution order:
|
|
70
77
|
* 1. `PVM_MODAL_{READ|WRITE}_TOKEN` env var
|
|
71
|
-
* 2.
|
|
72
|
-
* 3. `vaultMind.embedding.
|
|
73
|
-
* 4. Fallback to the unscoped `
|
|
78
|
+
* 2. `<vault>/.vault-mind/vault-mind.env`
|
|
79
|
+
* 3. `vaultMind.embedding.remoteReadApiKey` / `remoteWriteApiKey` in config
|
|
80
|
+
* 4. Fallback to the unscoped `PVM_REMOTE_EMBEDDING_API_KEY` (env, dotenv, then config)
|
|
81
|
+
*
|
|
82
|
+
* `PVM_API_TOKEN` is intentionally not consulted — it is reserved for the
|
|
83
|
+
* extension's bridge authentication.
|
|
74
84
|
*
|
|
75
85
|
* Never log the resolved token.
|
|
76
86
|
*/
|
|
77
87
|
export const getModalToken = (cfg, operation) => {
|
|
78
|
-
const
|
|
79
|
-
const dotenv = readDotEnv(MODAL_TOKEN_ENV_PATH);
|
|
88
|
+
const dotenv = readDotEnv(modalTokenEnvPath());
|
|
80
89
|
if (operation === "read") {
|
|
81
90
|
return (process.env[MODAL_READ_TOKEN_ENV] ||
|
|
82
91
|
dotenv[MODAL_READ_TOKEN_ENV] ||
|
|
83
|
-
|
|
92
|
+
cfg.embedding[EMBEDDING_FLAT_KEYS[8]] ||
|
|
84
93
|
resolveModalToken(cfg));
|
|
85
94
|
}
|
|
86
95
|
return (process.env[MODAL_WRITE_TOKEN_ENV] ||
|
|
87
96
|
dotenv[MODAL_WRITE_TOKEN_ENV] ||
|
|
88
|
-
|
|
97
|
+
cfg.embedding[EMBEDDING_FLAT_KEYS[9]] ||
|
|
89
98
|
resolveModalToken(cfg));
|
|
90
99
|
};
|
|
91
|
-
/**
|
|
100
|
+
/** Derive the remote URL from the top-level `remoteUrl` or the `workspace` shortcut. */
|
|
92
101
|
export const resolveBaseUrl = (cfg) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return modalUrl(modal.workspace);
|
|
102
|
+
if (cfg.embedding[EMBEDDING_FLAT_KEYS[0]])
|
|
103
|
+
return cfg.embedding[EMBEDDING_FLAT_KEYS[0]];
|
|
104
|
+
if (cfg.embedding[EMBEDDING_FLAT_KEYS[6]])
|
|
105
|
+
return modalUrl(cfg.embedding[EMBEDDING_FLAT_KEYS[6]]);
|
|
98
106
|
return undefined;
|
|
99
107
|
};
|
|
100
108
|
/** Resolve the configured workspace slug, if any. */
|
|
101
|
-
export const resolveWorkspace = (cfg) => cfg.embedding
|
|
109
|
+
export const resolveWorkspace = (cfg) => cfg.embedding[EMBEDDING_FLAT_KEYS[6]];
|
|
102
110
|
/** True when Modal is usable: a base URL (explicit or derived) and at least one resolvable scoped token are present. */
|
|
103
|
-
export const isModalConfigured = (cfg) => !!cfg.embedding
|
|
111
|
+
export const isModalConfigured = (cfg) => !!cfg.embedding[EMBEDDING_FLAT_KEYS[0]] &&
|
|
112
|
+
(!!getModalToken(cfg, "read") || !!getModalToken(cfg, "write"));
|
|
104
113
|
/**
|
|
105
114
|
* Build a `ModalEmbeddingClient` from config. Returns null when Modal is not
|
|
106
115
|
* configured (no base URL / token) so callers can degrade gracefully.
|
|
@@ -124,21 +133,39 @@ export const createModalClient = (cfg) => {
|
|
|
124
133
|
* hard-coded dimension).
|
|
125
134
|
*/
|
|
126
135
|
export const resolveModel = (cfg, collection) => {
|
|
127
|
-
const override = collection ? cfg.embedding
|
|
136
|
+
const override = collection ? cfg.embedding[EMBEDDING_FLAT_KEYS[12]]?.[collection] : undefined;
|
|
128
137
|
if (override?.model)
|
|
129
138
|
return override.model;
|
|
130
|
-
return cfg.embedding
|
|
139
|
+
return cfg.embedding[EMBEDDING_FLAT_KEYS[2]] || "embeddinggemma";
|
|
131
140
|
};
|
|
132
141
|
/**
|
|
133
142
|
* The configured output dimension for a collection, if known. Per-collection
|
|
134
|
-
* overrides win; otherwise the
|
|
135
|
-
* when unset — callers resolve the native dim lazily via `/models`.
|
|
143
|
+
* overrides win; otherwise the top-level `dim` on `EmbeddingConfig`.
|
|
144
|
+
* Returns undefined when unset — callers resolve the native dim lazily via `/models`.
|
|
136
145
|
*/
|
|
137
146
|
export const resolveDim = (cfg, collection) => {
|
|
138
|
-
const override = collection ? cfg.embedding
|
|
147
|
+
const override = collection ? cfg.embedding[EMBEDDING_FLAT_KEYS[12]]?.[collection] : undefined;
|
|
139
148
|
if (override?.dim != null)
|
|
140
149
|
return override.dim;
|
|
141
|
-
return cfg.embedding
|
|
150
|
+
return cfg.embedding[EMBEDDING_FLAT_KEYS[4]];
|
|
142
151
|
};
|
|
143
152
|
/** Namespaced local table name mirroring the server's ADR-3 scheme. */
|
|
144
153
|
export const namespacedTableName = (collection, model, dim) => `col_${collection}__${model}__${dim}`;
|
|
154
|
+
/**
|
|
155
|
+
* Resolve the server-side (Modal) collection table prefix for cross-vault
|
|
156
|
+
* isolation on a shared Modal deployment. Unset or `"auto"` derives it from
|
|
157
|
+
* the vault slug. The result is normalized (no `__`, no leading/trailing `_`)
|
|
158
|
+
* so it is safe for the server's `col_(.+)__(.+)__(\d+)$` parse.
|
|
159
|
+
*
|
|
160
|
+
* Applies ONLY to remote table names — local LanceDB tables stay unprefixed.
|
|
161
|
+
*/
|
|
162
|
+
export const resolveCollectionPrefix = (cfg, vaultPath) => {
|
|
163
|
+
const raw = cfg.vaults?.default?.collectionPrefix;
|
|
164
|
+
const base = raw && raw !== "auto" ? raw : deriveCollectionNameFromVaultPath(vaultPath);
|
|
165
|
+
return normalizeCollectionName(base) || undefined;
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Prepend the collection prefix to a collection name for server-side table
|
|
169
|
+
* identity. When `prefix` is undefined, returns the collection name unchanged.
|
|
170
|
+
*/
|
|
171
|
+
export const serverCollectionName = (prefix, collection) => prefix ? `${prefix}_${collection}` : collection;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-router config scaffolding — shared source of truth for the tuned
|
|
3
|
+
* default primary/fallback model sequence and the initial `model-router.json`
|
|
4
|
+
* shape.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from the Obsidian plugin (`packages/obsidian/src/main.ts`
|
|
7
|
+
* `defaultModelRouterChoices`/`scaffoldModelRouterConfig`), which previously
|
|
8
|
+
* carried the only implementation — a CLI-only setup got no equivalent
|
|
9
|
+
* scaffold at all. The plugin is expected to switch to calling this (or an
|
|
10
|
+
* equivalent extension-exposed path) in a future refactor rather than
|
|
11
|
+
* maintaining its own copy.
|
|
12
|
+
*/
|
|
13
|
+
/** Tuned default primary model + fallback sequence for the `auto` router profile. */
|
|
14
|
+
export declare const defaultModelRouterChoices: () => {
|
|
15
|
+
primary: string;
|
|
16
|
+
fallbackSequence: string[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Scaffold `model-router.json` at the vault-scoped agent dir
|
|
20
|
+
* (`resolveVaultMindPaths(cwd).modelRouter`) with tuned defaults, if it
|
|
21
|
+
* doesn't already exist. No-op (returns `created: false`) when the file is
|
|
22
|
+
* already present — never overwrites a user's router config.
|
|
23
|
+
*/
|
|
24
|
+
export declare const scaffoldModelRouterConfig: (cwd: string) => {
|
|
25
|
+
created: boolean;
|
|
26
|
+
path: string;
|
|
27
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-router config scaffolding — shared source of truth for the tuned
|
|
3
|
+
* default primary/fallback model sequence and the initial `model-router.json`
|
|
4
|
+
* shape.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from the Obsidian plugin (`packages/obsidian/src/main.ts`
|
|
7
|
+
* `defaultModelRouterChoices`/`scaffoldModelRouterConfig`), which previously
|
|
8
|
+
* carried the only implementation — a CLI-only setup got no equivalent
|
|
9
|
+
* scaffold at all. The plugin is expected to switch to calling this (or an
|
|
10
|
+
* equivalent extension-exposed path) in a future refactor rather than
|
|
11
|
+
* maintaining its own copy.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import { ensureDir, resolveVaultMindPaths } from "./utils.js";
|
|
15
|
+
/** Tuned default primary model + fallback sequence for the `auto` router profile. */
|
|
16
|
+
export const defaultModelRouterChoices = () => {
|
|
17
|
+
const isMacOS = process.platform === "darwin";
|
|
18
|
+
return {
|
|
19
|
+
primary: "ollama/gemma4:31b-cloud",
|
|
20
|
+
fallbackSequence: isMacOS
|
|
21
|
+
? ["ollama/gemma4:e4b", "ollama/gemma4:12b-mlx", "ollama/gemma3:4b", "ollama/*"]
|
|
22
|
+
: ["ollama/gemma4:e4b", "ollama/gemma4:12b", "ollama/gemma3:4b", "ollama/*"],
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Scaffold `model-router.json` at the vault-scoped agent dir
|
|
27
|
+
* (`resolveVaultMindPaths(cwd).modelRouter`) with tuned defaults, if it
|
|
28
|
+
* doesn't already exist. No-op (returns `created: false`) when the file is
|
|
29
|
+
* already present — never overwrites a user's router config.
|
|
30
|
+
*/
|
|
31
|
+
export const scaffoldModelRouterConfig = (cwd) => {
|
|
32
|
+
const { modelRouter: configPath } = resolveVaultMindPaths(cwd);
|
|
33
|
+
if (fs.existsSync(configPath))
|
|
34
|
+
return { created: false, path: configPath };
|
|
35
|
+
const { primary: mediumModel, fallbackSequence } = defaultModelRouterChoices();
|
|
36
|
+
const config = {
|
|
37
|
+
defaultProfile: "auto",
|
|
38
|
+
features: {
|
|
39
|
+
rateLimitFallback: true,
|
|
40
|
+
ollamaSync: false,
|
|
41
|
+
scopeShim: true,
|
|
42
|
+
perTurnRouting: false,
|
|
43
|
+
intentClassifier: false,
|
|
44
|
+
costBudgeting: false,
|
|
45
|
+
phaseMemory: false,
|
|
46
|
+
contextCompression: false,
|
|
47
|
+
},
|
|
48
|
+
rateLimitFallback: {
|
|
49
|
+
enabled: true,
|
|
50
|
+
shortDelayThreshold: 30,
|
|
51
|
+
autoFallback: true,
|
|
52
|
+
autoRestore: true,
|
|
53
|
+
restoreCheckInterval: 300,
|
|
54
|
+
fallbackSequence,
|
|
55
|
+
},
|
|
56
|
+
profiles: {
|
|
57
|
+
auto: {
|
|
58
|
+
high: { model: "ollama/gemma4:31b-cloud", thinking: "medium" },
|
|
59
|
+
medium: { model: mediumModel, thinking: "low" },
|
|
60
|
+
low: { model: "ollama/gemma4:e4b", thinking: "off" },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
ensureDir(configPath);
|
|
65
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
66
|
+
return { created: true, path: configPath };
|
|
67
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side reader for pi's `models.json` provider/model catalog.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the plugin's `packages/obsidian/src/chat/pi-config-reader.ts`
|
|
5
|
+
* (`readPiModelsConfig`) so the `GET /vault-mind/models` endpoint returns the
|
|
6
|
+
* same flattened `ProviderInfo[]` shape the plugin already consumes — letting
|
|
7
|
+
* it replace synchronous disk reads with a `VaultMindClient.getModels()` call.
|
|
8
|
+
*
|
|
9
|
+
* Reads two locations and merges (project overrides agent-dir):
|
|
10
|
+
* 1. Agent-dir: `resolveVaultMindPaths(vault).modelsJson`
|
|
11
|
+
* (= `<vault>/.vault-mind/.pi/agent/models.json`)
|
|
12
|
+
* 2. Project: `<vault>/.pi/models.json`
|
|
13
|
+
*
|
|
14
|
+
* Uses plain `JSON.parse` (consistent with the `/vm doctor` reader in
|
|
15
|
+
* commands.ts) — pi writes machine-generated strict JSON. A parse failure
|
|
16
|
+
* degrades gracefully to `{ providers: [], error }`.
|
|
17
|
+
*/
|
|
18
|
+
/** A single model entry, flattened for UI/transport. */
|
|
19
|
+
export interface ModelInfo {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
reasoning?: boolean;
|
|
23
|
+
contextWindow?: number;
|
|
24
|
+
}
|
|
25
|
+
/** A provider entry with its models and the env var it expects for auth. */
|
|
26
|
+
export interface ProviderInfo {
|
|
27
|
+
name: string;
|
|
28
|
+
/** Env var name the provider expects. Only exact $NAME or ${NAME} references are accepted; all others yield "". */
|
|
29
|
+
envVarName: string;
|
|
30
|
+
baseUrl?: string;
|
|
31
|
+
api?: string;
|
|
32
|
+
models: ModelInfo[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read + merge pi's `models.json` for a vault and flatten to `ProviderInfo[]`.
|
|
36
|
+
* Returns `{ providers, error? }`; `error` is set only when a config file
|
|
37
|
+
* exists but cannot be parsed (matching the plugin's contract).
|
|
38
|
+
*/
|
|
39
|
+
export declare const readModelProviders: (vaultPath: string) => {
|
|
40
|
+
providers: ProviderInfo[];
|
|
41
|
+
error?: string;
|
|
42
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side reader for pi's `models.json` provider/model catalog.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the plugin's `packages/obsidian/src/chat/pi-config-reader.ts`
|
|
5
|
+
* (`readPiModelsConfig`) so the `GET /vault-mind/models` endpoint returns the
|
|
6
|
+
* same flattened `ProviderInfo[]` shape the plugin already consumes — letting
|
|
7
|
+
* it replace synchronous disk reads with a `VaultMindClient.getModels()` call.
|
|
8
|
+
*
|
|
9
|
+
* Reads two locations and merges (project overrides agent-dir):
|
|
10
|
+
* 1. Agent-dir: `resolveVaultMindPaths(vault).modelsJson`
|
|
11
|
+
* (= `<vault>/.vault-mind/.pi/agent/models.json`)
|
|
12
|
+
* 2. Project: `<vault>/.pi/models.json`
|
|
13
|
+
*
|
|
14
|
+
* Uses plain `JSON.parse` (consistent with the `/vm doctor` reader in
|
|
15
|
+
* commands.ts) — pi writes machine-generated strict JSON. A parse failure
|
|
16
|
+
* degrades gracefully to `{ providers: [], error }`.
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from "node:fs";
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
import { resolveVaultMindPaths } from "./utils.js";
|
|
21
|
+
/**
|
|
22
|
+
* Read + merge pi's `models.json` for a vault and flatten to `ProviderInfo[]`.
|
|
23
|
+
* Returns `{ providers, error? }`; `error` is set only when a config file
|
|
24
|
+
* exists but cannot be parsed (matching the plugin's contract).
|
|
25
|
+
*/
|
|
26
|
+
export const readModelProviders = (vaultPath) => {
|
|
27
|
+
const merged = {};
|
|
28
|
+
let error;
|
|
29
|
+
let configExists = false;
|
|
30
|
+
const agentModelsPath = resolveVaultMindPaths(vaultPath).modelsJson;
|
|
31
|
+
const projectModelsPath = path.join(vaultPath, ".pi", "models.json");
|
|
32
|
+
for (const [label, file] of [
|
|
33
|
+
["agent-dir", agentModelsPath],
|
|
34
|
+
["project", projectModelsPath],
|
|
35
|
+
]) {
|
|
36
|
+
try {
|
|
37
|
+
if (!fs.existsSync(file))
|
|
38
|
+
continue;
|
|
39
|
+
configExists = true;
|
|
40
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
41
|
+
if (parsed.providers) {
|
|
42
|
+
// Project-level overrides agent-dir for same provider names.
|
|
43
|
+
Object.assign(merged, parsed.providers);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
error = `Failed to read ${label} models.json: ${err instanceof Error ? err.message : String(err)}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (configExists && Object.keys(merged).length === 0 && error) {
|
|
51
|
+
return { providers: [], error };
|
|
52
|
+
}
|
|
53
|
+
const providers = Object.entries(merged).map(([name, config]) => {
|
|
54
|
+
// Only exact shell-style references ($VAR_NAME or ${VAR_NAME}) are converted to raw
|
|
55
|
+
// env var names; all other apiKey values yield an empty string to prevent literal leakage.
|
|
56
|
+
let envVarName = "";
|
|
57
|
+
if (typeof config.apiKey === "string") {
|
|
58
|
+
const match = config.apiKey.match(/^\$([A-Za-z_][A-Za-z0-9_]*)$/) ||
|
|
59
|
+
config.apiKey.match(/^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/);
|
|
60
|
+
if (match)
|
|
61
|
+
envVarName = match[1];
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
name,
|
|
65
|
+
envVarName,
|
|
66
|
+
baseUrl: config.baseUrl,
|
|
67
|
+
api: config.api,
|
|
68
|
+
models: (config.models ?? []).map((m) => ({
|
|
69
|
+
id: m.id,
|
|
70
|
+
name: m.name || m.id,
|
|
71
|
+
reasoning: m.reasoning,
|
|
72
|
+
contextWindow: m.contextWindow,
|
|
73
|
+
})),
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
providers.sort((a, b) => a.name.localeCompare(b.name));
|
|
77
|
+
return { providers, error };
|
|
78
|
+
};
|
package/dist/src/personalize.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { getStatus } from "./lance.js";
|
|
5
|
-
import { expandHome, loadConfig } from "./utils.js";
|
|
5
|
+
import { expandHome, loadConfig, resolveAgentDir } from "./utils.js";
|
|
6
6
|
/** Resolve the vault path: configured default vault wins, otherwise ctx.cwd. */
|
|
7
7
|
export const resolveVaultPath = (ctx) => {
|
|
8
8
|
const cfg = loadConfig(ctx.cwd);
|
|
@@ -11,7 +11,7 @@ export const resolveVaultPath = (ctx) => {
|
|
|
11
11
|
};
|
|
12
12
|
/** Load current agent configuration files if they exist. */
|
|
13
13
|
export const loadCurrentConfig = (vaultPath) => {
|
|
14
|
-
const systemMdPath = path.join(vaultPath, "
|
|
14
|
+
const systemMdPath = path.join(resolveAgentDir(vaultPath), "system.md");
|
|
15
15
|
const agentsMdPath = path.join(vaultPath, "AGENTS.md");
|
|
16
16
|
return {
|
|
17
17
|
systemMd: fs.existsSync(systemMdPath) ? fs.readFileSync(systemMdPath, "utf-8") : null,
|
|
@@ -305,6 +305,20 @@ const resolveCurrentContent = (filePath, currentConfig) => {
|
|
|
305
305
|
}
|
|
306
306
|
return null;
|
|
307
307
|
};
|
|
308
|
+
/**
|
|
309
|
+
* Resolve a suggestion's relative path to its on-disk location. The framework
|
|
310
|
+
* `system.md` lives in the resolved agent dir (which may be outside the vault),
|
|
311
|
+
* while everything else (AGENTS.md, etc.) is vault-relative.
|
|
312
|
+
*/
|
|
313
|
+
const resolveSuggestionPath = (filePath, vaultPath) => {
|
|
314
|
+
if (path.isAbsolute(filePath))
|
|
315
|
+
return filePath;
|
|
316
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
317
|
+
if (normalized === ".pi/agent/system.md" || normalized.endsWith("/.pi/agent/system.md")) {
|
|
318
|
+
return path.join(resolveAgentDir(vaultPath), "system.md");
|
|
319
|
+
}
|
|
320
|
+
return path.join(vaultPath, filePath);
|
|
321
|
+
};
|
|
308
322
|
/** Present each suggestion diff to the user and write approved files. */
|
|
309
323
|
export const presentAndApplyDiff = async (suggestions, ctx, currentConfig) => {
|
|
310
324
|
const approved = [];
|
|
@@ -321,7 +335,7 @@ export const presentAndApplyDiff = async (suggestions, ctx, currentConfig) => {
|
|
|
321
335
|
const message = [suggestions.commentary, diff].filter(Boolean).join("\n\n");
|
|
322
336
|
const ok = await ctx.ui.confirm(title, message);
|
|
323
337
|
if (ok) {
|
|
324
|
-
const fullPath =
|
|
338
|
+
const fullPath = resolveSuggestionPath(file.path, vaultPath);
|
|
325
339
|
const dir = path.dirname(fullPath);
|
|
326
340
|
if (!fs.existsSync(dir))
|
|
327
341
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault config scaffolding — the single, UI-agnostic implementation of
|
|
3
|
+
* "create/merge-safe-update the config + collection files + injector
|
|
4
|
+
* artifacts + vault-root .gitignore." Shared by every entry point that needs
|
|
5
|
+
* to scaffold a vault: the `/vm setup` CLI command (`commands.ts`), the HTTP
|
|
6
|
+
* `/vm/setup` API route (`server.ts`), and any future consumer (e.g. an
|
|
7
|
+
* Obsidian plugin refactor) — none of them should carry their own copy of
|
|
8
|
+
* the collections/injectors template.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Vault-root `.gitignore` entries that keep pi-vault-mind compatible with
|
|
12
|
+
* `obsidian-git` (and, transitively, Obsidian Sync setups that also back up via
|
|
13
|
+
* git). The `.vault-mind/` directory holds local state (token, index, framework
|
|
14
|
+
* dir) that must never be committed, EXCEPT the hand-tuned config file, which is
|
|
15
|
+
* re-included via a `!` negation. The LanceDB index is a large, per-device,
|
|
16
|
+
* rebuildable binary; Obsidian's workspace UI state churns constantly. See
|
|
17
|
+
* docs/OBSIDIAN_SETUP.md §6.
|
|
18
|
+
*/
|
|
19
|
+
export declare const GITIGNORE_ENTRIES: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Decide what to do with the vault `.gitignore`, given its current contents
|
|
22
|
+
* (`null` if absent). Pure — does no I/O — so it is unit-testable. Creates the
|
|
23
|
+
* file when missing, appends only the entries that aren't already present
|
|
24
|
+
* (line-exact match, trimmed), and skips when everything is covered.
|
|
25
|
+
*
|
|
26
|
+
* A bare `.vault-mind/` directory exclude would block the per-file config
|
|
27
|
+
* negation (git cannot re-include a file inside an ignored directory), so when
|
|
28
|
+
* one is present it is rewritten to `.vault-mind/*` and the whole file returned.
|
|
29
|
+
*/
|
|
30
|
+
export declare const planGitignore: (existing: string | null) => {
|
|
31
|
+
action: "create" | "append" | "rewrite" | "skip";
|
|
32
|
+
content: string;
|
|
33
|
+
};
|
|
34
|
+
export interface ScaffoldResult {
|
|
35
|
+
created: string[];
|
|
36
|
+
updated: string[];
|
|
37
|
+
skipped: string[];
|
|
38
|
+
/** The resolved default-collection file name (vault-derived unless overridden). */
|
|
39
|
+
collectionName: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Scaffold (or merge-safe-update) `.vault-mind/vault-mind.config.json`,
|
|
43
|
+
* the default collection files, injector artifacts, and vault-root
|
|
44
|
+
* `.gitignore` for `vaultPath`. UI-agnostic — returns what happened instead
|
|
45
|
+
* of notifying; callers (a CLI command, an HTTP handler, anything else)
|
|
46
|
+
* render the result however fits their surface.
|
|
47
|
+
*/
|
|
48
|
+
export declare const scaffoldVaultConfig: (vaultPath: string, collectionOverride?: string) => ScaffoldResult;
|