pi-hermes-memory 0.4.0 → 0.4.1
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/docs/0.4/TASKS.md +59 -92
- package/package.json +1 -1
- package/src/handlers/index-sessions.ts +43 -40
- package/src/index.ts +17 -20
- package/src/tools/memory-search-tool.ts +37 -41
- package/src/tools/session-search-tool.ts +41 -45
package/docs/0.4/TASKS.md
CHANGED
|
@@ -7,140 +7,107 @@
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
## Epic 1: SQLite Foundation
|
|
10
|
+
## Epic 1: SQLite Foundation ✅
|
|
11
11
|
|
|
12
12
|
### Task 1.1: Install better-sqlite3 and create DB module
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
|
|
16
|
-
- WAL mode for concurrent reads
|
|
17
|
-
- Auto-create tables if they don't exist
|
|
18
|
-
- `close()` method for cleanup
|
|
19
|
-
- [ ] Create `tests/store/db.test.ts` — tests for DB initialization, table creation, close/reopen
|
|
13
|
+
- [x] Install `better-sqlite3` + `@types/better-sqlite3`
|
|
14
|
+
- [x] Create `src/store/db.ts` — DatabaseManager class
|
|
15
|
+
- [x] Create `tests/store/db.test.ts` — 14 tests passing
|
|
20
16
|
|
|
21
17
|
### Task 1.2: Create schema and migrations
|
|
22
|
-
- [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- `message_fts` FTS5 virtual table
|
|
26
|
-
- `memories` table
|
|
27
|
-
- `memory_fts` FTS5 virtual table
|
|
28
|
-
- [ ] Add triggers to keep FTS index in sync (INSERT/UPDATE/DELETE)
|
|
29
|
-
- [ ] Test: schema creates cleanly on fresh DB, idempotent on existing DB
|
|
18
|
+
- [x] Define schema in `src/store/schema.ts`
|
|
19
|
+
- [x] Add triggers to keep FTS index in sync
|
|
20
|
+
- [x] Test: schema creates cleanly on fresh DB, idempotent on existing DB
|
|
30
21
|
|
|
31
22
|
---
|
|
32
23
|
|
|
33
|
-
## Epic 2: Session History Indexing
|
|
24
|
+
## Epic 2: Session History Indexing ✅
|
|
34
25
|
|
|
35
26
|
### Task 2.1: JSONL parser
|
|
36
|
-
- [
|
|
37
|
-
|
|
38
|
-
- Handle all message types: user, assistant, system, tool_result
|
|
39
|
-
- Extract text content from `content` array (handle text, thinking, tool_use types)
|
|
40
|
-
- Skip unknown types gracefully
|
|
41
|
-
- Return structured `SessionData` with `messages: ParsedMessage[]`
|
|
42
|
-
- [ ] Create `tests/store/session-parser.test.ts` — test with real JSONL fixtures
|
|
27
|
+
- [x] Create `src/store/session-parser.ts`
|
|
28
|
+
- [x] Create `tests/store/session-parser.test.ts` — 14 tests passing
|
|
43
29
|
|
|
44
30
|
### Task 2.2: Session indexer
|
|
45
|
-
- [
|
|
46
|
-
|
|
47
|
-
- `indexAllSessions(db, projectPath?)` — bulk index all sessions for a project (or all projects)
|
|
48
|
-
- Skip already-indexed sessions (by session ID)
|
|
49
|
-
- `getSessionStats(db)` — count of sessions, messages, indexed projects
|
|
50
|
-
- [ ] Create `tests/store/session-indexer.test.ts` — test indexing, deduplication, stats
|
|
31
|
+
- [x] Create `src/store/session-indexer.ts`
|
|
32
|
+
- [x] Create `tests/store/session-indexer.test.ts` — 12 tests passing
|
|
51
33
|
|
|
52
34
|
### Task 2.3: /memory-index-sessions command
|
|
53
|
-
- [
|
|
54
|
-
|
|
55
|
-
- Show progress: "Indexing 36 sessions..."
|
|
56
|
-
- Show result: "Indexed 36 sessions, 1,247 messages"
|
|
57
|
-
- Handle errors gracefully (corrupt JSONL, missing files)
|
|
58
|
-
- [ ] Wire into `src/index.ts`
|
|
59
|
-
- [ ] Create `tests/handlers/index-sessions.test.ts`
|
|
35
|
+
- [x] Create `src/handlers/index-sessions.ts`
|
|
36
|
+
- [x] Wire into `src/index.ts`
|
|
60
37
|
|
|
61
38
|
---
|
|
62
39
|
|
|
63
|
-
## Epic 3: Session Search
|
|
40
|
+
## Epic 3: Session Search ✅
|
|
64
41
|
|
|
65
42
|
### Task 3.1: Session search store
|
|
66
|
-
- [
|
|
67
|
-
|
|
68
|
-
- Options: `limit`, `project`, `role` filter, `since` date filter
|
|
69
|
-
- Returns: `SearchResult[]` with `{sessionId, role, content, timestamp, snippet, project}`
|
|
70
|
-
- `snippet` — highlighted match context from FTS5 `snippet()` function
|
|
71
|
-
- [ ] Create `tests/store/session-search.test.ts` — test search, filters, relevance
|
|
43
|
+
- [x] Create `src/store/session-search.ts` — FTS5 search
|
|
44
|
+
- [x] Create `tests/store/session-search.test.ts` — 11 tests passing
|
|
72
45
|
|
|
73
46
|
### Task 3.2: session_search tool
|
|
74
|
-
- [
|
|
75
|
-
|
|
76
|
-
- Returns formatted results for the agent
|
|
77
|
-
- Includes session date, project, and content snippet
|
|
78
|
-
- [ ] Register in `src/index.ts`
|
|
79
|
-
- [ ] Create `tests/tools/session-search-tool.test.ts`
|
|
47
|
+
- [x] Create `src/tools/session-search-tool.ts`
|
|
48
|
+
- [x] Register in `src/index.ts`
|
|
80
49
|
|
|
81
50
|
---
|
|
82
51
|
|
|
83
|
-
## Epic 4: Extended Memory Store
|
|
52
|
+
## Epic 4: Extended Memory Store ✅
|
|
84
53
|
|
|
85
54
|
### Task 4.1: SQLite memory store
|
|
86
|
-
- [
|
|
87
|
-
|
|
88
|
-
- `searchMemories(db, query, options?)` — FTS5 search across memories
|
|
89
|
-
- `getMemories(db, project?, target?)` — list all memories (optionally filtered)
|
|
90
|
-
- `removeMemory(db, id)` — DELETE by ID
|
|
91
|
-
- `getMemoryStats(db)` — count by project/target
|
|
92
|
-
- [ ] Create `tests/store/sqlite-memory-store.test.ts`
|
|
55
|
+
- [x] Create `src/store/sqlite-memory-store.ts`
|
|
56
|
+
- [x] Create `tests/store/sqlite-memory-store.test.ts` — 19 tests passing
|
|
93
57
|
|
|
94
58
|
### Task 4.2: memory_search tool
|
|
95
|
-
- [
|
|
96
|
-
|
|
97
|
-
- Searches both global and project-specific memories
|
|
98
|
-
- Returns formatted results for the agent
|
|
99
|
-
- [ ] Register in `src/index.ts`
|
|
100
|
-
- [ ] Create `tests/tools/memory-search-tool.test.ts`
|
|
59
|
+
- [x] Create `src/tools/memory-search-tool.ts`
|
|
60
|
+
- [x] Register in `src/index.ts`
|
|
101
61
|
|
|
102
62
|
---
|
|
103
63
|
|
|
104
|
-
## Epic 5: Char Limit Increase
|
|
64
|
+
## Epic 5: Char Limit Increase ✅
|
|
105
65
|
|
|
106
66
|
### Task 5.1: Update defaults
|
|
107
|
-
- [
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
- `projectCharLimit`: 2200 → 5000
|
|
111
|
-
- [ ] Update `src/constants.ts` — change constants if any
|
|
112
|
-
- [ ] Update README configuration table
|
|
67
|
+
- [x] Update `src/constants.ts` — 5000 defaults
|
|
68
|
+
- [x] Update `src/types.ts` — updated comments
|
|
69
|
+
- [x] Update README configuration table
|
|
113
70
|
|
|
114
71
|
### Task 5.2: Update tests
|
|
115
|
-
- [
|
|
116
|
-
- [
|
|
117
|
-
- [ ] Verify interview still works at new limits
|
|
72
|
+
- [x] Updated all tests that depend on char limits
|
|
73
|
+
- [x] Verified consolidation still works at new limits
|
|
118
74
|
|
|
119
75
|
---
|
|
120
76
|
|
|
121
|
-
## Epic 6: Integration & Polish
|
|
77
|
+
## Epic 6: Integration & Polish ✅
|
|
122
78
|
|
|
123
79
|
### Task 6.1: Wire everything into index.ts
|
|
124
|
-
- [
|
|
125
|
-
- [
|
|
126
|
-
- [
|
|
127
|
-
- [
|
|
128
|
-
- [ ] Close DB on extension unload
|
|
80
|
+
- [x] Initialize DatabaseManager on extension load
|
|
81
|
+
- [x] Register `session_search` and `memory_search` tools
|
|
82
|
+
- [x] Register `/memory-index-sessions` command
|
|
83
|
+
- [x] Auto-index session on `session_shutdown` event
|
|
129
84
|
|
|
130
85
|
### Task 6.2: Add session indexing to background review
|
|
131
|
-
- [
|
|
132
|
-
- [ ] Ensure session is indexed even if shutdown event is missed
|
|
86
|
+
- [x] Auto-index on session_shutdown (indexes most recent session)
|
|
133
87
|
|
|
134
88
|
### Task 6.3: Update README
|
|
135
|
-
- [
|
|
136
|
-
- [
|
|
137
|
-
- [
|
|
138
|
-
- [
|
|
139
|
-
- [
|
|
89
|
+
- [x] Added session history search and extended memory sections
|
|
90
|
+
- [x] Updated char limits: 2200/1375 → 5000
|
|
91
|
+
- [x] Updated configuration table and JSON example
|
|
92
|
+
- [x] Updated Where Data Lives with sessions.db
|
|
93
|
+
- [x] Updated Known Limitations
|
|
140
94
|
|
|
141
95
|
### Task 6.4: Version bump & release
|
|
142
|
-
- [
|
|
143
|
-
- [
|
|
144
|
-
- [
|
|
145
|
-
- [
|
|
146
|
-
|
|
96
|
+
- [x] Bump version to `0.4.0`
|
|
97
|
+
- [x] Run full test suite — 272 tests passing
|
|
98
|
+
- [x] Publish to npm
|
|
99
|
+
- [x] Create GitHub release
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Summary
|
|
104
|
+
|
|
105
|
+
| Epic | Files Created | Tests |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| 1. SQLite Foundation | db.ts, schema.ts | 14 |
|
|
108
|
+
| 2. Session Indexing | session-parser.ts, session-indexer.ts, index-sessions.ts | 26 |
|
|
109
|
+
| 3. Session Search | session-search.ts, session-search-tool.ts | 11 |
|
|
110
|
+
| 4. Extended Memory | sqlite-memory-store.ts, memory-search-tool.ts | 19 |
|
|
111
|
+
| 5. Char Limits | constants.ts, types.ts | — |
|
|
112
|
+
| 6. Integration | index.ts, README.md, learn-memory-tool skill | — |
|
|
113
|
+
| **Total** | **12 new files** | **272 tests** |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hermes-memory",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Your Pi agent remembers everything across sessions — your preferences, your stack, your corrections, and even how it solved problems. Zero-config install, works immediately. Persistent memory + procedural skills + auto-correction detection + security-first content scanning.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -1,61 +1,64 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
3
4
|
import { DatabaseManager } from '../store/db.js';
|
|
4
5
|
import { indexAllSessions, getSessionStats } from '../store/session-indexer.js';
|
|
5
6
|
|
|
6
7
|
const SESSIONS_DIR = path.join(os.homedir(), '.pi', 'agent', 'sessions');
|
|
7
8
|
|
|
8
|
-
export function registerIndexSessionsCommand(
|
|
9
|
-
registerCommand
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
export function registerIndexSessionsCommand(pi: ExtensionAPI): void {
|
|
10
|
+
pi.registerCommand("memory-index-sessions", {
|
|
11
|
+
description: "Import past Pi sessions into the search database",
|
|
12
|
+
handler: async (_args, ctx) => {
|
|
13
|
+
const sendUserMessage = (msg: string) => {
|
|
14
|
+
if (ctx && typeof ctx === 'object' && 'sendUserMessage' in ctx) {
|
|
15
|
+
(ctx as { sendUserMessage: (msg: string) => void }).sendUserMessage(msg);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
const memoryDir = path.join(os.homedir(), '.pi', 'agent', 'memory');
|
|
20
|
-
const dbManager = new DatabaseManager(memoryDir);
|
|
19
|
+
sendUserMessage('🔍 Indexing session history...');
|
|
21
20
|
|
|
22
21
|
try {
|
|
23
|
-
const
|
|
22
|
+
const memoryDir = path.join(os.homedir(), '.pi', 'agent', 'memory');
|
|
23
|
+
const dbManager = new DatabaseManager(memoryDir);
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
try {
|
|
26
|
+
const result = indexAllSessions(dbManager, SESSIONS_DIR);
|
|
27
|
+
const stats = getSessionStats(dbManager);
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
let output = `\n✅ Session indexing complete!\n\n`;
|
|
30
|
+
output += `📊 Results:\n`;
|
|
31
|
+
output += `• Sessions processed: ${result.sessionsProcessed}\n`;
|
|
32
|
+
output += `• Sessions indexed: ${result.sessionsIndexed}\n`;
|
|
33
|
+
output += `• Sessions skipped (already indexed): ${result.sessionsSkipped}\n`;
|
|
34
|
+
output += `• Messages indexed: ${result.messagesIndexed}\n`;
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
if (stats.projects.length > 0) {
|
|
37
|
+
output += `\n📁 Projects:\n`;
|
|
38
|
+
for (const p of stats.projects) {
|
|
39
|
+
output += `• ${p.project}: ${p.sessions} sessions, ${p.messages} messages\n`;
|
|
40
|
+
}
|
|
38
41
|
}
|
|
39
|
-
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
if (result.errors.length > 0) {
|
|
44
|
+
output += `\n⚠️ Errors (${result.errors.length}):\n`;
|
|
45
|
+
for (const err of result.errors.slice(0, 5)) {
|
|
46
|
+
output += `• ${err}\n`;
|
|
47
|
+
}
|
|
48
|
+
if (result.errors.length > 5) {
|
|
49
|
+
output += `• ... and ${result.errors.length - 5} more\n`;
|
|
50
|
+
}
|
|
45
51
|
}
|
|
46
|
-
if (result.errors.length > 5) {
|
|
47
|
-
output += `• ... and ${result.errors.length - 5} more\n`;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
output += `\n💡 Use the \`session_search\` tool to search across indexed sessions.`;
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
sendUserMessage(output);
|
|
56
|
+
} finally {
|
|
57
|
+
dbManager.close();
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
sendUserMessage(`❌ Session indexing failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
61
|
}
|
|
57
|
-
}
|
|
58
|
-
sendUserMessage(`❌ Session indexing failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
59
|
-
}
|
|
62
|
+
},
|
|
60
63
|
});
|
|
61
64
|
}
|
package/src/index.ts
CHANGED
|
@@ -125,32 +125,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
125
125
|
registerIndexSessionsCommand(pi);
|
|
126
126
|
|
|
127
127
|
// ── 12. Auto-index session on shutdown ──
|
|
128
|
-
let currentSessionId: string | null = null;
|
|
129
|
-
let currentSessionCwd: string | null = null;
|
|
130
|
-
|
|
131
|
-
pi.on("session_start", async (event, _ctx) => {
|
|
132
|
-
// Capture session metadata for indexing
|
|
133
|
-
currentSessionId = (event as Record<string, unknown>).sessionId as string ?? null;
|
|
134
|
-
currentSessionCwd = process.cwd();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
128
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
129
|
+
try {
|
|
130
|
+
const fs = require("node:fs");
|
|
131
|
+
const sessionsDir = path.join(os.homedir(), ".pi", "agent", "sessions");
|
|
132
|
+
const cwd = process.cwd();
|
|
133
|
+
const encodedCwd = cwd.replace(/\//g, "-");
|
|
134
|
+
const sessionDir = path.join(sessionsDir, encodedCwd);
|
|
135
|
+
|
|
136
|
+
if (fs.existsSync(sessionDir)) {
|
|
137
|
+
// Find the most recent JSONL file (the one we just finished)
|
|
138
|
+
const files = fs.readdirSync(sessionDir)
|
|
139
|
+
.filter((f: string) => f.endsWith(".jsonl"))
|
|
140
|
+
.sort()
|
|
141
|
+
.reverse();
|
|
142
|
+
if (files.length > 0) {
|
|
143
|
+
const sessionData = parseSessionFile(path.join(sessionDir, files[0]));
|
|
147
144
|
if (sessionData) {
|
|
148
145
|
indexSession(dbManager, sessionData);
|
|
149
146
|
}
|
|
150
147
|
}
|
|
151
|
-
} catch {
|
|
152
|
-
// Silent fail — don't block shutdown
|
|
153
148
|
}
|
|
149
|
+
} catch {
|
|
150
|
+
// Silent fail — don't block shutdown
|
|
154
151
|
}
|
|
155
152
|
});
|
|
156
153
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
1
4
|
import { DatabaseManager } from '../store/db.js';
|
|
2
5
|
import { searchMemories, getMemoryStats } from '../store/sqlite-memory-store.js';
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
interface SearchResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
count?: number;
|
|
10
|
+
message?: string;
|
|
11
|
+
output?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function registerMemorySearchTool(pi: ExtensionAPI, dbManager: DatabaseManager): void {
|
|
15
|
+
pi.registerTool({
|
|
13
16
|
name: 'memory_search',
|
|
17
|
+
label: 'Memory Search',
|
|
14
18
|
description: `Search extended memory store for relevant entries. Use this when you need context beyond what's in the system prompt — the extended store has unlimited capacity and is searchable.
|
|
15
19
|
|
|
16
20
|
Use cases:
|
|
@@ -19,48 +23,39 @@ Use cases:
|
|
|
19
23
|
- Find user preferences: "What are the user's testing preferences?"
|
|
20
24
|
|
|
21
25
|
Returns matching memory entries with project context and dates.`,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
limit: {
|
|
39
|
-
type: 'number',
|
|
40
|
-
description: 'Maximum results to return (default: 10, max: 20).',
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
required: ['query'],
|
|
44
|
-
},
|
|
45
|
-
handler: async (args: Record<string, unknown>) => {
|
|
46
|
-
const query = args.query as string;
|
|
47
|
-
const project = args.project as string | undefined;
|
|
48
|
-
const target = args.target as string | undefined;
|
|
49
|
-
const limit = Math.min((args.limit as number) || 10, 20);
|
|
26
|
+
promptSnippet: 'Search extended memory store (unlimited capacity)',
|
|
27
|
+
promptGuidelines: [
|
|
28
|
+
'Use memory_search when you need context beyond what is in the system prompt.',
|
|
29
|
+
'Use memory_search to find project-specific memories or user preferences.',
|
|
30
|
+
],
|
|
31
|
+
parameters: Type.Object({
|
|
32
|
+
query: Type.String({ description: 'Search query. Use natural language or specific terms.' }),
|
|
33
|
+
project: Type.Optional(Type.String({ description: 'Filter by project name. Pass null for global memories only.' })),
|
|
34
|
+
target: Type.Optional(StringEnum(['memory', 'user'] as const, { description: 'Filter by target type (memory or user).' })),
|
|
35
|
+
limit: Type.Optional(Type.Number({ description: 'Maximum results to return (default: 10, max: 20).' })),
|
|
36
|
+
}),
|
|
37
|
+
execute: async (_id: string, args: { query: string; project?: string; target?: string; limit?: number }) => {
|
|
38
|
+
const query = args.query;
|
|
39
|
+
const project = args.project;
|
|
40
|
+
const target = args.target;
|
|
41
|
+
const limit = Math.min(args.limit || 10, 20);
|
|
50
42
|
|
|
51
43
|
if (!query || query.trim().length === 0) {
|
|
52
|
-
|
|
44
|
+
const result: SearchResult = { success: false, message: 'query is required' };
|
|
45
|
+
return { content: [{ type: 'text' as const, text: result.message! }], details: result };
|
|
53
46
|
}
|
|
54
47
|
|
|
55
48
|
const stats = getMemoryStats(dbManager);
|
|
56
49
|
if (stats.total === 0) {
|
|
57
|
-
|
|
50
|
+
const result: SearchResult = { success: false, message: 'No memories in extended store yet. Use the memory tool with add action to store memories.' };
|
|
51
|
+
return { content: [{ type: 'text' as const, text: result.message! }], details: result };
|
|
58
52
|
}
|
|
59
53
|
|
|
60
54
|
const results = searchMemories(dbManager, query, { project, target, limit });
|
|
61
55
|
|
|
62
56
|
if (results.length === 0) {
|
|
63
|
-
|
|
57
|
+
const result: SearchResult = { success: true, count: 0, message: `No memories found matching "${query}". Try a different search term or broader query.` };
|
|
58
|
+
return { content: [{ type: 'text' as const, text: result.message! }], details: result };
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
let output = `Found ${results.length} memories matching "${query}":\n\n`;
|
|
@@ -72,7 +67,8 @@ Returns matching memory entries with project context and dates.`,
|
|
|
72
67
|
output += ` Created: ${entry.created} | Last used: ${entry.lastReferenced}\n\n`;
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
|
|
70
|
+
const finalResult: SearchResult = { success: true, count: results.length, output: output.trim() };
|
|
71
|
+
return { content: [{ type: 'text' as const, text: output.trim() }], details: finalResult };
|
|
76
72
|
},
|
|
77
73
|
});
|
|
78
74
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
1
4
|
import { DatabaseManager } from '../store/db.js';
|
|
2
5
|
import { searchSessions, getIndexedMessageCount } from '../store/session-search.js';
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
interface SearchResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
count?: number;
|
|
10
|
+
message?: string;
|
|
11
|
+
output?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function registerSessionSearchTool(pi: ExtensionAPI, dbManager: DatabaseManager): void {
|
|
15
|
+
pi.registerTool({
|
|
13
16
|
name: 'session_search',
|
|
17
|
+
label: 'Session Search',
|
|
14
18
|
description: `Search across past Pi coding sessions for relevant conversation context. Use this when the user asks about previous discussions, past work, or when you need context from earlier sessions.
|
|
15
19
|
|
|
16
20
|
Examples:
|
|
@@ -19,65 +23,57 @@ Examples:
|
|
|
19
23
|
- "What approach did we take for the database migration?"
|
|
20
24
|
|
|
21
25
|
Returns conversation snippets with session dates and project context.`,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
limit: {
|
|
39
|
-
type: 'number',
|
|
40
|
-
description: 'Maximum results to return (default: 10, max: 20).',
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
required: ['query'],
|
|
44
|
-
},
|
|
45
|
-
handler: async (args: Record<string, unknown>) => {
|
|
46
|
-
const query = args.query as string;
|
|
47
|
-
const project = args.project as string | undefined;
|
|
48
|
-
const role = args.role as string | undefined;
|
|
49
|
-
const limit = Math.min((args.limit as number) || 10, 20);
|
|
26
|
+
promptSnippet: 'Search past conversations for relevant context',
|
|
27
|
+
promptGuidelines: [
|
|
28
|
+
'Use session_search when the user asks about previous discussions or past work.',
|
|
29
|
+
'Use session_search when you need context from earlier sessions.',
|
|
30
|
+
],
|
|
31
|
+
parameters: Type.Object({
|
|
32
|
+
query: Type.String({ description: 'Search query. Use natural language or specific terms.' }),
|
|
33
|
+
project: Type.Optional(Type.String({ description: 'Filter by project name (optional).' })),
|
|
34
|
+
role: Type.Optional(StringEnum(['user', 'assistant'] as const, { description: 'Filter by message role (optional).' })),
|
|
35
|
+
limit: Type.Optional(Type.Number({ description: 'Maximum results to return (default: 10, max: 20).' })),
|
|
36
|
+
}),
|
|
37
|
+
execute: async (_id: string, args: { query: string; project?: string; role?: string; limit?: number }) => {
|
|
38
|
+
const query = args.query;
|
|
39
|
+
const project = args.project;
|
|
40
|
+
const role = args.role;
|
|
41
|
+
const limit = Math.min(args.limit || 10, 20);
|
|
50
42
|
|
|
51
43
|
if (!query || query.trim().length === 0) {
|
|
52
|
-
|
|
44
|
+
const result: SearchResult = { success: false, message: 'query is required' };
|
|
45
|
+
return { content: [{ type: 'text' as const, text: result.message! }], details: result };
|
|
53
46
|
}
|
|
54
47
|
|
|
55
48
|
const totalMessages = getIndexedMessageCount(dbManager);
|
|
56
49
|
if (totalMessages === 0) {
|
|
57
|
-
|
|
50
|
+
const result: SearchResult = { success: false, message: 'No sessions indexed yet. Run /memory-index-sessions to import past sessions.' };
|
|
51
|
+
return { content: [{ type: 'text' as const, text: result.message! }], details: result };
|
|
58
52
|
}
|
|
59
53
|
|
|
60
54
|
const results = searchSessions(dbManager, query, { project, role, limit });
|
|
61
55
|
|
|
62
56
|
if (results.length === 0) {
|
|
63
|
-
|
|
57
|
+
const result: SearchResult = { success: true, count: 0, message: `No results found for "${query}". Try a different search term or broader query.` };
|
|
58
|
+
return { content: [{ type: 'text' as const, text: result.message! }], details: result };
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
let output = `Found ${results.length} results for "${query}":\n\n`;
|
|
67
62
|
|
|
68
|
-
for (const
|
|
69
|
-
const date = new Date(
|
|
63
|
+
for (const r of results) {
|
|
64
|
+
const date = new Date(r.timestamp).toLocaleDateString('en-US', {
|
|
70
65
|
year: 'numeric',
|
|
71
66
|
month: 'short',
|
|
72
67
|
day: 'numeric',
|
|
73
68
|
});
|
|
74
69
|
|
|
75
70
|
output += `---\n`;
|
|
76
|
-
output += `📅 ${date} | 📁 ${
|
|
77
|
-
output += `${
|
|
71
|
+
output += `📅 ${date} | 📁 ${r.project} | ${r.role === 'user' ? '👤 User' : '🤖 Assistant'}\n`;
|
|
72
|
+
output += `${r.snippet}\n\n`;
|
|
78
73
|
}
|
|
79
74
|
|
|
80
|
-
|
|
75
|
+
const finalResult: SearchResult = { success: true, count: results.length, output: output.trim() };
|
|
76
|
+
return { content: [{ type: 'text' as const, text: output.trim() }], details: finalResult };
|
|
81
77
|
},
|
|
82
78
|
});
|
|
83
79
|
}
|