nemoris 0.1.0 → 0.1.2
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/.env.example +49 -49
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/SECURITY.md +59 -119
- package/bin/nemoris +46 -46
- package/config/agents/agent.toml.example +28 -28
- package/config/agents/content.toml +23 -0
- package/config/agents/default.toml +22 -22
- package/config/agents/heartbeat.toml +35 -0
- package/config/agents/iris.toml +23 -0
- package/config/agents/lab.toml +23 -0
- package/config/agents/main.toml +45 -0
- package/config/agents/nemo.toml +21 -0
- package/config/agents/ops.toml +38 -0
- package/config/agents/orchestrator.toml +18 -18
- package/config/agents/revenue.toml +23 -0
- package/config/agents/testyboo.toml +19 -0
- package/config/delivery.toml +73 -73
- package/config/embeddings.toml +5 -5
- package/config/identity/content-purpose.md +11 -0
- package/config/identity/content-soul.md +45 -0
- package/config/identity/default-purpose.md +1 -1
- package/config/identity/default-soul.md +3 -3
- package/config/identity/heartbeat-purpose.md +9 -0
- package/config/identity/heartbeat-soul.md +16 -0
- package/config/identity/iris-purpose.md +17 -0
- package/config/identity/iris-soul.md +68 -0
- package/config/identity/lab-purpose.md +10 -0
- package/config/identity/lab-soul.md +38 -0
- package/config/identity/main-purpose.md +17 -0
- package/config/identity/main-soul.md +66 -0
- package/config/identity/main-user.md +22 -0
- package/config/identity/ops-purpose.md +9 -0
- package/config/identity/ops-soul.md +16 -0
- package/config/identity/orchestrator-purpose.md +1 -1
- package/config/identity/orchestrator-soul.md +1 -1
- package/config/identity/revenue-purpose.md +9 -0
- package/config/identity/revenue-soul.md +41 -0
- package/config/identity/testyboo-purpose.md +13 -0
- package/config/identity/testyboo-soul.md +20 -0
- package/config/improvement-targets.toml +15 -15
- package/config/jobs/heartbeat-check.toml +30 -30
- package/config/jobs/memory-rollup.toml +46 -46
- package/config/jobs/workspace-health.toml +63 -63
- package/config/mcp.toml +16 -16
- package/config/output-contracts.toml +17 -17
- package/config/peers.toml +32 -32
- package/config/peers.toml.example +32 -32
- package/config/policies/memory-default.toml +10 -10
- package/config/policies/memory-heartbeat.toml +5 -5
- package/config/policies/memory-ops.toml +10 -10
- package/config/policies/tools-heartbeat-minimal.toml +8 -8
- package/config/policies/tools-interactive-safe.toml +8 -8
- package/config/policies/tools-ops-bounded.toml +8 -8
- package/config/policies/tools-orchestrator.toml +7 -7
- package/config/providers/anthropic.toml +15 -15
- package/config/providers/ollama.toml +5 -5
- package/config/providers/openai-codex.toml +9 -9
- package/config/providers/openrouter.toml +5 -5
- package/config/router.toml +22 -22
- package/config/runtime.toml +114 -114
- package/config/skills/self-improvement.toml +15 -15
- package/config/skills/telegram-onboarding-spec.md +240 -240
- package/config/skills/workspace-monitor.toml +15 -15
- package/config/task-router.toml +42 -42
- package/install.sh +50 -50
- package/package.json +91 -90
- package/src/auth/auth-profiles.js +169 -169
- package/src/auth/openai-codex-oauth.js +285 -285
- package/src/battle.js +449 -449
- package/src/cli/help.js +265 -265
- package/src/cli/output-filter.js +49 -49
- package/src/cli/runtime-control.js +704 -704
- package/src/cli-main.js +2763 -2763
- package/src/cli.js +78 -78
- package/src/config/loader.js +332 -332
- package/src/config/schema-validator.js +214 -214
- package/src/config/toml-lite.js +8 -8
- package/src/daemon/action-handlers.js +71 -71
- package/src/daemon/healing-tick.js +87 -87
- package/src/daemon/health-probes.js +90 -90
- package/src/daemon/notifier.js +57 -57
- package/src/daemon/nurse.js +218 -218
- package/src/daemon/repair-log.js +106 -106
- package/src/daemon/rule-staging.js +90 -90
- package/src/daemon/rules.js +29 -29
- package/src/daemon/telegram-commands.js +54 -54
- package/src/daemon/updater.js +85 -85
- package/src/jobs/job-runner.js +78 -78
- package/src/mcp/consumer.js +129 -129
- package/src/memory/active-recall.js +171 -171
- package/src/memory/backend-manager.js +97 -97
- package/src/memory/backends/file-backend.js +38 -38
- package/src/memory/backends/qmd-backend.js +219 -219
- package/src/memory/embedding-guards.js +24 -24
- package/src/memory/embedding-index.js +118 -118
- package/src/memory/embedding-service.js +179 -179
- package/src/memory/file-index.js +177 -177
- package/src/memory/memory-signature.js +5 -5
- package/src/memory/memory-store.js +648 -648
- package/src/memory/retrieval-planner.js +66 -66
- package/src/memory/scoring.js +145 -145
- package/src/memory/simhash.js +78 -78
- package/src/memory/sqlite-active-store.js +824 -824
- package/src/memory/write-policy.js +36 -36
- package/src/onboarding/aliases.js +33 -33
- package/src/onboarding/auth/api-key.js +224 -224
- package/src/onboarding/auth/ollama-detect.js +42 -42
- package/src/onboarding/clack-prompter.js +77 -77
- package/src/onboarding/doctor.js +530 -530
- package/src/onboarding/lock.js +42 -42
- package/src/onboarding/model-catalog.js +344 -344
- package/src/onboarding/phases/auth.js +576 -589
- package/src/onboarding/phases/build.js +130 -130
- package/src/onboarding/phases/choose.js +82 -82
- package/src/onboarding/phases/detect.js +98 -98
- package/src/onboarding/phases/hatch.js +216 -216
- package/src/onboarding/phases/identity.js +79 -79
- package/src/onboarding/phases/ollama.js +345 -345
- package/src/onboarding/phases/scaffold.js +99 -99
- package/src/onboarding/phases/telegram.js +377 -377
- package/src/onboarding/phases/validate.js +204 -204
- package/src/onboarding/phases/verify.js +206 -206
- package/src/onboarding/platform.js +482 -482
- package/src/onboarding/status-bar.js +95 -95
- package/src/onboarding/templates.js +794 -794
- package/src/onboarding/toml-writer.js +38 -38
- package/src/onboarding/tui.js +250 -250
- package/src/onboarding/uninstall.js +153 -153
- package/src/onboarding/wizard.js +516 -499
- package/src/providers/anthropic.js +168 -168
- package/src/providers/base.js +247 -247
- package/src/providers/circuit-breaker.js +136 -136
- package/src/providers/ollama.js +163 -163
- package/src/providers/openai-codex.js +149 -149
- package/src/providers/openrouter.js +136 -136
- package/src/providers/registry.js +36 -36
- package/src/providers/router.js +16 -16
- package/src/runtime/bootstrap-cache.js +47 -47
- package/src/runtime/capabilities-prompt.js +25 -25
- package/src/runtime/completion-ping.js +99 -99
- package/src/runtime/config-validator.js +121 -121
- package/src/runtime/context-ledger.js +360 -360
- package/src/runtime/cutover-readiness.js +42 -42
- package/src/runtime/daemon.js +729 -729
- package/src/runtime/delivery-ack.js +195 -195
- package/src/runtime/delivery-adapters/local-file.js +41 -41
- package/src/runtime/delivery-adapters/openclaw-cli.js +94 -94
- package/src/runtime/delivery-adapters/openclaw-peer.js +98 -98
- package/src/runtime/delivery-adapters/shadow.js +13 -13
- package/src/runtime/delivery-adapters/standalone-http.js +98 -98
- package/src/runtime/delivery-adapters/telegram.js +104 -104
- package/src/runtime/delivery-adapters/tui.js +128 -128
- package/src/runtime/delivery-manager.js +807 -807
- package/src/runtime/delivery-store.js +168 -168
- package/src/runtime/dependency-health.js +118 -118
- package/src/runtime/envelope.js +114 -114
- package/src/runtime/evaluation.js +1089 -1089
- package/src/runtime/exec-approvals.js +216 -216
- package/src/runtime/executor.js +500 -500
- package/src/runtime/failure-ping.js +67 -67
- package/src/runtime/flows.js +83 -83
- package/src/runtime/guards.js +45 -45
- package/src/runtime/handoff.js +51 -51
- package/src/runtime/identity-cache.js +28 -28
- package/src/runtime/improvement-engine.js +109 -109
- package/src/runtime/improvement-harness.js +581 -581
- package/src/runtime/input-sanitiser.js +72 -72
- package/src/runtime/interaction-contract.js +347 -347
- package/src/runtime/lane-readiness.js +226 -226
- package/src/runtime/migration.js +323 -323
- package/src/runtime/model-resolution.js +78 -78
- package/src/runtime/network.js +64 -64
- package/src/runtime/notification-store.js +97 -97
- package/src/runtime/notifier.js +256 -256
- package/src/runtime/orchestrator.js +53 -53
- package/src/runtime/orphan-reaper.js +41 -41
- package/src/runtime/output-contract-schema.js +139 -139
- package/src/runtime/output-contract-validator.js +439 -439
- package/src/runtime/peer-readiness.js +69 -69
- package/src/runtime/peer-registry.js +133 -133
- package/src/runtime/pilot-status.js +108 -108
- package/src/runtime/prompt-builder.js +261 -261
- package/src/runtime/provider-attempt.js +582 -582
- package/src/runtime/report-fallback.js +71 -71
- package/src/runtime/result-normalizer.js +183 -183
- package/src/runtime/retention.js +74 -74
- package/src/runtime/review.js +244 -244
- package/src/runtime/route-job.js +15 -15
- package/src/runtime/run-store.js +38 -38
- package/src/runtime/schedule.js +88 -88
- package/src/runtime/scheduler-state.js +434 -434
- package/src/runtime/scheduler.js +656 -656
- package/src/runtime/session-compactor.js +182 -182
- package/src/runtime/session-search.js +155 -155
- package/src/runtime/slack-inbound.js +249 -249
- package/src/runtime/ssrf.js +102 -102
- package/src/runtime/status-aggregator.js +330 -330
- package/src/runtime/task-contract.js +140 -140
- package/src/runtime/task-packet.js +107 -107
- package/src/runtime/task-router.js +140 -140
- package/src/runtime/telegram-inbound.js +1565 -1565
- package/src/runtime/token-counter.js +134 -134
- package/src/runtime/token-estimator.js +59 -59
- package/src/runtime/tool-loop.js +200 -200
- package/src/runtime/transport-server.js +311 -311
- package/src/runtime/tui-server.js +411 -411
- package/src/runtime/ulid.js +44 -44
- package/src/security/ssrf-check.js +197 -197
- package/src/setup.js +369 -369
- package/src/shadow/bridge.js +303 -303
- package/src/skills/loader.js +84 -84
- package/src/tools/catalog.json +49 -49
- package/src/tools/cli-delegate.js +44 -44
- package/src/tools/mcp-client.js +106 -106
- package/src/tools/micro/cancel-task.js +6 -6
- package/src/tools/micro/complete-task.js +6 -6
- package/src/tools/micro/fail-task.js +6 -6
- package/src/tools/micro/http-fetch.js +74 -74
- package/src/tools/micro/index.js +36 -36
- package/src/tools/micro/lcm-recall.js +60 -60
- package/src/tools/micro/list-dir.js +17 -17
- package/src/tools/micro/list-skills.js +46 -46
- package/src/tools/micro/load-skill.js +38 -38
- package/src/tools/micro/memory-search.js +45 -45
- package/src/tools/micro/read-file.js +11 -11
- package/src/tools/micro/session-search.js +54 -54
- package/src/tools/micro/shell-exec.js +43 -43
- package/src/tools/micro/trigger-job.js +79 -79
- package/src/tools/micro/web-search.js +58 -58
- package/src/tools/micro/workspace-paths.js +39 -39
- package/src/tools/micro/write-file.js +14 -14
- package/src/tools/micro/write-memory.js +41 -41
- package/src/tools/registry.js +348 -348
- package/src/tools/tool-result-contract.js +36 -36
- package/src/tui/chat.js +835 -835
- package/src/tui/renderer.js +175 -175
- package/src/tui/socket-client.js +217 -217
- package/src/utils/canonical-json.js +29 -29
- package/src/utils/compaction.js +30 -30
- package/src/utils/env-loader.js +5 -5
- package/src/utils/errors.js +80 -80
- package/src/utils/fs.js +101 -101
- package/src/utils/ids.js +5 -5
- package/src/utils/model-context-limits.js +30 -30
- package/src/utils/token-budget.js +74 -74
- package/src/utils/usage-cost.js +25 -25
- package/src/utils/usage-metrics.js +14 -14
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FTS5-powered session search over conversation history.
|
|
3
|
-
* Searches context_events (messages) and context_summaries (compacted history).
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Normalize a user query into FTS5 MATCH syntax.
|
|
8
|
-
* "did we discuss auth last week" → "\"did\" OR \"we\" OR \"discuss\" OR \"auth\" OR \"last\" OR \"week\""
|
|
9
|
-
*/
|
|
10
|
-
function normalizeQuery(query) {
|
|
11
|
-
return String(query || "")
|
|
12
|
-
.split(/[^a-z0-9]+/i)
|
|
13
|
-
.map(t => t.trim())
|
|
14
|
-
.filter(t => t.length > 1)
|
|
15
|
-
.map(t => `"${t.replace(/"/g, "")}"`)
|
|
16
|
-
.join(" OR ");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Extract text content from a context_event payload_json.
|
|
21
|
-
*/
|
|
22
|
-
function extractContent(payloadJson) {
|
|
23
|
-
try {
|
|
24
|
-
const p = typeof payloadJson === "string" ? JSON.parse(payloadJson) : payloadJson;
|
|
25
|
-
// message_in/message_out typically have { content: "..." } or { text: "..." } or is a plain string
|
|
26
|
-
if (typeof p === "string") return p;
|
|
27
|
-
return p.content || p.text || p.message || p.summary || JSON.stringify(p);
|
|
28
|
-
} catch {
|
|
29
|
-
return String(payloadJson || "");
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class SessionSearch {
|
|
34
|
-
constructor(contextLedger) {
|
|
35
|
-
this.ledger = contextLedger;
|
|
36
|
-
this.db = contextLedger.db;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Ensure FTS5 tables exist. Called once after ledger.open().
|
|
41
|
-
*/
|
|
42
|
-
ensureSchema() {
|
|
43
|
-
this.db.exec(`
|
|
44
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS context_events_fts USING fts5(
|
|
45
|
-
event_id UNINDEXED, session_id UNINDEXED, kind UNINDEXED, ts UNINDEXED, content
|
|
46
|
-
);
|
|
47
|
-
`);
|
|
48
|
-
this.db.exec(`
|
|
49
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS context_summaries_fts USING fts5(
|
|
50
|
-
summary_id UNINDEXED, session_id UNINDEXED, summary_text
|
|
51
|
-
);
|
|
52
|
-
`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Index a single context event into FTS5. Call this after appendEvent().
|
|
57
|
-
*/
|
|
58
|
-
indexEvent(event) {
|
|
59
|
-
// Only index message events — tool_call/tool_result/state_patch are noisy
|
|
60
|
-
if (event.kind !== "message_in" && event.kind !== "message_out") return;
|
|
61
|
-
const content = extractContent(event.payload_json);
|
|
62
|
-
if (!content || content.length < 5) return;
|
|
63
|
-
this.db.prepare(
|
|
64
|
-
"INSERT INTO context_events_fts (event_id, session_id, kind, ts, content) VALUES (?, ?, ?, ?, ?)"
|
|
65
|
-
).run(event.id, event.session_id, event.kind, String(event.ts), content);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Index a single context summary into FTS5. Call after creating a summary.
|
|
70
|
-
*/
|
|
71
|
-
indexSummary(summary) {
|
|
72
|
-
this.db.prepare(
|
|
73
|
-
"INSERT INTO context_summaries_fts (summary_id, session_id, summary_text) VALUES (?, ?, ?)"
|
|
74
|
-
).run(String(summary.id), summary.session_id, summary.summary_text);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Rebuild the FTS index from scratch (for existing data).
|
|
79
|
-
*/
|
|
80
|
-
rebuildIndex() {
|
|
81
|
-
// Clear existing
|
|
82
|
-
this.db.exec("DELETE FROM context_events_fts");
|
|
83
|
-
this.db.exec("DELETE FROM context_summaries_fts");
|
|
84
|
-
|
|
85
|
-
// Re-index all message events
|
|
86
|
-
const events = this.db.prepare(
|
|
87
|
-
"SELECT id, session_id, kind, ts, payload_json FROM context_events WHERE kind IN ('message_in', 'message_out')"
|
|
88
|
-
).all();
|
|
89
|
-
const insertEvent = this.db.prepare(
|
|
90
|
-
"INSERT INTO context_events_fts (event_id, session_id, kind, ts, content) VALUES (?, ?, ?, ?, ?)"
|
|
91
|
-
);
|
|
92
|
-
for (const e of events) {
|
|
93
|
-
const content = extractContent(e.payload_json);
|
|
94
|
-
if (content && content.length >= 5) {
|
|
95
|
-
insertEvent.run(e.id, e.session_id, e.kind, String(e.ts), content);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Re-index all summaries
|
|
100
|
-
const summaries = this.db.prepare(
|
|
101
|
-
"SELECT id, session_id, summary_text FROM context_summaries"
|
|
102
|
-
).all();
|
|
103
|
-
const insertSummary = this.db.prepare(
|
|
104
|
-
"INSERT INTO context_summaries_fts (summary_id, session_id, summary_text) VALUES (?, ?, ?)"
|
|
105
|
-
);
|
|
106
|
-
for (const s of summaries) {
|
|
107
|
-
insertSummary.run(String(s.id), s.session_id, s.summary_text);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Search across events and summaries. Returns ranked results.
|
|
113
|
-
* @param {string} query User's search query
|
|
114
|
-
* @param {{ sessionId?: string, limit?: number }} opts
|
|
115
|
-
* @returns {{ events: Array, summaries: Array }}
|
|
116
|
-
*/
|
|
117
|
-
search(query, { sessionId, limit = 10 } = {}) {
|
|
118
|
-
const match = normalizeQuery(query);
|
|
119
|
-
if (!match) return { events: [], summaries: [] };
|
|
120
|
-
|
|
121
|
-
const sessionFilter = sessionId ? "AND session_id = ?" : "";
|
|
122
|
-
const params = sessionId ? [match, sessionId, limit] : [match, limit];
|
|
123
|
-
|
|
124
|
-
let events = [];
|
|
125
|
-
try {
|
|
126
|
-
events = this.db.prepare(`
|
|
127
|
-
SELECT event_id, session_id, kind, ts, snippet(context_events_fts, 4, '»', '«', '...', 32) as snippet,
|
|
128
|
-
rank
|
|
129
|
-
FROM context_events_fts
|
|
130
|
-
WHERE context_events_fts MATCH ?
|
|
131
|
-
${sessionFilter}
|
|
132
|
-
ORDER BY rank
|
|
133
|
-
LIMIT ?
|
|
134
|
-
`).all(...params);
|
|
135
|
-
} catch { /* FTS5 match can throw on malformed queries */ }
|
|
136
|
-
|
|
137
|
-
let summaries = [];
|
|
138
|
-
try {
|
|
139
|
-
summaries = this.db.prepare(`
|
|
140
|
-
SELECT summary_id, session_id, snippet(context_summaries_fts, 2, '»', '«', '...', 32) as snippet,
|
|
141
|
-
rank
|
|
142
|
-
FROM context_summaries_fts
|
|
143
|
-
WHERE context_summaries_fts MATCH ?
|
|
144
|
-
${sessionFilter}
|
|
145
|
-
ORDER BY rank
|
|
146
|
-
LIMIT ?
|
|
147
|
-
`).all(...params);
|
|
148
|
-
} catch { /* FTS5 match can throw on malformed queries */ }
|
|
149
|
-
|
|
150
|
-
return { events, summaries };
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Export extractContent and normalizeQuery for testing
|
|
155
|
-
export { extractContent, normalizeQuery };
|
|
1
|
+
/**
|
|
2
|
+
* FTS5-powered session search over conversation history.
|
|
3
|
+
* Searches context_events (messages) and context_summaries (compacted history).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a user query into FTS5 MATCH syntax.
|
|
8
|
+
* "did we discuss auth last week" → "\"did\" OR \"we\" OR \"discuss\" OR \"auth\" OR \"last\" OR \"week\""
|
|
9
|
+
*/
|
|
10
|
+
function normalizeQuery(query) {
|
|
11
|
+
return String(query || "")
|
|
12
|
+
.split(/[^a-z0-9]+/i)
|
|
13
|
+
.map(t => t.trim())
|
|
14
|
+
.filter(t => t.length > 1)
|
|
15
|
+
.map(t => `"${t.replace(/"/g, "")}"`)
|
|
16
|
+
.join(" OR ");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extract text content from a context_event payload_json.
|
|
21
|
+
*/
|
|
22
|
+
function extractContent(payloadJson) {
|
|
23
|
+
try {
|
|
24
|
+
const p = typeof payloadJson === "string" ? JSON.parse(payloadJson) : payloadJson;
|
|
25
|
+
// message_in/message_out typically have { content: "..." } or { text: "..." } or is a plain string
|
|
26
|
+
if (typeof p === "string") return p;
|
|
27
|
+
return p.content || p.text || p.message || p.summary || JSON.stringify(p);
|
|
28
|
+
} catch {
|
|
29
|
+
return String(payloadJson || "");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class SessionSearch {
|
|
34
|
+
constructor(contextLedger) {
|
|
35
|
+
this.ledger = contextLedger;
|
|
36
|
+
this.db = contextLedger.db;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ensure FTS5 tables exist. Called once after ledger.open().
|
|
41
|
+
*/
|
|
42
|
+
ensureSchema() {
|
|
43
|
+
this.db.exec(`
|
|
44
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS context_events_fts USING fts5(
|
|
45
|
+
event_id UNINDEXED, session_id UNINDEXED, kind UNINDEXED, ts UNINDEXED, content
|
|
46
|
+
);
|
|
47
|
+
`);
|
|
48
|
+
this.db.exec(`
|
|
49
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS context_summaries_fts USING fts5(
|
|
50
|
+
summary_id UNINDEXED, session_id UNINDEXED, summary_text
|
|
51
|
+
);
|
|
52
|
+
`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Index a single context event into FTS5. Call this after appendEvent().
|
|
57
|
+
*/
|
|
58
|
+
indexEvent(event) {
|
|
59
|
+
// Only index message events — tool_call/tool_result/state_patch are noisy
|
|
60
|
+
if (event.kind !== "message_in" && event.kind !== "message_out") return;
|
|
61
|
+
const content = extractContent(event.payload_json);
|
|
62
|
+
if (!content || content.length < 5) return;
|
|
63
|
+
this.db.prepare(
|
|
64
|
+
"INSERT INTO context_events_fts (event_id, session_id, kind, ts, content) VALUES (?, ?, ?, ?, ?)"
|
|
65
|
+
).run(event.id, event.session_id, event.kind, String(event.ts), content);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Index a single context summary into FTS5. Call after creating a summary.
|
|
70
|
+
*/
|
|
71
|
+
indexSummary(summary) {
|
|
72
|
+
this.db.prepare(
|
|
73
|
+
"INSERT INTO context_summaries_fts (summary_id, session_id, summary_text) VALUES (?, ?, ?)"
|
|
74
|
+
).run(String(summary.id), summary.session_id, summary.summary_text);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Rebuild the FTS index from scratch (for existing data).
|
|
79
|
+
*/
|
|
80
|
+
rebuildIndex() {
|
|
81
|
+
// Clear existing
|
|
82
|
+
this.db.exec("DELETE FROM context_events_fts");
|
|
83
|
+
this.db.exec("DELETE FROM context_summaries_fts");
|
|
84
|
+
|
|
85
|
+
// Re-index all message events
|
|
86
|
+
const events = this.db.prepare(
|
|
87
|
+
"SELECT id, session_id, kind, ts, payload_json FROM context_events WHERE kind IN ('message_in', 'message_out')"
|
|
88
|
+
).all();
|
|
89
|
+
const insertEvent = this.db.prepare(
|
|
90
|
+
"INSERT INTO context_events_fts (event_id, session_id, kind, ts, content) VALUES (?, ?, ?, ?, ?)"
|
|
91
|
+
);
|
|
92
|
+
for (const e of events) {
|
|
93
|
+
const content = extractContent(e.payload_json);
|
|
94
|
+
if (content && content.length >= 5) {
|
|
95
|
+
insertEvent.run(e.id, e.session_id, e.kind, String(e.ts), content);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Re-index all summaries
|
|
100
|
+
const summaries = this.db.prepare(
|
|
101
|
+
"SELECT id, session_id, summary_text FROM context_summaries"
|
|
102
|
+
).all();
|
|
103
|
+
const insertSummary = this.db.prepare(
|
|
104
|
+
"INSERT INTO context_summaries_fts (summary_id, session_id, summary_text) VALUES (?, ?, ?)"
|
|
105
|
+
);
|
|
106
|
+
for (const s of summaries) {
|
|
107
|
+
insertSummary.run(String(s.id), s.session_id, s.summary_text);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Search across events and summaries. Returns ranked results.
|
|
113
|
+
* @param {string} query User's search query
|
|
114
|
+
* @param {{ sessionId?: string, limit?: number }} opts
|
|
115
|
+
* @returns {{ events: Array, summaries: Array }}
|
|
116
|
+
*/
|
|
117
|
+
search(query, { sessionId, limit = 10 } = {}) {
|
|
118
|
+
const match = normalizeQuery(query);
|
|
119
|
+
if (!match) return { events: [], summaries: [] };
|
|
120
|
+
|
|
121
|
+
const sessionFilter = sessionId ? "AND session_id = ?" : "";
|
|
122
|
+
const params = sessionId ? [match, sessionId, limit] : [match, limit];
|
|
123
|
+
|
|
124
|
+
let events = [];
|
|
125
|
+
try {
|
|
126
|
+
events = this.db.prepare(`
|
|
127
|
+
SELECT event_id, session_id, kind, ts, snippet(context_events_fts, 4, '»', '«', '...', 32) as snippet,
|
|
128
|
+
rank
|
|
129
|
+
FROM context_events_fts
|
|
130
|
+
WHERE context_events_fts MATCH ?
|
|
131
|
+
${sessionFilter}
|
|
132
|
+
ORDER BY rank
|
|
133
|
+
LIMIT ?
|
|
134
|
+
`).all(...params);
|
|
135
|
+
} catch { /* FTS5 match can throw on malformed queries */ }
|
|
136
|
+
|
|
137
|
+
let summaries = [];
|
|
138
|
+
try {
|
|
139
|
+
summaries = this.db.prepare(`
|
|
140
|
+
SELECT summary_id, session_id, snippet(context_summaries_fts, 2, '»', '«', '...', 32) as snippet,
|
|
141
|
+
rank
|
|
142
|
+
FROM context_summaries_fts
|
|
143
|
+
WHERE context_summaries_fts MATCH ?
|
|
144
|
+
${sessionFilter}
|
|
145
|
+
ORDER BY rank
|
|
146
|
+
LIMIT ?
|
|
147
|
+
`).all(...params);
|
|
148
|
+
} catch { /* FTS5 match can throw on malformed queries */ }
|
|
149
|
+
|
|
150
|
+
return { events, summaries };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Export extractContent and normalizeQuery for testing
|
|
155
|
+
export { extractContent, normalizeQuery };
|