ei-tui 1.4.1 → 1.6.0
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 +2 -2
- package/package.json +1 -1
- package/src/cli/README.md +25 -88
- package/src/cli/mcp.ts +3 -123
- package/src/cli/retrieval.ts +3 -34
- package/src/cli.ts +425 -27
- package/src/core/orchestrators/ceremony.ts +5 -3
- package/src/core/processor.ts +50 -13
- package/src/core/tools/builtin/find-memory.ts +1 -1
- package/src/core/types/data-items.ts +1 -1
- package/src/core/types/entities.ts +2 -0
- package/src/integrations/slack/importer.ts +36 -15
- package/src/integrations/slack/reader.ts +23 -10
- package/src/integrations/slack/types.ts +27 -9
- package/src/prompts/message-utils.ts +1 -1
- package/tui/README.md +1 -1
- package/tui/src/commands/slack-auth.ts +13 -7
- package/tui/src/util/yaml-settings.ts +38 -10
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ You can access the Web version at [ei.flare576.com](https://ei.flare576.com).
|
|
|
6
6
|
|
|
7
7
|
You can run the local version via `bunx ei-tui` — no install needed, always current (see [### TUI](#tui) for details).
|
|
8
8
|
|
|
9
|
-
If you're here to give your coding tools (OpenCode, Claude Code, Cursor) persistent memory, jump over to [TUI README.md](./tui/README.md) to learn how to get information _into_ Ei, and [CLI README.md](./src/cli/README.md) to
|
|
9
|
+
If you're here to give your coding tools (OpenCode, Claude Code, Cursor) persistent memory, jump over to [TUI README.md](./tui/README.md) to learn how to get information _into_ Ei, and [CLI README.md](./src/cli/README.md) to wire up automatic context injection so your agents receive relevant memory before every message — no tool calls required.
|
|
10
10
|
|
|
11
11
|
## What Does "Local First" Mean?
|
|
12
12
|
|
|
@@ -140,7 +140,7 @@ opencode:
|
|
|
140
140
|
|
|
141
141
|
OpenCode saves sessions as JSON or SQLite (depending on version). Ei reads them, extracts context per-agent (each agent like Sisyphus gets its own persona), and keeps everything current as sessions accumulate.
|
|
142
142
|
|
|
143
|
-
OpenCode can also *read* Ei's knowledge back out via the [CLI tool](src/cli/README.md) —
|
|
143
|
+
OpenCode can also *read* Ei's knowledge back out via the [CLI tool](src/cli/README.md). Run `ei --install` to wire up automatic context injection — relevant topics are injected before every message, and (with [Oh My OpenCode](https://github.com/code-yeongyu/oh-my-opencode)) each agent's Ei persona record is loaded into the system prompt at session start. The agent knows who it is *to you* before it reads your first message.
|
|
144
144
|
|
|
145
145
|
#### Claude Code
|
|
146
146
|
|
package/package.json
CHANGED
package/src/cli/README.md
CHANGED
|
@@ -15,7 +15,7 @@ ei --recent # Most recently mentioned items (no query
|
|
|
15
15
|
ei --persona "Beta" --recent # Most recently mentioned items Beta has learned
|
|
16
16
|
ei --id <id> # Look up entity by ID — or fetch a message by FQ ID
|
|
17
17
|
echo <id> | ei --id # Look up entity by ID from stdin
|
|
18
|
-
ei --install #
|
|
18
|
+
ei --install # Wire Ei into Claude Code, Cursor, and OpenCode (MCP + hooks + persona plugin)
|
|
19
19
|
ei mcp # Start the Ei MCP stdio server (for Cursor/Claude Desktop)
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -37,7 +37,7 @@ ei --id "claudecode:my-machine:session-uuid:message-uuid"
|
|
|
37
37
|
ei --id "cursor:my-machine:composer-uuid:bubble-uuid"
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Quotes surfaced by `ei_search`
|
|
40
|
+
Quotes surfaced by `ei_search` include a `message_id` field in this format — pipe it to `ei --id` to read the original conversation.
|
|
41
41
|
|
|
42
42
|
# OpenCode Integration
|
|
43
43
|
|
|
@@ -47,12 +47,19 @@ Quotes surfaced by `ei_search` or `ei_find_memory` include a `message_id` field
|
|
|
47
47
|
ei --install
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
This registers Ei with Claude Code and
|
|
50
|
+
This registers Ei with Claude Code, Cursor, and OpenCode — MCP server config, context injection hooks, and (for OpenCode) a persona identity plugin so agents know who they are before the first message:
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
| Tool | MCP | Context Hook | Persona Plugin |
|
|
53
|
+
|------|-----|-------------|----------------|
|
|
54
|
+
| **Claude Code** | `~/.claude.json` | `~/.claude/settings.json` (`UserPromptSubmit`) + `~/.claude/hooks/ei-inject.ts` | — |
|
|
55
|
+
| **Cursor** | `~/.cursor/mcp.json` | `~/.cursor/hooks.json` (`beforeSubmitPrompt`) + `~/.cursor/hooks/ei-inject.sh` | — |
|
|
56
|
+
| **OpenCode** | manual (see below) | Via Oh My OpenCode compatibility layer (reads `~/.claude/settings.json`) | `~/.config/opencode/plugins/ei-persona.ts` |
|
|
54
57
|
|
|
55
|
-
**
|
|
58
|
+
**Context hook**: fires before every message, searches Ei for topics relevant to what you just asked, injects them silently. No tool call required.
|
|
59
|
+
|
|
60
|
+
**Persona plugin** (OpenCode + [Oh My OpenCode](https://github.com/code-yeongyu/oh-my-opencode) only): injects the agent's Ei relationship record directly into the system prompt at session start — traits, working style, shared context. The agent knows who it is *to you* before it reads a word of your message.
|
|
61
|
+
|
|
62
|
+
**OpenCode MCP**: add manually to `~/.config/opencode/opencode.jsonc`:
|
|
56
63
|
|
|
57
64
|
```json
|
|
58
65
|
{
|
|
@@ -79,82 +86,14 @@ Claude Code and Cursor call `ei mcp` to start the MCP stdio server. You can run
|
|
|
79
86
|
ei mcp
|
|
80
87
|
```
|
|
81
88
|
|
|
82
|
-
##
|
|
83
|
-
|
|
84
|
-
`ei --install` handles the technical wiring. This step tells your agent *when* and *how* to reach for it.
|
|
85
|
-
|
|
86
|
-
Without this, your agent has Ei available but may never call it. Add a snippet to your tool's config and it'll start querying Ei at the start of each session — and whenever you reference past context.
|
|
87
|
-
|
|
88
|
-
### OpenCode
|
|
89
|
-
|
|
90
|
-
Add to `~/.config/opencode/AGENTS.md` (applies to all projects):
|
|
91
|
-
|
|
92
|
-
```markdown
|
|
93
|
-
At session start, query Ei for user context:
|
|
94
|
-
|
|
95
|
-
\```bash
|
|
96
|
-
ei "What are the user's current preferences, active projects, and workflow?"
|
|
97
|
-
\```
|
|
98
|
-
|
|
99
|
-
Ei is a persistent knowledge base built from the user's conversations — facts, preferences,
|
|
100
|
-
people, topics, personas. Use it when the user references past work, mentions how they like things done,
|
|
101
|
-
asks "how did we do X," or needs to look up a person by any name, handle, or account (GitHub username,
|
|
102
|
-
Discord handle, email, nickname, etc.) — people results include an `identifiers` array covering all
|
|
103
|
-
known accounts and aliases for that person. Use `ei --persona "Beta" "walruses"` to scope results to
|
|
104
|
-
what a specific persona has learned. Use `ei personas "name"` to find personas by name. Query again
|
|
105
|
-
mid-session when they correct you or reference something from a previous session.
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Claude Code
|
|
89
|
+
## How Automatic Context Injection Works
|
|
109
90
|
|
|
110
|
-
|
|
91
|
+
After `ei --install`, agents receive Ei context without any manual tool calls:
|
|
111
92
|
|
|
112
|
-
|
|
113
|
-
At session start
|
|
114
|
-
natural-language query about the user's preferences, active projects, and workflow.
|
|
115
|
-
A `persona` filter is available to scope results to what a specific persona has learned.
|
|
116
|
-
Use `type: "personas"` to search for personas by name.
|
|
93
|
+
1. **Before each message** — the hook searches Ei using your prompt + recent conversation history as the query, then injects relevant topics into the conversation as `[Ei Memory Context]`. You won't see this in your chat view; the agent does.
|
|
94
|
+
2. **At session start** (OpenCode + OMO only) — the persona plugin finds the agent's Ei persona record and appends it to the system prompt as `<ei-relationship>`. The agent knows its working style, traits, and shared history with you before the session begins.
|
|
117
95
|
|
|
118
|
-
|
|
119
|
-
"how did we do X," or needs to look up a person by any name, handle, or account — people
|
|
120
|
-
results include an `identifiers` array (GitHub username, Discord handle, email, nickname, etc.)
|
|
121
|
-
covering all known accounts and aliases. Query again when they correct you or reference
|
|
122
|
-
something from a previous session.
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Cursor
|
|
126
|
-
|
|
127
|
-
Create `.cursor/rules/ei-mcp.mdc` in your project (or `~/.cursor/rules/` for user-level):
|
|
128
|
-
|
|
129
|
-
```markdown
|
|
130
|
-
---
|
|
131
|
-
description: When to use the Ei MCP for user memory and context
|
|
132
|
-
alwaysApply: true
|
|
133
|
-
---
|
|
134
|
-
# Ei MCP — User knowledge base
|
|
135
|
-
|
|
136
|
-
The **ei** MCP (server `user-ei`) is a persistent knowledge base built from the user's
|
|
137
|
-
conversations (facts, people, topics, quotes, personas).
|
|
138
|
-
|
|
139
|
-
**Use it when:**
|
|
140
|
-
- The user refers to past decisions, fixes, or "how we did X" and current chat/codebase
|
|
141
|
-
doesn't have that context.
|
|
142
|
-
- You need the user's preferences, contacts, or project conventions (e.g. who to ask for
|
|
143
|
-
access, how something was fixed).
|
|
144
|
-
- You need to look up a person by any name, handle, or account — people results include an
|
|
145
|
-
`identifiers` array (GitHub username, Discord handle, email, nickname, etc.) covering all
|
|
146
|
-
known accounts and aliases for that person.
|
|
147
|
-
- The question is about the user personally (people, workflow, prior discussions) rather
|
|
148
|
-
than only code.
|
|
149
|
-
|
|
150
|
-
**How to use:**
|
|
151
|
-
1. Call `ei_search` (server `user-ei`) with a natural-language query (or omit query and use `recent: true` to browse); optionally filter by `type` (facts, people, topics, quotes, personas) or `persona` display_name.
|
|
152
|
-
2. If you need full detail for a human entity (fact, topic, person, quote), call `ei_fetch_memory` with the entity `id`.
|
|
153
|
-
3. If you need full detail for a result including personas, call `ei_lookup` with the entity `id` from step 1.
|
|
154
|
-
4. To fetch a specific message with surrounding context, call `ei_fetch_message` with the message `id` and optional `before`/`after` counts.
|
|
155
|
-
|
|
156
|
-
Prefer querying Ei before asking the user for context they may have already shared.
|
|
157
|
-
```
|
|
96
|
+
The `ei_search`, `ei_lookup`, and `ei_fetch_message` MCP tools are still available for targeted mid-session queries — use them when you want to look something up explicitly.
|
|
158
97
|
|
|
159
98
|
## MCP Tools Reference
|
|
160
99
|
|
|
@@ -162,20 +101,18 @@ The MCP server exposes these tools to Claude Code, Cursor, and OpenCode:
|
|
|
162
101
|
|
|
163
102
|
| Tool | Description |
|
|
164
103
|
|------|-------------|
|
|
165
|
-
| `ei_search` |
|
|
166
|
-
| `ei_lookup` | Full-record lookup for any entity by ID
|
|
167
|
-
| `
|
|
168
|
-
| `ei_fetch_memory` | Full-record lookup for a human entity (Fact, Topic, Person, or Quote) by ID. Returns the complete record including all fields. |
|
|
169
|
-
| `ei_fetch_message` | Retrieve a specific message by fully-qualified ID with optional `before`/`after` context window. Routes to the correct source: `ei:uuid` searches Ei state, `opencode:machine:session:id` queries the OpenCode SQLite DB, `claudecode:...` scans Claude Code JSONL files, `cursor:...` reads the Cursor global DB. Returns message content, surrounding context, and session metadata. |
|
|
104
|
+
| `ei_search` | Search across all five data types (facts, topics, people, quotes, personas). Supports `type`, `persona`, `source`, `recent`, `limit` filters. Start here. |
|
|
105
|
+
| `ei_lookup` | Full-record lookup for any entity by ID — facts, topics, people, quotes, or personas. Use when you need complete details beyond the search summary. |
|
|
106
|
+
| `ei_fetch_message` | Retrieve a specific message by fully-qualified ID with optional `before`/`after` context window. Use when a quote result has a `message_id` and you want the original conversation. Routes to the correct source automatically. |
|
|
170
107
|
|
|
171
|
-
### `ei_search`
|
|
108
|
+
### `ei_search` arguments
|
|
172
109
|
|
|
173
110
|
| Arg | Type | Description |
|
|
174
111
|
|-----|------|-------------|
|
|
175
112
|
| `query` | string (optional) | Search text. Omit to browse by recency. |
|
|
113
|
+
| `type` | enum (optional) | `facts` \| `people` \| `topics` \| `quotes` \| `personas` — omit for balanced results across all types |
|
|
176
114
|
| `persona` | string (optional) | Persona display_name to scope results to what that persona has learned |
|
|
177
|
-
| `
|
|
178
|
-
| `types` | array (optional, `ei_find_memory` only) | `["facts", "topics", "people", "quotes"]` — omit for all human types |
|
|
115
|
+
| `source` | string (optional) | Prefix match against source identifiers (e.g. `opencode`, `cursor:my-machine`) |
|
|
179
116
|
| `limit` | number (optional) | Max results, default 10 |
|
|
180
117
|
| `recent` | boolean (optional) | Sort by most recently mentioned instead of relevance |
|
|
181
118
|
|
|
@@ -187,7 +124,7 @@ All search commands return arrays. Each result includes a `type` field.
|
|
|
187
124
|
|
|
188
125
|
**Person**: `{ type, id, name, description, relationship, sentiment, identifiers[] }` — `identifiers` contains all known accounts and aliases (e.g. `{ type: "GitHub", value: "flare576" }`)
|
|
189
126
|
|
|
190
|
-
**Quote**: `{ type,
|
|
127
|
+
**Quote**: `{ type, text, speaker, message_id, timestamp, linked_items[] }` — note: `id` is intentionally omitted; use `message_id` with `ei_fetch_message` to retrieve the original conversation
|
|
191
128
|
|
|
192
129
|
**Persona**: `{ type, id, display_name, short_description, model, base_prompt, traits[], topics[] }`
|
|
193
130
|
|
package/src/cli/mcp.ts
CHANGED
|
@@ -18,7 +18,7 @@ export function createMcpServer(): McpServer {
|
|
|
18
18
|
"ei_search",
|
|
19
19
|
{
|
|
20
20
|
description:
|
|
21
|
-
"Search the user's Ei knowledge base — a persistent memory store built from conversations. Returns facts, people, topics of interest, quotes, and personas. People results include an identifiers array (e.g. GitHub username, Discord handle, email, nickname) — query by any name or handle to find what Ei knows about that person. Persona results include traits and topics
|
|
21
|
+
"Search the user's Ei knowledge base — a persistent memory store built from conversations. Use at session start to load user context, and mid-session whenever the user references past work, preferences, or people. Returns facts, people, topics of interest, quotes, and personas. TYPE GUIDANCE: 'facts' are ONLY user demographics (name, age, job title, location, family structure, physical traits). For interests, opinions, hobbies, or anything the human cares about, use 'topics'. For named individuals, use 'people'. For verbatim things said, use 'quotes'. For AI agent identities with traits and working style, use 'personas'. People results include an identifiers array (e.g. GitHub username, Discord handle, email, nickname) — query by any name or handle to find what Ei knows about that person. Persona results include traits and topics — use type='personas' with the persona's name OR a natural-language description of their role to load a persona's character sheet. Results include entity IDs that can be passed to ei_lookup for full detail. Omit query with recent=true to browse the most recently discussed items.",
|
|
22
22
|
inputSchema: {
|
|
23
23
|
query: z.string().optional().describe("Search text. Supports natural language. Omit to browse without semantic filtering — useful with recent=true or persona filter."),
|
|
24
24
|
type: z
|
|
@@ -99,7 +99,7 @@ export function createMcpServer(): McpServer {
|
|
|
99
99
|
"ei_lookup",
|
|
100
100
|
{
|
|
101
101
|
description:
|
|
102
|
-
"
|
|
102
|
+
"Retrieve the full record for any Ei entity by ID — facts, topics, people, quotes, or personas. Use when ei_search returns an item and you need its complete details (all fields, traits, topics, identifiers, etc.). Pass the entity id from ei_search results.",
|
|
103
103
|
inputSchema: {
|
|
104
104
|
id: z.string().describe("The entity ID to look up."),
|
|
105
105
|
source: z
|
|
@@ -130,131 +130,11 @@ export function createMcpServer(): McpServer {
|
|
|
130
130
|
}
|
|
131
131
|
);
|
|
132
132
|
|
|
133
|
-
server.registerTool(
|
|
134
|
-
"ei_find_memory",
|
|
135
|
-
{
|
|
136
|
-
description:
|
|
137
|
-
"Search Ei's persistent knowledge base — facts, topics, people, and quotes learned across ALL conversations over time. Use when you need context about the user, their life, relationships, or interests that may not be visible in the current exchange. Returns results grouped by type. Use `recent: true` to retrieve what's been discussed recently. TYPE GUIDANCE: 'facts' are ONLY user demographics — name, age, job title, location, family structure, physical traits. For interests, opinions, hobbies, or anything the human cares about, use 'topics'. For named individuals, use 'people'. For verbatim things said, use 'quotes'.",
|
|
138
|
-
inputSchema: {
|
|
139
|
-
query: z
|
|
140
|
-
.string()
|
|
141
|
-
.optional()
|
|
142
|
-
.describe(
|
|
143
|
-
"What to search for — a person, topic, fact, or anything Ei has learned about the user. Omit with recent: true to browse recent items."
|
|
144
|
-
),
|
|
145
|
-
types: z
|
|
146
|
-
.array(z.enum(["facts", "topics", "people", "quotes"]))
|
|
147
|
-
.optional()
|
|
148
|
-
.describe("Limit search to specific memory types (default: all types). Use 'facts' ONLY for user demographics (name, age, job, location, family). Use 'topics' for interests, opinions, and anything the human cares about."),
|
|
149
|
-
limit: z
|
|
150
|
-
.number()
|
|
151
|
-
.optional()
|
|
152
|
-
.default(10)
|
|
153
|
-
.describe("Max results per type to return (default: 10, max: 20)"),
|
|
154
|
-
recent: z
|
|
155
|
-
.boolean()
|
|
156
|
-
.optional()
|
|
157
|
-
.describe(
|
|
158
|
-
"If true, return recently-mentioned results sorted by last_mentioned date instead of relevance. Combine with a query to filter recent results by topic."
|
|
159
|
-
),
|
|
160
|
-
persona: z
|
|
161
|
-
.string()
|
|
162
|
-
.optional()
|
|
163
|
-
.describe(
|
|
164
|
-
"Filter results to what a specific persona has learned. Use the persona display name."
|
|
165
|
-
),
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
async ({ query: rawQuery, types, limit, recent, persona }) => {
|
|
169
|
-
const query = rawQuery ?? "";
|
|
170
|
-
const effectiveLimit = Math.min(limit ?? 10, 20);
|
|
171
|
-
const options = { recent: recent ?? false };
|
|
172
|
-
|
|
173
|
-
const humanTypes = ["facts", "topics", "people", "quotes"] as const;
|
|
174
|
-
type HumanType = (typeof humanTypes)[number];
|
|
175
|
-
const requestedTypes: HumanType[] =
|
|
176
|
-
types && types.length > 0
|
|
177
|
-
? (types as HumanType[])
|
|
178
|
-
: [...humanTypes];
|
|
179
|
-
|
|
180
|
-
let state: StorageState | null = null;
|
|
181
|
-
let personaId: string | undefined;
|
|
182
|
-
if (persona) {
|
|
183
|
-
state = await loadLatestState();
|
|
184
|
-
if (state) {
|
|
185
|
-
personaId = resolvePersonaId(state, persona) ?? undefined;
|
|
186
|
-
if (!personaId) {
|
|
187
|
-
return {
|
|
188
|
-
content: [{ type: "text" as const, text: `Persona "${persona}" not found.` }],
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const grouped: Record<string, unknown[]> = {};
|
|
195
|
-
for (const t of requestedTypes) {
|
|
196
|
-
const module = await import(`./commands/${t}.js`);
|
|
197
|
-
let results = await (
|
|
198
|
-
module.execute as (
|
|
199
|
-
q: string,
|
|
200
|
-
l: number,
|
|
201
|
-
o: { recent: boolean }
|
|
202
|
-
) => Promise<{ id: string }[]>
|
|
203
|
-
)(query, effectiveLimit, options);
|
|
204
|
-
if (personaId && state) {
|
|
205
|
-
results = filterTypeSpecificByPersona(results, state, personaId, t);
|
|
206
|
-
}
|
|
207
|
-
if (results.length > 0) {
|
|
208
|
-
grouped[t] = results;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (Object.keys(grouped).length === 0) {
|
|
213
|
-
return {
|
|
214
|
-
content: [
|
|
215
|
-
{
|
|
216
|
-
type: "text" as const,
|
|
217
|
-
text: JSON.stringify({ result: "No relevant memories found for this query." }),
|
|
218
|
-
},
|
|
219
|
-
],
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
content: [{ type: "text" as const, text: JSON.stringify(grouped, null, 2) }],
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
server.registerTool(
|
|
230
|
-
"ei_fetch_memory",
|
|
231
|
-
{
|
|
232
|
-
description:
|
|
233
|
-
"Retrieve the full record for a specific memory by its ID. Use when ei_find_memory or ei_search returns an item and you need its complete details. Returns the full Fact, Topic, Person, or Quote record.",
|
|
234
|
-
inputSchema: {
|
|
235
|
-
id: z.string().describe("The ID of the memory record to retrieve"),
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
async ({ id }) => {
|
|
239
|
-
const result = await lookupById(id);
|
|
240
|
-
|
|
241
|
-
if (result === null || result.type === "persona") {
|
|
242
|
-
return {
|
|
243
|
-
content: [{ type: "text" as const, text: `No memory record found with ID: ${id}` }],
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return {
|
|
248
|
-
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
);
|
|
252
|
-
|
|
253
133
|
server.registerTool(
|
|
254
134
|
"ei_fetch_message",
|
|
255
135
|
{
|
|
256
136
|
description:
|
|
257
|
-
"Retrieve a specific message by its ID, with optional surrounding context. Use when
|
|
137
|
+
"Retrieve a specific message by its fully-qualified ID, with optional surrounding conversation context. Use when ei_search returns a quote with a message_id and you want to read the original exchange. The 'before' and 'after' parameters expand the context window in either direction (default 0). Accepts IDs from any integrated source: 'ei:uuid' searches Ei state, 'opencode:machine:session:id' queries OpenCode SQLite, 'claudecode:...' scans Claude Code JSONL files, 'cursor:...' reads the Cursor DB.",
|
|
258
138
|
inputSchema: {
|
|
259
139
|
id: z.string().describe("The ID of the message to retrieve"),
|
|
260
140
|
before: z
|
package/src/cli/retrieval.ts
CHANGED
|
@@ -84,7 +84,6 @@ export interface LinkedItem {
|
|
|
84
84
|
type: string;
|
|
85
85
|
}
|
|
86
86
|
export interface QuoteResult {
|
|
87
|
-
id: string;
|
|
88
87
|
text: string;
|
|
89
88
|
speaker: string;
|
|
90
89
|
timestamp: string;
|
|
@@ -163,7 +162,6 @@ export function resolveLinkedItems(dataItemIds: string[], state: StorageState):
|
|
|
163
162
|
}
|
|
164
163
|
export function mapQuote(quote: Quote, state: StorageState): QuoteResult {
|
|
165
164
|
return {
|
|
166
|
-
id: quote.id,
|
|
167
165
|
text: quote.text,
|
|
168
166
|
speaker: quote.speaker,
|
|
169
167
|
timestamp: quote.timestamp,
|
|
@@ -287,16 +285,11 @@ export async function retrieveBalanced(
|
|
|
287
285
|
const recentDate = (item: AnyItem): string => item.last_mentioned ?? item.last_updated ?? "";
|
|
288
286
|
|
|
289
287
|
if (recent && !query) {
|
|
290
|
-
const allItems: Array<{ type: DataType; item: AnyItem; mapped: QuoteResult | FactResult | PersonResult | TopicResult
|
|
288
|
+
const allItems: Array<{ type: DataType; item: AnyItem; mapped: QuoteResult | FactResult | PersonResult | TopicResult }> = [
|
|
291
289
|
...state.human.quotes.map(q => ({ type: "quote" as DataType, item: q as AnyItem, mapped: mapQuote(q, state) })),
|
|
292
290
|
...state.human.facts.map(f => ({ type: "fact" as DataType, item: f as AnyItem, mapped: mapFact(f) })),
|
|
293
291
|
...state.human.people.map(p => ({ type: "person" as DataType, item: p as AnyItem, mapped: mapPerson(p) })),
|
|
294
292
|
...state.human.topics.map(t => ({ type: "topic" as DataType, item: t as AnyItem, mapped: mapTopic(t) })),
|
|
295
|
-
...Object.values(state.personas).map(({ entity: p }) => ({
|
|
296
|
-
type: "persona" as DataType,
|
|
297
|
-
item: { id: p.id, last_updated: p.last_updated } as AnyItem,
|
|
298
|
-
mapped: mapPersona(p),
|
|
299
|
-
})),
|
|
300
293
|
];
|
|
301
294
|
return allItems
|
|
302
295
|
.sort((a, b) => recentDate(b.item).localeCompare(recentDate(a.item)))
|
|
@@ -330,22 +323,10 @@ export async function retrieveBalanced(
|
|
|
330
323
|
}
|
|
331
324
|
}
|
|
332
325
|
}
|
|
333
|
-
|
|
326
|
+
return allScored
|
|
334
327
|
.sort((a, b) => recentDate(b.mapped as AnyItem).localeCompare(recentDate(a.mapped as AnyItem)))
|
|
335
328
|
.slice(0, limit)
|
|
336
329
|
.map(({ type, mapped }) => ({ type, ...mapped }) as BalancedResult);
|
|
337
|
-
const personaMatches = [
|
|
338
|
-
...retrievePersonas(query, state, limit, { recent: true }),
|
|
339
|
-
...await retrievePersonasSemantic(queryVector, state, limit),
|
|
340
|
-
].filter((p, i, arr) => arr.findIndex(x => x.id === p.id) === i);
|
|
341
|
-
if (personaMatches.length > 0) {
|
|
342
|
-
const combined = [
|
|
343
|
-
...personaMatches.map((p) => ({ type: "persona" as const, ...p }) as BalancedResult),
|
|
344
|
-
...embeddingResults,
|
|
345
|
-
];
|
|
346
|
-
return combined.slice(0, limit);
|
|
347
|
-
}
|
|
348
|
-
return embeddingResults;
|
|
349
330
|
}
|
|
350
331
|
|
|
351
332
|
for (const { type, items, mapper } of typeConfigs) {
|
|
@@ -383,19 +364,7 @@ export async function retrieveBalanced(
|
|
|
383
364
|
|
|
384
365
|
result.sort((a, b) => b.similarity - a.similarity);
|
|
385
366
|
|
|
386
|
-
|
|
387
|
-
const personaFinal = [
|
|
388
|
-
...retrievePersonas(query, state, limit),
|
|
389
|
-
...await retrievePersonasSemantic(queryVector, state, limit),
|
|
390
|
-
].filter((p, i, arr) => arr.findIndex(x => x.id === p.id) === i);
|
|
391
|
-
if (personaFinal.length > 0) {
|
|
392
|
-
const combined = [
|
|
393
|
-
...personaFinal.map((p) => ({ type: "persona" as const, ...p }) as BalancedResult),
|
|
394
|
-
...embeddingFinal,
|
|
395
|
-
];
|
|
396
|
-
return combined.slice(0, limit);
|
|
397
|
-
}
|
|
398
|
-
return embeddingFinal;
|
|
367
|
+
return result.map(({ type, mapped }) => ({ type, ...mapped }) as BalancedResult);
|
|
399
368
|
}
|
|
400
369
|
|
|
401
370
|
const OPENCODE_MESSAGE_ID = /^msg_[a-zA-Z0-9]+$/;
|