pi-memory-stone 0.1.4 → 0.1.5
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/README.md +60 -6
- package/package.json +15 -4
- package/src/commands/index.ts +161 -2
- package/src/index.ts +79 -29
- package/src/vault/capture.ts +268 -0
- package/src/vault/extract.ts +259 -0
- package/src/vault/fetch.ts +155 -0
- package/src/vault/index.ts +306 -0
- package/src/vault/intent.ts +37 -0
- package/src/vault/markdown.ts +120 -0
- package/src/vault/paths.ts +44 -0
- package/src/vault/quality.ts +65 -0
- package/src/vault/url-resolvers.ts +113 -0
package/README.md
CHANGED
|
@@ -46,9 +46,10 @@ pi-memory-stone package source
|
|
|
46
46
|
│ ├── retrieval/ # FTS search, hybrid ranking, injection builder
|
|
47
47
|
│ ├── commands/ # /memory-* slash commands
|
|
48
48
|
│ ├── tools/ # LLM-callable tools
|
|
49
|
+
│ ├── vault/ # Optional Obsidian-compatible markdown vaults
|
|
49
50
|
│ ├── privacy/ # Secret redaction, sensitive path filtering
|
|
50
51
|
│ └── config/ # Project identity, settings
|
|
51
|
-
└── test/ #
|
|
52
|
+
└── test/ # tests across memory, privacy, portable, and vault helpers
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
## How It Works
|
|
@@ -116,6 +117,10 @@ Before each agent turn, the extension:
|
|
|
116
117
|
| `/memory-import <memory-export.json> --preserve-project` | | Import records while preserving exported project IDs |
|
|
117
118
|
| `/memory-import <memory-export.json> --global` | | Import all records as global memories |
|
|
118
119
|
| `/memory-backup [path]` | `/stone-backup` | Copy the SQLite memory database to a timestamped backup file |
|
|
120
|
+
| `/memory-vault-init [--project\|--personal]` | `/stone-vault-init` | Initialize an Obsidian-compatible markdown vault |
|
|
121
|
+
| `/memory-vault-sync [--project\|--personal]` | `/stone-vault-sync` | Generate vault pages from active memory records |
|
|
122
|
+
| `/memory-vault-status [--project\|--personal]` | `/stone-vault-status` | Show vault path, page counts, registry, and last sync |
|
|
123
|
+
| `/memory-vault-capture-url <url> [--project\|--personal]` | `/stone-vault-capture-url` | Capture a web page into the vault as a source page |
|
|
119
124
|
| `/memory-on` | | Enable memory injection for this session |
|
|
120
125
|
| `/memory-off` | | Disable memory injection for this session |
|
|
121
126
|
|
|
@@ -188,6 +193,52 @@ Use a database backup before pruning or hard deletion:
|
|
|
188
193
|
|
|
189
194
|
Import defaults are intentionally practical for machine moves: project-scoped records are remapped to the current project. Use `--preserve-project` to keep exported project IDs, or `--global` to import everything as global memory.
|
|
190
195
|
|
|
196
|
+
## Knowledge Vaults
|
|
197
|
+
|
|
198
|
+
Memory vaults are optional Obsidian-compatible markdown projections of active memory records. SQLite remains the source of truth; generated pages may be overwritten by `/memory-vault-sync`.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Project-local vault, written only after explicit init or capture request
|
|
202
|
+
/memory-vault-init --project
|
|
203
|
+
/memory-vault-sync --project
|
|
204
|
+
/memory-vault-status --project
|
|
205
|
+
|
|
206
|
+
# Capture a web page source into the vault
|
|
207
|
+
/memory-vault-capture-url https://example.com/article --project
|
|
208
|
+
|
|
209
|
+
# Capture resolves known source formats before generic HTML extraction.
|
|
210
|
+
# Examples: GitHub Gist pages are fetched from gist.githubusercontent.com/raw,
|
|
211
|
+
# GitHub blob URLs are fetched from raw.githubusercontent.com, and raw Markdown
|
|
212
|
+
# is preserved as Markdown.
|
|
213
|
+
|
|
214
|
+
# Natural-language capture also works from normal prompts:
|
|
215
|
+
# "Capture this article into vault https://example.com/article"
|
|
216
|
+
# "Add page to personal vault https://example.com/article"
|
|
217
|
+
|
|
218
|
+
# Private personal vault for global memories
|
|
219
|
+
/memory-vault-init --personal
|
|
220
|
+
/memory-vault-sync --personal
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Default locations:
|
|
224
|
+
|
|
225
|
+
```txt
|
|
226
|
+
<repo>/.memory-stone/vault/ # project vault
|
|
227
|
+
~/.pi/agent/memory/vaults/personal/ # personal vault
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Initial layout:
|
|
231
|
+
|
|
232
|
+
```txt
|
|
233
|
+
index.md
|
|
234
|
+
WIKI_SCHEMA.md
|
|
235
|
+
records/{decisions,preferences,tasks,error-resolutions,turn-summaries,session-summaries}/
|
|
236
|
+
sources/ # captured web source pages
|
|
237
|
+
meta/registry.json
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
URL capture writes curated source notes into `sources/`. Raw provenance packets (manifest, metadata, fetch attempts, original artifact, extracted markdown) are stored outside the Obsidian vault under `.memory-stone/source-packets/` for project vaults or `~/.pi/agent/memory/source-packets/personal/` for personal vaults. Capture extracts HTML articles with Mozilla Readability, converts to Markdown, redacts secrets, and marks extraction quality as `good` or `weak` with warnings.
|
|
241
|
+
|
|
191
242
|
## Privacy & Safety
|
|
192
243
|
|
|
193
244
|
### Secret Redaction
|
|
@@ -260,16 +311,18 @@ npm run typecheck
|
|
|
260
311
|
|
|
261
312
|
These scripts are also runnable from a pi package clone installed from git; the required script runners are regular dependencies because pi package installs omit `devDependencies`.
|
|
262
313
|
|
|
263
|
-
|
|
314
|
+
69 tests across 8 test files:
|
|
264
315
|
|
|
265
316
|
| Suite | Tests | Focus |
|
|
266
317
|
|---|---|---|
|
|
267
318
|
| `indexing.test.ts` | 1 | Incremental session indexing |
|
|
268
319
|
| `parser.test.ts` | 10 | Turn parsing, file activity detection, error extraction |
|
|
269
|
-
| `portable.test.ts` |
|
|
270
|
-
| `privacy.test.ts` |
|
|
271
|
-
| `ranking.test.ts` |
|
|
272
|
-
| `session-state.test.ts` |
|
|
320
|
+
| `portable.test.ts` | 5 | JSON/Markdown export, JSON import, SQLite backup |
|
|
321
|
+
| `privacy.test.ts` | 21 | Secret redaction, sensitive path filtering |
|
|
322
|
+
| `ranking.test.ts` | 18 | Hybrid ranking, cross-project filtering, injection formatting |
|
|
323
|
+
| `session-state.test.ts` | 5 | Injection mode config, session ref selection, manual-only injection |
|
|
324
|
+
| `tools.test.ts` | 2 | Tool visibility and forgetting safety |
|
|
325
|
+
| `vault.test.ts` | 7 | Vault path resolution, initialization, sync, URL capture, registry generation |
|
|
273
326
|
|
|
274
327
|
## Configuration
|
|
275
328
|
|
|
@@ -302,6 +355,7 @@ For manual-only memory, keep `enabled: true` and set `injectionMode: "manual"`.
|
|
|
302
355
|
- LLM extraction (decisions, preferences, tasks)
|
|
303
356
|
- Historical backfill (`/memory-backfill`)
|
|
304
357
|
- Embedding-based semantic search
|
|
358
|
+
- Vault backlinks/lint/search/capture commands
|
|
305
359
|
- `/memory-edit`, `/memory-supersede`
|
|
306
360
|
- Rich TUI memory browser
|
|
307
361
|
- Multi-machine sync
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-memory-stone",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Global pi extension: preserves and retrieves useful memory across pi sessions",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"keywords": [
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pi-package"
|
|
9
|
+
],
|
|
8
10
|
"files": [
|
|
9
11
|
"src",
|
|
10
12
|
"skills/**/*",
|
|
@@ -12,15 +14,24 @@
|
|
|
12
14
|
"LICENSE"
|
|
13
15
|
],
|
|
14
16
|
"pi": {
|
|
15
|
-
"extensions": [
|
|
16
|
-
|
|
17
|
+
"extensions": [
|
|
18
|
+
"./src/index.ts"
|
|
19
|
+
],
|
|
20
|
+
"skills": [
|
|
21
|
+
"./skills"
|
|
22
|
+
]
|
|
17
23
|
},
|
|
18
24
|
"scripts": {
|
|
19
25
|
"test": "NODE_OPTIONS='--experimental-sqlite' tsx --test test/*.test.ts",
|
|
20
26
|
"typecheck": "tsc --noEmit"
|
|
21
27
|
},
|
|
22
28
|
"dependencies": {
|
|
29
|
+
"@mozilla/readability": "^0.6.0",
|
|
30
|
+
"@types/jsdom": "^28.0.3",
|
|
31
|
+
"@types/turndown": "^5.0.6",
|
|
32
|
+
"jsdom": "^29.1.1",
|
|
23
33
|
"tsx": "^4.22.3",
|
|
34
|
+
"turndown": "^7.2.4",
|
|
24
35
|
"typescript": "^5.9.3"
|
|
25
36
|
},
|
|
26
37
|
"peerDependencies": {
|
package/src/commands/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Commands: /memory-status, /memory-search, /memory-open, /memory-inject, /memory-mode, /memory-last,
|
|
3
|
-
* /memory-export, /memory-import, /memory-backup
|
|
3
|
+
* /memory-export, /memory-import, /memory-backup, /memory-vault-*
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
@@ -24,6 +24,15 @@ import {
|
|
|
24
24
|
isRecordVisibleInProject,
|
|
25
25
|
parseRefArgs,
|
|
26
26
|
} from "../session-state/index.js";
|
|
27
|
+
import {
|
|
28
|
+
getVaultStatus,
|
|
29
|
+
initVault,
|
|
30
|
+
parseVaultScope,
|
|
31
|
+
syncVault,
|
|
32
|
+
type VaultScope,
|
|
33
|
+
} from "../vault/index.js";
|
|
34
|
+
import { captureUrlToVault } from "../vault/capture.js";
|
|
35
|
+
import { extractFirstUrl } from "../vault/intent.js";
|
|
27
36
|
|
|
28
37
|
export function registerCommands(pi: ExtensionAPI): void {
|
|
29
38
|
// ── /memory-status ──────────────────────────────────────────────
|
|
@@ -198,6 +207,64 @@ export function registerCommands(pi: ExtensionAPI): void {
|
|
|
198
207
|
},
|
|
199
208
|
});
|
|
200
209
|
|
|
210
|
+
// ── /memory-vault-* ─────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
pi.registerCommand("memory-vault-init", {
|
|
213
|
+
description: "Initialize an Obsidian-compatible memory vault",
|
|
214
|
+
handler: async (args, ctx) => {
|
|
215
|
+
await handleMemoryVaultInit(args, ctx);
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
pi.registerCommand("stone-vault-init", {
|
|
220
|
+
description: "Alias for /memory-vault-init",
|
|
221
|
+
handler: async (args, ctx) => {
|
|
222
|
+
await handleMemoryVaultInit(args, ctx);
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
pi.registerCommand("memory-vault-sync", {
|
|
227
|
+
description: "Sync active memory records into the initialized markdown vault",
|
|
228
|
+
handler: async (args, ctx) => {
|
|
229
|
+
await handleMemoryVaultSync(args, ctx);
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
pi.registerCommand("stone-vault-sync", {
|
|
234
|
+
description: "Alias for /memory-vault-sync",
|
|
235
|
+
handler: async (args, ctx) => {
|
|
236
|
+
await handleMemoryVaultSync(args, ctx);
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
pi.registerCommand("memory-vault-status", {
|
|
241
|
+
description: "Show memory vault path, initialization, and sync status",
|
|
242
|
+
handler: async (args, ctx) => {
|
|
243
|
+
await handleMemoryVaultStatus(args, ctx);
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
pi.registerCommand("memory-vault-capture-url", {
|
|
248
|
+
description: "Capture a web page URL into the memory vault",
|
|
249
|
+
handler: async (args, ctx) => {
|
|
250
|
+
await handleMemoryVaultCaptureUrl(args, ctx);
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
pi.registerCommand("stone-vault-status", {
|
|
255
|
+
description: "Alias for /memory-vault-status",
|
|
256
|
+
handler: async (args, ctx) => {
|
|
257
|
+
await handleMemoryVaultStatus(args, ctx);
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
pi.registerCommand("stone-vault-capture-url", {
|
|
262
|
+
description: "Alias for /memory-vault-capture-url",
|
|
263
|
+
handler: async (args, ctx) => {
|
|
264
|
+
await handleMemoryVaultCaptureUrl(args, ctx);
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
201
268
|
// ── /memory-on / /memory-off ────────────────────────────────────
|
|
202
269
|
|
|
203
270
|
pi.registerCommand("memory-on", {
|
|
@@ -481,6 +548,98 @@ async function handleMemoryBackup(args: string, ctx: ExtensionCommandContext): P
|
|
|
481
548
|
}
|
|
482
549
|
}
|
|
483
550
|
|
|
551
|
+
async function handleMemoryVaultInit(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
552
|
+
const parsed = parseCommandArgs(args);
|
|
553
|
+
const scope = parseVaultScopeOrNotify(parsed, ctx);
|
|
554
|
+
if (!scope) return;
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
const projectId = getProjectId(ctx.cwd);
|
|
558
|
+
const result = initVault(scope, projectId, ctx.cwd);
|
|
559
|
+
ctx.ui.notify(
|
|
560
|
+
result.created
|
|
561
|
+
? `Initialized ${scope} memory vault at ${result.path}`
|
|
562
|
+
: `${scope} memory vault already initialized at ${result.path}`,
|
|
563
|
+
"info",
|
|
564
|
+
);
|
|
565
|
+
} catch (err) {
|
|
566
|
+
ctx.ui.notify(`Memory vault init failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function handleMemoryVaultSync(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
571
|
+
const parsed = parseCommandArgs(args);
|
|
572
|
+
const scope = parseVaultScopeOrNotify(parsed, ctx);
|
|
573
|
+
if (!scope) return;
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
const projectId = getProjectId(ctx.cwd);
|
|
577
|
+
const result = syncVault(scope, projectId, ctx.cwd);
|
|
578
|
+
ctx.ui.notify(
|
|
579
|
+
`Synced ${result.records} memory records to ${result.path} (${result.pagesWritten} page${result.pagesWritten === 1 ? "" : "s"} written). Registry: ${result.registryPath}`,
|
|
580
|
+
"info",
|
|
581
|
+
);
|
|
582
|
+
} catch (err) {
|
|
583
|
+
ctx.ui.notify(`Memory vault sync failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function handleMemoryVaultStatus(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
588
|
+
const parsed = parseCommandArgs(args);
|
|
589
|
+
const scope = parseVaultScopeOrNotify(parsed, ctx);
|
|
590
|
+
if (!scope) return;
|
|
591
|
+
|
|
592
|
+
const projectId = getProjectId(ctx.cwd);
|
|
593
|
+
const status = getVaultStatus(scope, projectId, ctx.cwd);
|
|
594
|
+
const lines: string[] = [];
|
|
595
|
+
lines.push("📚 Memory Vault Status");
|
|
596
|
+
lines.push("");
|
|
597
|
+
lines.push(` scope: ${scope}`);
|
|
598
|
+
lines.push(` path: ${status.path}`);
|
|
599
|
+
lines.push(` initialized: ${status.initialized}`);
|
|
600
|
+
lines.push(` registry: ${status.registryExists ? "present" : "missing"}`);
|
|
601
|
+
lines.push(` markdown pages: ${status.pageCount}`);
|
|
602
|
+
lines.push(` synced record pages: ${status.recordPageCount}`);
|
|
603
|
+
lines.push(` last sync: ${status.lastSyncedAt ?? "never"}`);
|
|
604
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async function handleMemoryVaultCaptureUrl(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
608
|
+
const parsed = parseCommandArgs(args);
|
|
609
|
+
const scope = parseVaultScopeOrNotify(parsed, ctx);
|
|
610
|
+
if (!scope) return;
|
|
611
|
+
|
|
612
|
+
const url = extractFirstUrl(args);
|
|
613
|
+
if (!url) {
|
|
614
|
+
ctx.ui.notify("Usage: /memory-vault-capture-url <http-url> [--project|--personal]", "warning");
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
const projectId = getProjectId(ctx.cwd);
|
|
620
|
+
const result = await captureUrlToVault(scope, projectId, ctx.cwd, url);
|
|
621
|
+
const warnings = result.warnings.length > 0 ? `\nWarnings: ${result.warnings.join("; ")}` : "";
|
|
622
|
+
ctx.ui.notify(
|
|
623
|
+
`Captured ${result.title} into ${scope} memory vault${result.initialized ? " (initialized vault)" : ""}\nQuality: ${result.quality} (${result.qualityScore})${warnings}\nPage: ${result.pagePath}`,
|
|
624
|
+
"info",
|
|
625
|
+
);
|
|
626
|
+
} catch (err) {
|
|
627
|
+
ctx.ui.notify(`Memory vault URL capture failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function parseVaultScopeOrNotify(
|
|
632
|
+
parsed: ReturnType<typeof parseCommandArgs>,
|
|
633
|
+
ctx: ExtensionCommandContext,
|
|
634
|
+
): VaultScope | null {
|
|
635
|
+
const scope = parseVaultScope(parsed);
|
|
636
|
+
if (!scope) {
|
|
637
|
+
ctx.ui.notify("Usage: /memory-vault-<init|sync|status> [--project|--personal|--scope project|personal]", "warning");
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
return scope;
|
|
641
|
+
}
|
|
642
|
+
|
|
484
643
|
async function handleMemoryForget(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
485
644
|
const { softForgetRecord, hardDeleteRecord, getRecord } = await import("../db/index.js");
|
|
486
645
|
|
|
@@ -550,7 +709,7 @@ function parseCommandArgs(args: string): {
|
|
|
550
709
|
}
|
|
551
710
|
|
|
552
711
|
const next = parts[i + 1];
|
|
553
|
-
if (next && !next.startsWith("--") && ["format"].includes(withoutPrefix)) {
|
|
712
|
+
if (next && !next.startsWith("--") && ["format", "scope"].includes(withoutPrefix)) {
|
|
554
713
|
options.set(withoutPrefix, next);
|
|
555
714
|
i += 1;
|
|
556
715
|
} else {
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - Deterministic turn_summary and file_activity capture on agent_end
|
|
10
10
|
* - FTS5 search
|
|
11
11
|
* - /memory-status, /memory-search, /memory-open, /memory-inject, /memory-last commands
|
|
12
|
+
* - /memory-vault-* commands and natural-language URL capture
|
|
12
13
|
* - memory_search, memory_open, memory_remember, memory_forget tools
|
|
13
14
|
* - Conservative same-project before_agent_start injection
|
|
14
15
|
*/
|
|
@@ -21,6 +22,8 @@ import { retrieve, buildInjectionPacket, formatInjectionForLlm } from "./retriev
|
|
|
21
22
|
import { getProjectId, getConfig, clearProjectCache } from "./config/index.js";
|
|
22
23
|
import { closeDb, getRecord, insertInjection } from "./db/index.js";
|
|
23
24
|
import { getMemorySessionState, manualRecordsToRankedResults } from "./session-state/index.js";
|
|
25
|
+
import { captureUrlToVault } from "./vault/capture.js";
|
|
26
|
+
import { parseVaultCaptureIntent } from "./vault/intent.js";
|
|
24
27
|
import { createHash } from "node:crypto";
|
|
25
28
|
|
|
26
29
|
// ─── Session-scoped state ───────────────────────────────────────────
|
|
@@ -31,6 +34,37 @@ const injectedRefsThisSession: Set<string> = new Set();
|
|
|
31
34
|
/** Whether memory injection is temporarily disabled for this session */
|
|
32
35
|
let sessionEnabled = true;
|
|
33
36
|
|
|
37
|
+
async function maybeCaptureVaultUrl(
|
|
38
|
+
prompt: string,
|
|
39
|
+
projectId: string | null,
|
|
40
|
+
cwd: string,
|
|
41
|
+
signal?: AbortSignal,
|
|
42
|
+
): Promise<string | null> {
|
|
43
|
+
const intent = parseVaultCaptureIntent(prompt);
|
|
44
|
+
if (!intent) return null;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const result = await captureUrlToVault(intent.scope, projectId, cwd, intent.url, { signal });
|
|
48
|
+
const warnings = result.warnings.length > 0 ? `\nWarnings: ${result.warnings.join("; ")}` : "";
|
|
49
|
+
return `Captured web page into ${intent.scope} memory vault${result.initialized ? " (initialized vault)" : ""}: ${result.title}\nQuality: ${result.quality} (${result.qualityScore})${warnings}\nPage: ${result.pagePath}\nSource packet: ${result.sourcePacketPath}`;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
+
console.error("[pi-memory-stone] vault URL capture failed:", err);
|
|
53
|
+
return `Memory vault URL capture failed: ${message}`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function vaultCaptureReturn(systemPrompt: string, notice: string) {
|
|
58
|
+
return {
|
|
59
|
+
message: {
|
|
60
|
+
customType: "memory-vault-capture",
|
|
61
|
+
content: notice,
|
|
62
|
+
display: true,
|
|
63
|
+
},
|
|
64
|
+
systemPrompt: `${systemPrompt}\n\n--- Memory Vault Capture ---\n${notice}\nThe user's vault capture request has already been handled by pi-memory-stone. Briefly confirm the result; do not fetch the same URL again unless the user asks.\n--- End Memory Vault Capture ---`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
34
68
|
// ─── Extension entry point ─────────────────────────────────────────
|
|
35
69
|
|
|
36
70
|
export default function (pi: ExtensionAPI) {
|
|
@@ -69,18 +103,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
69
103
|
|
|
70
104
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
71
105
|
try {
|
|
106
|
+
const prompt = event.prompt || "";
|
|
107
|
+
const projectId = getProjectId(ctx.cwd);
|
|
108
|
+
const vaultCaptureNotice = await maybeCaptureVaultUrl(prompt, projectId, ctx.cwd, ctx.signal);
|
|
109
|
+
|
|
72
110
|
// Check if memory is enabled
|
|
73
111
|
const config = getConfig(ctx.cwd);
|
|
74
|
-
if (!config.enabled)
|
|
112
|
+
if (!config.enabled) {
|
|
113
|
+
return vaultCaptureNotice ? vaultCaptureReturn(event.systemPrompt || "", vaultCaptureNotice) : undefined;
|
|
114
|
+
}
|
|
75
115
|
|
|
76
116
|
const sessionState = getMemorySessionState(ctx.sessionManager.getBranch());
|
|
77
117
|
sessionEnabled = sessionState.enabled;
|
|
78
118
|
|
|
79
|
-
if (!sessionEnabled)
|
|
119
|
+
if (!sessionEnabled) {
|
|
120
|
+
return vaultCaptureNotice ? vaultCaptureReturn(event.systemPrompt || "", vaultCaptureNotice) : undefined;
|
|
121
|
+
}
|
|
80
122
|
|
|
81
|
-
const prompt = event.prompt || "";
|
|
82
123
|
const promptHash = createHash("sha256").update(prompt).digest("hex").slice(0, 12);
|
|
83
|
-
const projectId = getProjectId(ctx.cwd);
|
|
84
124
|
const injectionMode = sessionState.injectionMode ?? config.injectionMode;
|
|
85
125
|
|
|
86
126
|
const manualRecords = sessionState.manualRefs
|
|
@@ -103,10 +143,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
103
143
|
}
|
|
104
144
|
|
|
105
145
|
const selectedResults = [...manualResults, ...autoResults];
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
146
|
+
let formatted: string | null = null;
|
|
147
|
+
if (selectedResults.length > 0) {
|
|
148
|
+
const packet = buildInjectionPacket(selectedResults);
|
|
149
|
+
formatted = formatInjectionForLlm(packet, config.maxInjectedTokens);
|
|
150
|
+
}
|
|
110
151
|
|
|
111
152
|
// Track only search-selected refs. Manually chosen refs are intentionally
|
|
112
153
|
// injected on every turn until /memory-clear-injected is used.
|
|
@@ -114,27 +155,36 @@ export default function (pi: ExtensionAPI) {
|
|
|
114
155
|
injectedRefsThisSession.add(r.record.id);
|
|
115
156
|
}
|
|
116
157
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
158
|
+
if (formatted) {
|
|
159
|
+
insertInjection({
|
|
160
|
+
session_id: ctx.sessionManager.getSessionId(),
|
|
161
|
+
turn_entry_id: ctx.sessionManager.getLeafId() ?? undefined,
|
|
162
|
+
prompt_hash: promptHash,
|
|
163
|
+
injected_refs: selectedResults.map((r) => r.record.id).join(","),
|
|
164
|
+
packet: formatted,
|
|
165
|
+
reasons: selectedResults.map((r) => r.reasons.join(";")).join(" | "),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!formatted && !vaultCaptureNotice) return;
|
|
170
|
+
|
|
171
|
+
let systemPrompt = event.systemPrompt || "";
|
|
172
|
+
if (formatted) {
|
|
173
|
+
// Inject as a non-context audit custom entry (separate from LLM context)
|
|
174
|
+
// but also as a system prompt addition for the LLM
|
|
175
|
+
systemPrompt += [
|
|
176
|
+
"",
|
|
177
|
+
"--- Memory Stone Context ---",
|
|
178
|
+
formatted,
|
|
179
|
+
"--- End Memory Stone Context ---",
|
|
180
|
+
].join("\n");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (vaultCaptureNotice) {
|
|
184
|
+
return vaultCaptureReturn(systemPrompt, vaultCaptureNotice);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { systemPrompt };
|
|
138
188
|
} catch (err) {
|
|
139
189
|
console.error("[pi-memory-stone] before_agent_start handler error:", err);
|
|
140
190
|
}
|