pi-vault-mind 0.16.28 → 0.16.30
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 +28 -0
- package/README.md +185 -1
- package/dist/src/commands.js +81 -0
- package/dist/src/identities-config.js +20 -4
- package/dist/src/identity-injector.d.ts +13 -2
- package/dist/src/identity-injector.js +42 -28
- package/dist/src/scaffold.js +29 -29
- package/dist/src/server.js +21 -12
- package/dist/src/types.d.ts +8 -0
- package/dist/src/utils.js +15 -0
- package/dist/test/commands.test.js +78 -0
- package/dist/test/identity-injector.test.js +86 -148
- package/dist/test/pvm-cli.test.js +88 -0
- package/dist/test/rest-vm.test.js +24 -0
- package/dist/test/utils.test.js +13 -0
- package/dist/test/vault-pi-script.test.js +101 -0
- package/package.json +4 -1
- package/scripts/pvm.mjs +80 -0
- package/scripts/vault-pi.sh +76 -12
- package/skills/.agents/broadcaster.agent.md +0 -31
- package/skills/.agents/heavy-lifter.agent.md +0 -31
- package/skills/.agents/manager.agent.md +0 -30
- package/skills/.agents/miner.agent.md +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.30 — 2026-08-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **First-class `pvm` executable.** The npm package now exposes `pvm run [vault-path]`; it uses the current directory when the vault path is omitted and supports both global installation and `npx pi-vault-mind run`.
|
|
8
|
+
- **Explicit launch modes.** `pvm run` stays in the current terminal by default, with `--same-terminal`, `--new-window`, and `--auto` overrides.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Portable vault launcher.** `vault-pi.sh` now handles interactive terminals directly, normalizes relative vault paths before deriving runtime state, removes inline-session PID files on exit, and rejects unknown flags instead of treating them as vault paths.
|
|
13
|
+
|
|
14
|
+
### Verification
|
|
15
|
+
|
|
16
|
+
- Root and Obsidian builds passed; full test suite: 910/910 passing.
|
|
17
|
+
- `npm pack` includes `scripts/pvm.mjs` as an executable, and `npm exec` resolved the packed tarball's `pvm` command successfully.
|
|
18
|
+
|
|
19
|
+
## 0.16.29 — 2026-08-27
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Main-terminal identity isolation.** Ambient bundled specialist skills no longer make the interactive Pi session inherit the first matching specialist boundary. Identity injection now defaults to the unrestricted main agent and only activates from an explicit vault or environment selection.
|
|
24
|
+
- **Explicit persona controls.** Added `/vm identity status | set <role> | on | off`, `VAULT_MIND_AGENT_ROLE`, and `VAULT_MIND_IDENTITY_INJECTION`, with canonical persistence under `vaultMind.identities`.
|
|
25
|
+
- **Identity configuration migration and write safety.** Legacy top-level identity settings migrate without losing role allowlists, new scaffolds use the canonical path, and malformed JSON is reported without overwriting the existing vault configuration or leaking an unhandled HTTP rejection.
|
|
26
|
+
|
|
27
|
+
### Verification
|
|
28
|
+
|
|
29
|
+
- Root and Obsidian builds passed; full test suite: 899/899 passing.
|
|
30
|
+
- Real-vault startup probe confirmed ambient Broadcaster resources keep the unrestricted main identity while an explicit Broadcaster override activates the boundary.
|
|
3
31
|
|
|
4
32
|
## 0.16.28 — 2026-08-09
|
|
5
33
|
|
package/README.md
CHANGED
|
@@ -194,10 +194,25 @@ The Broadcaster agent can generate podcasts, study guides, and slide decks from
|
|
|
194
194
|
|
|
195
195
|
```bash
|
|
196
196
|
pi install npm:pi-vault-mind # latest
|
|
197
|
-
pi install npm:pi-vault-mind@0.
|
|
197
|
+
pi install npm:pi-vault-mind@0.16.30 # pinned release
|
|
198
198
|
pi -e npm:pi-vault-mind # try without installing
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
`pi install` loads the extension but does not expose its npm executable on your
|
|
202
|
+
shell `PATH`. Install the first-class launcher once when you want the bare
|
|
203
|
+
`pvm` command:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
npm install --global pi-vault-mind
|
|
207
|
+
pvm run /path/to/your/vault
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Or run it without a global installation:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npx --yes pi-vault-mind run /path/to/your/vault
|
|
214
|
+
```
|
|
215
|
+
|
|
201
216
|
Or from git:
|
|
202
217
|
|
|
203
218
|
```bash
|
|
@@ -269,6 +284,174 @@ Edit `<vault>/.vault-mind/vault-mind.config.json` to match your domain:
|
|
|
269
284
|
}
|
|
270
285
|
```
|
|
271
286
|
|
|
287
|
+
## Using Vault Mind agent identities
|
|
288
|
+
|
|
289
|
+
Vault Mind keeps the interactive Pi session on the unrestricted `main` persona
|
|
290
|
+
unless you explicitly select a specialist boundary. Bundled specialist skills
|
|
291
|
+
are available resources; merely loading them does not change the active
|
|
292
|
+
persona.
|
|
293
|
+
|
|
294
|
+
### Start the vault-local main agent
|
|
295
|
+
|
|
296
|
+
From the vault directory, start Pi in the current terminal:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
pvm run
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
You can launch a different vault from anywhere or use the package without a
|
|
303
|
+
global install:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
pvm run /path/to/your/vault
|
|
307
|
+
npx --yes pi-vault-mind run /path/to/your/vault
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`pvm run` defaults to the current terminal. This is the recommended Obsidian
|
|
311
|
+
Terminal profile command. Use a vault-path placeholder supplied by the plugin,
|
|
312
|
+
or provide the vault's absolute path. Override the launch mode when needed:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
pvm run --same-terminal /path/to/your/vault
|
|
316
|
+
pvm run --new-window /path/to/your/vault
|
|
317
|
+
pvm run --auto /path/to/your/vault
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`--new-window` opens a dedicated Terminal/iTerm window. `--auto` uses the
|
|
321
|
+
current terminal when attached to a TTY and falls back to a new window
|
|
322
|
+
otherwise. The packaged shell launcher remains available for direct use as
|
|
323
|
+
`scripts/vault-pi.sh`.
|
|
324
|
+
|
|
325
|
+
In every mode, the launcher runs Pi inside the target vault with its
|
|
326
|
+
configuration isolated under `<vault>/.vault-mind/.pi/agent/`. It also prevents
|
|
327
|
+
two launcher-managed Pi processes from running for the same vault and removes
|
|
328
|
+
the PID file when an inline session exits.
|
|
329
|
+
|
|
330
|
+
Check the active persona after startup:
|
|
331
|
+
|
|
332
|
+
```text
|
|
333
|
+
/vm identity status
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
A normal main-agent session reports:
|
|
337
|
+
|
|
338
|
+
```text
|
|
339
|
+
Persona: main
|
|
340
|
+
Effective boundary: none (main agent)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`Injection` may report `enabled` or `disabled`. An enabled injector still
|
|
344
|
+
applies no boundary while the persona is `main`.
|
|
345
|
+
|
|
346
|
+
### Use specialist agents without changing the main session
|
|
347
|
+
|
|
348
|
+
Normally, leave the interactive session on `main` and dispatch specialist work
|
|
349
|
+
through the passive watcher:
|
|
350
|
+
|
|
351
|
+
```text
|
|
352
|
+
@agent-Miner
|
|
353
|
+
Research and extract the durable facts from this note.
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
The watcher runs that work as an isolated specialist job. It does not turn the
|
|
357
|
+
interactive terminal into the Miner persona. The same distinction applies to
|
|
358
|
+
`@agent-Broadcaster`, `@agent-Manager`, and `@agent-Heavy-Lifter`.
|
|
359
|
+
|
|
360
|
+
### Explicitly change the interactive persona
|
|
361
|
+
|
|
362
|
+
Use an identity boundary only when the current interactive session itself
|
|
363
|
+
should follow a specialist capability contract:
|
|
364
|
+
|
|
365
|
+
```text
|
|
366
|
+
/vm identity set miner
|
|
367
|
+
/vm identity set broadcaster
|
|
368
|
+
/vm identity set manager
|
|
369
|
+
/vm identity set heavy-lifter
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
| Persona | Intended interactive boundary |
|
|
373
|
+
|---|---|
|
|
374
|
+
| `main` | Unrestricted primary agent with the full configured toolset |
|
|
375
|
+
| `manager` | Orchestrates collections, publishing, and subagents; does not run Bash |
|
|
376
|
+
| `miner` | Extracts and stores knowledge; does not run Bash or publish |
|
|
377
|
+
| `broadcaster` | Produces presentation artifacts; does not run Bash or write durable knowledge |
|
|
378
|
+
| `heavy-lifter` | Performs file and Bash work in isolation; does not write durable knowledge or publish |
|
|
379
|
+
|
|
380
|
+
`/vm identity set <role>` persists the selected persona, enables boundary
|
|
381
|
+
injection, and applies on the next agent turn.
|
|
382
|
+
|
|
383
|
+
Use the remaining controls to inspect or suspend that selection:
|
|
384
|
+
|
|
385
|
+
```text
|
|
386
|
+
/vm identity status
|
|
387
|
+
/vm identity off
|
|
388
|
+
/vm identity on
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
- `off` disables boundary injection without discarding the selected persona.
|
|
392
|
+
- `on` re-enables the configured persona boundary on the next agent turn.
|
|
393
|
+
- `status` shows the configured persona, injection switch, effective boundary,
|
|
394
|
+
and any environment overrides.
|
|
395
|
+
|
|
396
|
+
Restore the safe main-agent default with:
|
|
397
|
+
|
|
398
|
+
```text
|
|
399
|
+
/vm identity set main
|
|
400
|
+
/vm identity off
|
|
401
|
+
/vm identity status
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The final status should show `Effective boundary: none (main agent)`.
|
|
405
|
+
|
|
406
|
+
### Configuration and environment overrides
|
|
407
|
+
|
|
408
|
+
Identity controls are stored in the vault-local configuration:
|
|
409
|
+
|
|
410
|
+
```json
|
|
411
|
+
{
|
|
412
|
+
"vaultMind": {
|
|
413
|
+
"identities": {
|
|
414
|
+
"activeRole": "main",
|
|
415
|
+
"injectBoundaries": false
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The canonical path is
|
|
422
|
+
`<vault>/.vault-mind/vault-mind.config.json` under
|
|
423
|
+
`vaultMind.identities`. Identity commands preserve any existing
|
|
424
|
+
`vaultMind.identities.roles` allowlists.
|
|
425
|
+
|
|
426
|
+
Launch automation and explicit specialist forks can override the saved values
|
|
427
|
+
for one process:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
VAULT_MIND_AGENT_ROLE=miner VAULT_MIND_IDENTITY_INJECTION=on pi
|
|
431
|
+
VAULT_MIND_IDENTITY_INJECTION=off pi
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
`VAULT_MIND_AGENT_ROLE` takes precedence over `activeRole`.
|
|
435
|
+
`VAULT_MIND_IDENTITY_INJECTION=off` suppresses every boundary, while `on`
|
|
436
|
+
enables the explicitly selected role. Missing, `main`, `none`, and unknown
|
|
437
|
+
roles all resolve safely to the unrestricted main agent.
|
|
438
|
+
|
|
439
|
+
### Troubleshoot an unexpected specialist boundary
|
|
440
|
+
|
|
441
|
+
1. Run `/vm identity status` and check both the configured persona and any
|
|
442
|
+
reported environment overrides.
|
|
443
|
+
2. If an environment override is present, exit Pi, unset
|
|
444
|
+
`VAULT_MIND_AGENT_ROLE` and `VAULT_MIND_IDENTITY_INJECTION`, then relaunch.
|
|
445
|
+
3. Run `/vm identity set main`, followed by `/vm identity off`.
|
|
446
|
+
4. Send the next agent turn and re-run `/vm identity status`; boundary changes
|
|
447
|
+
take effect on the next turn.
|
|
448
|
+
5. Confirm Pi was started for the intended vault and uses
|
|
449
|
+
`<vault>/.vault-mind/.pi/agent/` as `PI_CODING_AGENT_DIR`.
|
|
450
|
+
|
|
451
|
+
Do not edit `SYSTEM.md` or an agent skill to change the interactive identity.
|
|
452
|
+
Those files provide operating instructions and resources; persona selection
|
|
453
|
+
belongs in `vaultMind.identities` or an explicit environment override.
|
|
454
|
+
|
|
272
455
|
## Example domains
|
|
273
456
|
|
|
274
457
|
### Research project
|
|
@@ -356,6 +539,7 @@ Edit `<vault>/.vault-mind/vault-mind.config.json` to match your domain:
|
|
|
356
539
|
| `/vm injector create` | Interactive wizard to create a new injector |
|
|
357
540
|
| `/vm context enable \| disable \| status` | Manage pi-context integration |
|
|
358
541
|
| `/vm embedding status \| use \| model \| models \| pull` | Manage embedding provider |
|
|
542
|
+
| `/vm identity status \| set <role> \| on \| off` | Inspect or explicitly control the interactive agent persona boundary |
|
|
359
543
|
| `/vm remote status \| config \| sync \| jobs \| migrate` | Manage remote embedding + vector sync |
|
|
360
544
|
| `/vm watcher start \| stop \| status` | Manage the passive file watcher |
|
|
361
545
|
| `/vm server status` | Show HTTP server status, port, and uptime |
|
package/dist/src/commands.js
CHANGED
|
@@ -8,6 +8,8 @@ import { revokeToken, rotateToken } from "./auth.js";
|
|
|
8
8
|
import { handleDiscoverSchema } from "./discover-schema.js";
|
|
9
9
|
import { writeEnvEntry } from "./embedding-secrets.js";
|
|
10
10
|
import { queryGraph } from "./graph.js";
|
|
11
|
+
import { writeIdentitiesLayer } from "./identities-config.js";
|
|
12
|
+
import { IDENTITY_BOUNDARY_ROLES, resolveIdentityBoundaryRole } from "./identity-injector.js";
|
|
11
13
|
import { runAskIntake } from "./intake.js";
|
|
12
14
|
import { connect, searchFts, searchHybrid, searchHybridRanked } from "./lance.js";
|
|
13
15
|
import { createModalClient, isModalConfigured, MODAL_TOKEN_ENV, modalTokenEnvPath, resolveModalToken, } from "./modal-config.js";
|
|
@@ -39,6 +41,9 @@ const VM_USAGE = [
|
|
|
39
41
|
" /vm index [--all] [--reembed] [--remote] Index collections (alias: reindex)",
|
|
40
42
|
" /vm discover-schema Infer schema from a JSONL file",
|
|
41
43
|
" /vm injector create Create a new injector (wizard)",
|
|
44
|
+
" /vm identity status Show configured and effective persona",
|
|
45
|
+
" /vm identity set <role> Set main|manager|miner|broadcaster|heavy-lifter",
|
|
46
|
+
" /vm identity on|off Enable or disable persona boundary injection",
|
|
42
47
|
" /vm context enable|disable Enable/disable pi-context integration",
|
|
43
48
|
" /vm embedding status Show embedding config",
|
|
44
49
|
" /vm remote status Show remote provider config + health + collections",
|
|
@@ -1616,6 +1621,74 @@ const handleToken = (args, ctx) => {
|
|
|
1616
1621
|
ctx.ui.notify(`Unknown: ${sub}. Try: show, rotate, revoke`, "error");
|
|
1617
1622
|
}
|
|
1618
1623
|
};
|
|
1624
|
+
const handleIdentity = (args, ctx) => {
|
|
1625
|
+
const parts = args.trim().split(/\s+/g).filter(Boolean);
|
|
1626
|
+
const subcommand = parts[0]?.toLowerCase() || "status";
|
|
1627
|
+
const identities = loadConfig(ctx.cwd).vaultMind.identities;
|
|
1628
|
+
const configuredRole = identities?.activeRole?.trim().toLowerCase() || "main";
|
|
1629
|
+
const updateIdentity = (patch) => {
|
|
1630
|
+
try {
|
|
1631
|
+
writeIdentitiesLayer(ctx.cwd, patch);
|
|
1632
|
+
return true;
|
|
1633
|
+
}
|
|
1634
|
+
catch (error) {
|
|
1635
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1636
|
+
ctx.ui.notify(`Failed to update identity config: ${message}`, "error");
|
|
1637
|
+
return false;
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
switch (subcommand) {
|
|
1641
|
+
case "status": {
|
|
1642
|
+
const effectiveRole = resolveIdentityBoundaryRole(ctx.cwd);
|
|
1643
|
+
const injectionEnabled = identities?.injectBoundaries !== false;
|
|
1644
|
+
const envRole = process.env.VAULT_MIND_AGENT_ROLE?.trim();
|
|
1645
|
+
const envInjection = process.env.VAULT_MIND_IDENTITY_INJECTION?.trim();
|
|
1646
|
+
ctx.ui.notify([
|
|
1647
|
+
"**Identity Boundary**",
|
|
1648
|
+
`Persona: ${configuredRole}`,
|
|
1649
|
+
`Injection: ${injectionEnabled ? "enabled" : "disabled"}`,
|
|
1650
|
+
`Effective boundary: ${effectiveRole ?? "none (main agent)"}`,
|
|
1651
|
+
...(envRole ? [`Environment role override: ${envRole}`] : []),
|
|
1652
|
+
...(envInjection ? [`Environment injection override: ${envInjection}`] : []),
|
|
1653
|
+
].join("\n"), "info");
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
case "set": {
|
|
1657
|
+
const role = parts[1]?.toLowerCase();
|
|
1658
|
+
const validRole = role === "main" ||
|
|
1659
|
+
(role !== undefined &&
|
|
1660
|
+
IDENTITY_BOUNDARY_ROLES.includes(role));
|
|
1661
|
+
if (!role || !validRole) {
|
|
1662
|
+
ctx.ui.notify(`Unknown persona: ${role ?? "(missing)"}. Use main, ${IDENTITY_BOUNDARY_ROLES.join(", ")}.`, "error");
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
if (!updateIdentity({
|
|
1666
|
+
activeRole: role,
|
|
1667
|
+
injectBoundaries: true,
|
|
1668
|
+
})) {
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
ctx.ui.notify(`Persona set to ${role}. The boundary applies on the next agent turn.`, "info");
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
case "off":
|
|
1675
|
+
case "disable": {
|
|
1676
|
+
if (!updateIdentity({ injectBoundaries: false }))
|
|
1677
|
+
return;
|
|
1678
|
+
ctx.ui.notify("Identity injection disabled. The main agent remains unrestricted.", "info");
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
case "on":
|
|
1682
|
+
case "enable": {
|
|
1683
|
+
if (!updateIdentity({ injectBoundaries: true }))
|
|
1684
|
+
return;
|
|
1685
|
+
ctx.ui.notify(`Identity injection enabled for ${configuredRole}. The setting applies on the next agent turn.`, "info");
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
default:
|
|
1689
|
+
ctx.ui.notify("Usage: /vm identity status | set <role> | on | off", "error");
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1619
1692
|
// ── Main /vm command ───────────────────────────────────────────────────────
|
|
1620
1693
|
export const registerCommands = (pi) => {
|
|
1621
1694
|
pi.registerCommand("vm", {
|
|
@@ -1633,6 +1706,7 @@ export const registerCommands = (pi) => {
|
|
|
1633
1706
|
"discover-schema",
|
|
1634
1707
|
"doctor",
|
|
1635
1708
|
"injector",
|
|
1709
|
+
"identity",
|
|
1636
1710
|
"context",
|
|
1637
1711
|
"embedding",
|
|
1638
1712
|
"remote",
|
|
@@ -1662,6 +1736,11 @@ export const registerCommands = (pi) => {
|
|
|
1662
1736
|
.filter((c) => c.startsWith(prefix))
|
|
1663
1737
|
.map((c) => ({ label: c, value: c, description: `context ${c}` }));
|
|
1664
1738
|
}
|
|
1739
|
+
if (subcommand === "identity") {
|
|
1740
|
+
return ["status", "set", "on", "off", "main", ...IDENTITY_BOUNDARY_ROLES]
|
|
1741
|
+
.filter((c) => c.startsWith(prefix))
|
|
1742
|
+
.map((c) => ({ label: c, value: c, description: `identity ${c}` }));
|
|
1743
|
+
}
|
|
1665
1744
|
if (subcommand === "discover-schema") {
|
|
1666
1745
|
return ["--name", "--sample"]
|
|
1667
1746
|
.filter((c) => c.startsWith(prefix))
|
|
@@ -1736,6 +1815,8 @@ export const registerCommands = (pi) => {
|
|
|
1736
1815
|
return handleCollection(rest, ctx, pi);
|
|
1737
1816
|
case "injector":
|
|
1738
1817
|
return handleInjector(rest, ctx);
|
|
1818
|
+
case "identity":
|
|
1819
|
+
return handleIdentity(rest, ctx);
|
|
1739
1820
|
case "context":
|
|
1740
1821
|
return handleContext(rest, ctx, pi);
|
|
1741
1822
|
case "embedding":
|
|
@@ -26,11 +26,27 @@ function deepMerge(base, overlay) {
|
|
|
26
26
|
*/
|
|
27
27
|
export function writeIdentitiesLayer(cwd, identities) {
|
|
28
28
|
const configPath = getConfigPath(cwd);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
let existing = {};
|
|
30
|
+
if (fs.existsSync(configPath)) {
|
|
31
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
35
|
+
throw new TypeError("the root value must be a JSON object");
|
|
36
|
+
}
|
|
37
|
+
existing = parsed;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new Error(`Invalid JSON in ${configPath}; the file was not changed.`, {
|
|
41
|
+
cause: error,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
32
45
|
const vaultMind = existing.vaultMind || {};
|
|
33
|
-
|
|
46
|
+
const legacyIdentities = existing.identities || {};
|
|
47
|
+
const canonicalIdentities = vaultMind.identities || {};
|
|
48
|
+
vaultMind.identities = deepMerge(deepMerge(legacyIdentities, canonicalIdentities), identities);
|
|
49
|
+
delete existing.identities;
|
|
34
50
|
existing.vaultMind = vaultMind;
|
|
35
51
|
const dir = path.dirname(configPath);
|
|
36
52
|
if (!fs.existsSync(dir))
|
|
@@ -6,10 +6,21 @@
|
|
|
6
6
|
* from pi-guideline-loader (kylebrodeur/pi-guideline-loader, MIT).
|
|
7
7
|
*/
|
|
8
8
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
export declare const IDENTITY_BOUNDARY_ROLES: readonly ["heavy-lifter", "miner", "broadcaster", "manager"];
|
|
10
|
+
export type IdentityBoundaryRole = (typeof IDENTITY_BOUNDARY_ROLES)[number];
|
|
11
|
+
/**
|
|
12
|
+
* Resolve an explicitly selected specialist persona.
|
|
13
|
+
*
|
|
14
|
+
* Specialist skills are ambient resources in every pi session, so their
|
|
15
|
+
* presence must never select a persona. The environment can override the
|
|
16
|
+
* vault-local config for launcher/fork use. Unset, "main", "none", and invalid
|
|
17
|
+
* roles all keep the unrestricted interactive main agent.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveIdentityBoundaryRole(cwd: string, env?: NodeJS.ProcessEnv): IdentityBoundaryRole | undefined;
|
|
9
20
|
/**
|
|
10
21
|
* Register the identity injector on a pi ExtensionAPI.
|
|
11
22
|
*
|
|
12
|
-
* On every before_agent_start event,
|
|
13
|
-
*
|
|
23
|
+
* On every before_agent_start event, injects a boundary only when a specialist
|
|
24
|
+
* persona was selected explicitly through environment or vault config.
|
|
14
25
|
*/
|
|
15
26
|
export declare function registerIdentityInjector(pi: ExtensionAPI): void;
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* per role stating capability boundaries as a contract. Pattern adapted
|
|
6
6
|
* from pi-guideline-loader (kylebrodeur/pi-guideline-loader, MIT).
|
|
7
7
|
*/
|
|
8
|
+
import { loadConfig } from "./utils.js";
|
|
8
9
|
// ── Per-role boundary prompts ────────────────────────────────────────────────
|
|
10
|
+
export const IDENTITY_BOUNDARY_ROLES = ["heavy-lifter", "miner", "broadcaster", "manager"];
|
|
9
11
|
const BOUNDARY_PROMPTS = {
|
|
10
12
|
"heavy-lifter": [
|
|
11
13
|
"--- IDENTITY BOUNDARY: Heavy-Lifter ---",
|
|
@@ -50,46 +52,58 @@ const BOUNDARY_PROMPTS = {
|
|
|
50
52
|
"------------------------------------------",
|
|
51
53
|
].join("\n"),
|
|
52
54
|
};
|
|
53
|
-
// ── Role
|
|
55
|
+
// ── Role resolution ──────────────────────────────────────────────────────────
|
|
56
|
+
const DISABLED_SWITCH_VALUES = {
|
|
57
|
+
"0": true,
|
|
58
|
+
false: true,
|
|
59
|
+
no: true,
|
|
60
|
+
off: true,
|
|
61
|
+
disabled: true,
|
|
62
|
+
};
|
|
63
|
+
const ENABLED_SWITCH_VALUES = {
|
|
64
|
+
"1": true,
|
|
65
|
+
true: true,
|
|
66
|
+
yes: true,
|
|
67
|
+
on: true,
|
|
68
|
+
enabled: true,
|
|
69
|
+
};
|
|
54
70
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* PRIMARY signal — event.systemPromptOptions.skills:
|
|
58
|
-
* pi loads `--agent vault-mind-{role}` as a named Skill (Skill.name == "vault-mind-{role}").
|
|
59
|
-
* Exact name match is structural and immune to prose false-positives.
|
|
60
|
-
*
|
|
61
|
-
* FALLBACK signal — system prompt XML:
|
|
62
|
-
* Only when no matching skill is present. Matches only the machine-generated
|
|
63
|
-
* `<name>vault-mind-{role}</name>` element emitted by formatSkillsForPrompt()
|
|
64
|
-
* inside <available_skills> — not any free-text prose mention of the role name.
|
|
71
|
+
* Resolve an explicitly selected specialist persona.
|
|
65
72
|
*
|
|
66
|
-
*
|
|
73
|
+
* Specialist skills are ambient resources in every pi session, so their
|
|
74
|
+
* presence must never select a persona. The environment can override the
|
|
75
|
+
* vault-local config for launcher/fork use. Unset, "main", "none", and invalid
|
|
76
|
+
* roles all keep the unrestricted interactive main agent.
|
|
67
77
|
*/
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
78
|
+
export function resolveIdentityBoundaryRole(cwd, env = process.env) {
|
|
79
|
+
const identities = loadConfig(cwd).vaultMind.identities;
|
|
80
|
+
const injectionOverride = env.VAULT_MIND_IDENTITY_INJECTION?.trim().toLowerCase();
|
|
81
|
+
if (injectionOverride && DISABLED_SWITCH_VALUES[injectionOverride])
|
|
82
|
+
return;
|
|
83
|
+
if ((!injectionOverride || !ENABLED_SWITCH_VALUES[injectionOverride]) &&
|
|
84
|
+
identities?.injectBoundaries === false) {
|
|
85
|
+
return;
|
|
77
86
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
const rawRole = Object.hasOwn(env, "VAULT_MIND_AGENT_ROLE")
|
|
88
|
+
? env.VAULT_MIND_AGENT_ROLE
|
|
89
|
+
: identities?.activeRole;
|
|
90
|
+
const role = rawRole?.trim().toLowerCase();
|
|
91
|
+
if (!role || role === "main" || role === "none")
|
|
92
|
+
return;
|
|
93
|
+
if (!Object.hasOwn(BOUNDARY_PROMPTS, role))
|
|
94
|
+
return;
|
|
95
|
+
return role;
|
|
82
96
|
}
|
|
83
97
|
// ── Injector ──────────────────────────────────────────────────────────────────
|
|
84
98
|
/**
|
|
85
99
|
* Register the identity injector on a pi ExtensionAPI.
|
|
86
100
|
*
|
|
87
|
-
* On every before_agent_start event,
|
|
88
|
-
*
|
|
101
|
+
* On every before_agent_start event, injects a boundary only when a specialist
|
|
102
|
+
* persona was selected explicitly through environment or vault config.
|
|
89
103
|
*/
|
|
90
104
|
export function registerIdentityInjector(pi) {
|
|
91
105
|
pi.on("before_agent_start", async (event) => {
|
|
92
|
-
const role =
|
|
106
|
+
const role = resolveIdentityBoundaryRole(event.systemPromptOptions.cwd);
|
|
93
107
|
if (!role)
|
|
94
108
|
return;
|
|
95
109
|
const boundaryPrompt = BOUNDARY_PROMPTS[role];
|
package/dist/src/scaffold.js
CHANGED
|
@@ -135,37 +135,37 @@ export const scaffoldVaultConfig = (vaultPath, collectionOverride) => {
|
|
|
135
135
|
version: 2,
|
|
136
136
|
collections: defaultCollections,
|
|
137
137
|
injectors: defaultInjectors,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
138
|
+
vaultMind: {
|
|
139
|
+
identities: {
|
|
140
|
+
roles: {
|
|
141
|
+
main: {
|
|
142
|
+
allowedTools: [
|
|
143
|
+
"read",
|
|
144
|
+
"write",
|
|
145
|
+
"edit",
|
|
146
|
+
"bash",
|
|
147
|
+
"grep",
|
|
148
|
+
"find",
|
|
149
|
+
"ls",
|
|
150
|
+
"vm_search",
|
|
151
|
+
"vm_query",
|
|
152
|
+
"vm_append",
|
|
153
|
+
"vm_stats",
|
|
154
|
+
"vm_status",
|
|
155
|
+
"vm_describe",
|
|
156
|
+
"vm_configure",
|
|
157
|
+
"vault_read",
|
|
158
|
+
"vault_search",
|
|
159
|
+
"vault_list",
|
|
160
|
+
"vault_tags",
|
|
161
|
+
"vm_backlinks",
|
|
162
|
+
"vm_related",
|
|
163
|
+
"vm_broken_links",
|
|
164
|
+
"vm_codegraph_impact",
|
|
165
|
+
],
|
|
166
|
+
},
|
|
165
167
|
},
|
|
166
168
|
},
|
|
167
|
-
},
|
|
168
|
-
vaultMind: {
|
|
169
169
|
dataDir: ".vault-mind/.lancedb",
|
|
170
170
|
ftsEnabled: true,
|
|
171
171
|
graph: { enabled: true, canvasSync: false },
|
package/dist/src/server.js
CHANGED
|
@@ -832,19 +832,28 @@ export function startServer(pi, serverState, watcherState, readinessCallback) {
|
|
|
832
832
|
if (parts.length === 5) {
|
|
833
833
|
const role = parts[3];
|
|
834
834
|
withAuth(req, res, async () => {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
835
|
+
try {
|
|
836
|
+
const body = (await readJsonBody(req));
|
|
837
|
+
if (!body.allowedTools || !Array.isArray(body.allowedTools)) {
|
|
838
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
839
|
+
res.end(JSON.stringify({ error: "Missing allowedTools array in request body" }));
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
// writeIdentitiesLayer deep-merges; pass a partial role config
|
|
843
|
+
writeIdentitiesLayer(process.cwd(), {
|
|
844
|
+
roles: { [role]: { allowedTools: body.allowedTools } },
|
|
845
|
+
});
|
|
846
|
+
refreshAgentIdentities([role]);
|
|
847
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
848
|
+
res.end(JSON.stringify({ ok: true, role, allowedTools: body.allowedTools }));
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
852
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
853
|
+
res.end(JSON.stringify({
|
|
854
|
+
error: `Failed to update identity config: ${message}`,
|
|
855
|
+
}));
|
|
840
856
|
}
|
|
841
|
-
// writeIdentitiesLayer deep-merges; pass a partial role config
|
|
842
|
-
writeIdentitiesLayer(process.cwd(), {
|
|
843
|
-
roles: { [role]: { allowedTools: body.allowedTools } },
|
|
844
|
-
});
|
|
845
|
-
refreshAgentIdentities([role]);
|
|
846
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
847
|
-
res.end(JSON.stringify({ ok: true, role, allowedTools: body.allowedTools }));
|
|
848
857
|
}, { requireWrite: true });
|
|
849
858
|
}
|
|
850
859
|
else {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -203,6 +203,10 @@ export interface VaultMindConfig {
|
|
|
203
203
|
defaultProfile?: IdentityConfig;
|
|
204
204
|
/** Per-role overrides. */
|
|
205
205
|
roles?: Record<string, IdentityConfig>;
|
|
206
|
+
/** Master switch for system-prompt identity-boundary injection. */
|
|
207
|
+
injectBoundaries?: boolean;
|
|
208
|
+
/** Explicit persona to inject; unset, "main", or "none" keeps the main agent. */
|
|
209
|
+
activeRole?: string;
|
|
206
210
|
};
|
|
207
211
|
/**
|
|
208
212
|
* Vault folder layout overrides. Each path is vault-relative and joined to
|
|
@@ -235,6 +239,10 @@ export interface IdentitiesConfig {
|
|
|
235
239
|
defaultProfile?: IdentityConfig;
|
|
236
240
|
/** Per-role overrides. */
|
|
237
241
|
roles?: Record<string, IdentityConfig>;
|
|
242
|
+
/** Master switch for system-prompt identity-boundary injection. */
|
|
243
|
+
injectBoundaries?: boolean;
|
|
244
|
+
/** Explicit persona to inject; unset, "main", or "none" keeps the main agent. */
|
|
245
|
+
activeRole?: string;
|
|
238
246
|
}
|
|
239
247
|
export interface PiContextDef {
|
|
240
248
|
enabled?: boolean;
|