ei-tui 1.6.3 → 1.6.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/package.json +1 -1
- package/src/cli/README.md +13 -0
- package/src/cli/install.ts +708 -0
- package/src/cli/session-context.ts +98 -0
- package/src/cli.ts +82 -808
- package/src/core/bootstrap-tools.ts +486 -0
- package/src/core/handlers/document-segmentation.ts +1 -2
- package/src/core/handlers/heartbeat.ts +3 -2
- package/src/core/handlers/persona-response.ts +5 -4
- package/src/core/handlers/rooms.ts +6 -5
- package/src/core/integration-sync-manager.ts +482 -0
- package/src/core/message-manager.ts +2 -1
- package/src/core/migrations.ts +297 -0
- package/src/core/orchestrators/ceremony.ts +2 -1
- package/src/core/processor.ts +17 -1220
- package/src/core/room-manager.ts +17 -4
- package/src/core/state-manager.ts +2 -1
- package/src/integrations/slack/importer.ts +1 -1
- package/tui/src/components/PromptInput.tsx +5 -1
- package/tui/src/storage/file.ts +6 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
// src/core/bootstrap-tools.ts
|
|
2
|
+
import { StateManager } from "./state-manager.js";
|
|
3
|
+
import type { ToolProvider } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Seed built-in tool providers and tools if they don't exist yet.
|
|
7
|
+
* Called on every startup (after state load/restore) — safe to call repeatedly.
|
|
8
|
+
* New builtins added in future releases will be seeded automatically.
|
|
9
|
+
*/
|
|
10
|
+
export function bootstrapTools(stateManager: StateManager): void {
|
|
11
|
+
const now = new Date().toISOString();
|
|
12
|
+
|
|
13
|
+
for (const name of ["find_memory", "fetch_memory", "fetch_message", "read_memory"]) {
|
|
14
|
+
const tool = stateManager.tools_getByName(name);
|
|
15
|
+
if (tool) stateManager.tools_remove(tool.id);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// --- Ei built-in provider ---
|
|
19
|
+
if (!stateManager.tools_getProviderById("ei")) {
|
|
20
|
+
const eiProvider: ToolProvider = {
|
|
21
|
+
id: "ei",
|
|
22
|
+
name: "ei",
|
|
23
|
+
display_name: "Ei Built-ins",
|
|
24
|
+
description: "Built-in tools that ship with Ei. No external API needed.",
|
|
25
|
+
builtin: true,
|
|
26
|
+
config: {},
|
|
27
|
+
enabled: true,
|
|
28
|
+
created_at: now,
|
|
29
|
+
};
|
|
30
|
+
stateManager.tools_addProvider(eiProvider);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// file_read tool (TUI only)
|
|
34
|
+
stateManager.tools_upsertBuiltin({
|
|
35
|
+
id: crypto.randomUUID(),
|
|
36
|
+
provider_id: "ei",
|
|
37
|
+
name: "file_read",
|
|
38
|
+
display_name: "Read File",
|
|
39
|
+
description:
|
|
40
|
+
"Read the contents of a file from the local filesystem. Use list_directory first to explore folder structure. Only available in the TUI.",
|
|
41
|
+
input_schema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
path: { type: "string", description: "Absolute or relative path to the file" },
|
|
45
|
+
},
|
|
46
|
+
required: ["path"],
|
|
47
|
+
},
|
|
48
|
+
runtime: "node",
|
|
49
|
+
builtin: true,
|
|
50
|
+
enabled: true,
|
|
51
|
+
created_at: now,
|
|
52
|
+
max_calls_per_interaction: 5,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// list_directory tool (TUI only)
|
|
56
|
+
stateManager.tools_upsertBuiltin({
|
|
57
|
+
id: crypto.randomUUID(),
|
|
58
|
+
provider_id: "ei",
|
|
59
|
+
name: "list_directory",
|
|
60
|
+
display_name: "List Directory",
|
|
61
|
+
description:
|
|
62
|
+
"List the contents of a directory on the local filesystem. Returns filenames prefixed with [FILE] or [DIR]. Use this to explore folder structure before reading files. Only available in the TUI.",
|
|
63
|
+
input_schema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
path: { type: "string", description: "Absolute or relative path to the directory (e.g. ~/Projects/myapp or /home/user/docs)" },
|
|
67
|
+
},
|
|
68
|
+
required: ["path"],
|
|
69
|
+
},
|
|
70
|
+
runtime: "node",
|
|
71
|
+
builtin: true,
|
|
72
|
+
enabled: true,
|
|
73
|
+
created_at: now,
|
|
74
|
+
max_calls_per_interaction: 5,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// directory_tree tool (TUI only)
|
|
78
|
+
stateManager.tools_upsertBuiltin({
|
|
79
|
+
id: crypto.randomUUID(),
|
|
80
|
+
provider_id: "ei",
|
|
81
|
+
name: "directory_tree",
|
|
82
|
+
display_name: "Directory Tree",
|
|
83
|
+
description:
|
|
84
|
+
"Show a recursive tree of a directory up to a configurable depth. Returns a JSON tree with name, type, and children fields. Default max_depth is 3, maximum is 8. Only available in the TUI.",
|
|
85
|
+
input_schema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
path: { type: "string", description: "Absolute or relative path to the directory" },
|
|
89
|
+
max_depth: { type: "number", description: "Maximum depth to recurse (1-8, default 3)" },
|
|
90
|
+
},
|
|
91
|
+
required: ["path"],
|
|
92
|
+
},
|
|
93
|
+
runtime: "node",
|
|
94
|
+
builtin: true,
|
|
95
|
+
enabled: true,
|
|
96
|
+
created_at: now,
|
|
97
|
+
max_calls_per_interaction: 3,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// search_files tool (TUI only)
|
|
101
|
+
stateManager.tools_upsertBuiltin({
|
|
102
|
+
id: crypto.randomUUID(),
|
|
103
|
+
provider_id: "ei",
|
|
104
|
+
name: "search_files",
|
|
105
|
+
display_name: "Search Files",
|
|
106
|
+
description:
|
|
107
|
+
"Recursively search for files by name pattern within a directory. Supports * wildcards. Returns matching absolute paths. Skips node_modules, .git, dist. Only available in the TUI.",
|
|
108
|
+
input_schema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
path: { type: "string", description: "Root directory to search from" },
|
|
112
|
+
pattern: { type: "string", description: "Filename glob pattern, e.g. \"*.ts\" or \"README*\"" },
|
|
113
|
+
},
|
|
114
|
+
required: ["path", "pattern"],
|
|
115
|
+
},
|
|
116
|
+
runtime: "node",
|
|
117
|
+
builtin: true,
|
|
118
|
+
enabled: true,
|
|
119
|
+
created_at: now,
|
|
120
|
+
max_calls_per_interaction: 3,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// grep tool (TUI only)
|
|
124
|
+
stateManager.tools_upsertBuiltin({
|
|
125
|
+
id: crypto.randomUUID(),
|
|
126
|
+
provider_id: "ei",
|
|
127
|
+
name: "grep",
|
|
128
|
+
display_name: "Grep",
|
|
129
|
+
description:
|
|
130
|
+
"Search file contents for lines matching a regex pattern. Recursively searches a directory (or a single file). Skips binary files and node_modules. Returns matching file, line number, and text. Only available in the TUI.",
|
|
131
|
+
input_schema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
pattern: { type: "string", description: "Regex pattern to search for" },
|
|
135
|
+
path: { type: "string", description: "File or directory to search" },
|
|
136
|
+
include: { type: "string", description: "Optional glob to filter filenames, e.g. \"*.ts\"" },
|
|
137
|
+
case_insensitive: { type: "boolean", description: "Case-insensitive match (default false)" },
|
|
138
|
+
},
|
|
139
|
+
required: ["pattern", "path"],
|
|
140
|
+
},
|
|
141
|
+
runtime: "node",
|
|
142
|
+
builtin: true,
|
|
143
|
+
enabled: true,
|
|
144
|
+
created_at: now,
|
|
145
|
+
max_calls_per_interaction: 5,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// get_file_info tool (TUI only)
|
|
149
|
+
stateManager.tools_upsertBuiltin({
|
|
150
|
+
id: crypto.randomUUID(),
|
|
151
|
+
provider_id: "ei",
|
|
152
|
+
name: "get_file_info",
|
|
153
|
+
display_name: "Get File Info",
|
|
154
|
+
description:
|
|
155
|
+
"Get metadata about a file or directory: type, size, permissions, created/modified/accessed timestamps. Only available in the TUI.",
|
|
156
|
+
input_schema: {
|
|
157
|
+
type: "object",
|
|
158
|
+
properties: {
|
|
159
|
+
path: { type: "string", description: "Absolute or relative path to the file or directory" },
|
|
160
|
+
},
|
|
161
|
+
required: ["path"],
|
|
162
|
+
},
|
|
163
|
+
runtime: "node",
|
|
164
|
+
builtin: true,
|
|
165
|
+
enabled: true,
|
|
166
|
+
created_at: now,
|
|
167
|
+
max_calls_per_interaction: 5,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// web_fetch tool
|
|
171
|
+
stateManager.tools_upsertBuiltin({
|
|
172
|
+
id: crypto.randomUUID(),
|
|
173
|
+
provider_id: "ei",
|
|
174
|
+
name: "web_fetch",
|
|
175
|
+
display_name: "Web Fetch",
|
|
176
|
+
description:
|
|
177
|
+
"Fetch content from a URL and return the text. Useful for reading web pages, documentation, or public APIs. HTML is stripped to plain text. Only available in the TUI.",
|
|
178
|
+
input_schema: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
url: { type: "string", description: "The URL to fetch (http or https only)" },
|
|
182
|
+
},
|
|
183
|
+
required: ["url"],
|
|
184
|
+
},
|
|
185
|
+
runtime: "node",
|
|
186
|
+
builtin: true,
|
|
187
|
+
enabled: true,
|
|
188
|
+
created_at: now,
|
|
189
|
+
max_calls_per_interaction: 3,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// --- Tavily Search provider ---
|
|
193
|
+
if (!stateManager.tools_getProviderById("tavily")) {
|
|
194
|
+
const tavilyProvider: ToolProvider = {
|
|
195
|
+
id: "tavily",
|
|
196
|
+
name: "tavily",
|
|
197
|
+
display_name: "Tavily Search",
|
|
198
|
+
description:
|
|
199
|
+
"Browser-compatible web search. Requires a Tavily API key (free tier: 1000 requests/month).",
|
|
200
|
+
builtin: true,
|
|
201
|
+
config: { api_key: "" },
|
|
202
|
+
enabled: false,
|
|
203
|
+
created_at: now,
|
|
204
|
+
};
|
|
205
|
+
stateManager.tools_addProvider(tavilyProvider);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// tavily_web_search
|
|
209
|
+
stateManager.tools_upsertBuiltin({
|
|
210
|
+
id: crypto.randomUUID(),
|
|
211
|
+
provider_id: "tavily",
|
|
212
|
+
name: "tavily_web_search",
|
|
213
|
+
display_name: "Web Search",
|
|
214
|
+
description:
|
|
215
|
+
"Search the web using Tavily. Use for current events, fact verification, or any topic that benefits from up-to-date information.",
|
|
216
|
+
input_schema: {
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
query: { type: "string", description: "Search query" },
|
|
220
|
+
max_results: { type: "number", description: "Number of results (default: 5, max: 10)" },
|
|
221
|
+
},
|
|
222
|
+
required: ["query"],
|
|
223
|
+
},
|
|
224
|
+
runtime: "any",
|
|
225
|
+
builtin: true,
|
|
226
|
+
enabled: true,
|
|
227
|
+
created_at: now,
|
|
228
|
+
max_calls_per_interaction: 3,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// tavily_news_search
|
|
232
|
+
stateManager.tools_upsertBuiltin({
|
|
233
|
+
id: crypto.randomUUID(),
|
|
234
|
+
provider_id: "tavily",
|
|
235
|
+
name: "tavily_news_search",
|
|
236
|
+
display_name: "News Search",
|
|
237
|
+
description:
|
|
238
|
+
"Search recent news articles using Tavily. Use for current events and recent developments.",
|
|
239
|
+
input_schema: {
|
|
240
|
+
type: "object",
|
|
241
|
+
properties: {
|
|
242
|
+
query: { type: "string", description: "News search query" },
|
|
243
|
+
max_results: { type: "number", description: "Number of results (default: 5, max: 10)" },
|
|
244
|
+
},
|
|
245
|
+
required: ["query"],
|
|
246
|
+
},
|
|
247
|
+
runtime: "any",
|
|
248
|
+
builtin: true,
|
|
249
|
+
enabled: true,
|
|
250
|
+
created_at: now,
|
|
251
|
+
max_calls_per_interaction: 3,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// --- Spotify provider ---
|
|
255
|
+
if (!stateManager.tools_getProviderById("spotify")) {
|
|
256
|
+
const spotifyProvider: ToolProvider = {
|
|
257
|
+
id: "spotify",
|
|
258
|
+
name: "spotify",
|
|
259
|
+
display_name: "Spotify",
|
|
260
|
+
description:
|
|
261
|
+
"Access your Spotify playback and music library. Connect via Settings → Tool Kits → Spotify.",
|
|
262
|
+
builtin: true,
|
|
263
|
+
config: { spotify_refresh_token: "" },
|
|
264
|
+
enabled: false,
|
|
265
|
+
created_at: now,
|
|
266
|
+
};
|
|
267
|
+
stateManager.tools_addProvider(spotifyProvider);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// get_currently_playing
|
|
271
|
+
stateManager.tools_upsertBuiltin({
|
|
272
|
+
id: crypto.randomUUID(),
|
|
273
|
+
provider_id: "spotify",
|
|
274
|
+
name: "get_currently_playing",
|
|
275
|
+
display_name: "Currently Playing",
|
|
276
|
+
description:
|
|
277
|
+
"Get the song currently playing on the user's Spotify. Returns artist, title, album, playback state, and progress. Returns nothing_playing if nothing is active.",
|
|
278
|
+
input_schema: {
|
|
279
|
+
type: "object",
|
|
280
|
+
properties: {},
|
|
281
|
+
required: [],
|
|
282
|
+
},
|
|
283
|
+
runtime: "any",
|
|
284
|
+
builtin: true,
|
|
285
|
+
enabled: true,
|
|
286
|
+
created_at: now,
|
|
287
|
+
max_calls_per_interaction: 3,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// get_liked_songs
|
|
291
|
+
stateManager.tools_upsertBuiltin({
|
|
292
|
+
id: crypto.randomUUID(),
|
|
293
|
+
provider_id: "spotify",
|
|
294
|
+
name: "get_liked_songs",
|
|
295
|
+
display_name: "Liked Songs",
|
|
296
|
+
description:
|
|
297
|
+
"Get the user's full Spotify liked songs library. Returns an array of { artist, title, added_at }. Results are cached for 30 minutes. Ask the user before calling — it may return thousands of tracks.",
|
|
298
|
+
input_schema: {
|
|
299
|
+
type: "object",
|
|
300
|
+
properties: {},
|
|
301
|
+
required: [],
|
|
302
|
+
},
|
|
303
|
+
runtime: "any",
|
|
304
|
+
builtin: true,
|
|
305
|
+
enabled: true,
|
|
306
|
+
created_at: now,
|
|
307
|
+
max_calls_per_interaction: 1,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// submit_response tool — auto-injected for Heartbeat steps only (HandleHeartbeatCheck).
|
|
311
|
+
// PersonaResponse and RoomResponse agents now use natural Markdown output instead.
|
|
312
|
+
// Not user-configurable; invisible in the tools UI. Terminates the tool loop immediately
|
|
313
|
+
// when called; its arguments become response.parsed.
|
|
314
|
+
stateManager.tools_upsertBuiltin({
|
|
315
|
+
id: crypto.randomUUID(),
|
|
316
|
+
provider_id: "ei",
|
|
317
|
+
name: "submit_response",
|
|
318
|
+
display_name: "Submit Response",
|
|
319
|
+
description: "Submit your response to the conversation. Call this when you are ready to respond — after any research or tool use is complete.",
|
|
320
|
+
input_schema: {
|
|
321
|
+
type: "object",
|
|
322
|
+
properties: {
|
|
323
|
+
should_respond: {
|
|
324
|
+
type: "boolean",
|
|
325
|
+
description: "Whether you are responding (true) or staying silent (false)",
|
|
326
|
+
},
|
|
327
|
+
content: {
|
|
328
|
+
type: "string",
|
|
329
|
+
description: "Your response in Markdown. Required when should_respond is true. Use _underscores_ for actions or stage directions inline with your text.",
|
|
330
|
+
},
|
|
331
|
+
reason: {
|
|
332
|
+
type: "string",
|
|
333
|
+
description: "Why you are staying silent. Only used when should_respond is false.",
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
required: ["should_respond"],
|
|
337
|
+
additionalProperties: false,
|
|
338
|
+
},
|
|
339
|
+
runtime: "any",
|
|
340
|
+
builtin: true,
|
|
341
|
+
enabled: true,
|
|
342
|
+
is_submit: true,
|
|
343
|
+
max_calls_per_interaction: 1,
|
|
344
|
+
created_at: now,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
stateManager.tools_upsertBuiltin({
|
|
348
|
+
id: crypto.randomUUID(),
|
|
349
|
+
provider_id: "ei",
|
|
350
|
+
name: "submit_heartbeat_check",
|
|
351
|
+
display_name: "Submit Heartbeat Decision",
|
|
352
|
+
description: "Submit your decision on whether to reach out with a message. Call this when you have decided.",
|
|
353
|
+
input_schema: {
|
|
354
|
+
type: "object",
|
|
355
|
+
properties: {
|
|
356
|
+
should_respond: { type: "boolean", description: "Whether you want to initiate a message" },
|
|
357
|
+
topic: { type: "string", description: "The specific topic you want to discuss (when should_respond is true)" },
|
|
358
|
+
message: { type: "string", description: "Your actual message to them (when should_respond is true)" },
|
|
359
|
+
},
|
|
360
|
+
required: ["should_respond"],
|
|
361
|
+
additionalProperties: false,
|
|
362
|
+
},
|
|
363
|
+
runtime: "any",
|
|
364
|
+
builtin: true,
|
|
365
|
+
enabled: true,
|
|
366
|
+
is_submit: true,
|
|
367
|
+
max_calls_per_interaction: 1,
|
|
368
|
+
created_at: now,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
stateManager.tools_upsertBuiltin({
|
|
372
|
+
id: crypto.randomUUID(),
|
|
373
|
+
provider_id: "ei",
|
|
374
|
+
name: "submit_ei_heartbeat",
|
|
375
|
+
display_name: "Submit Ei Heartbeat Decision",
|
|
376
|
+
description: "Submit your choice of item to follow up on, or indicate nothing warrants reaching out.",
|
|
377
|
+
input_schema: {
|
|
378
|
+
type: "object",
|
|
379
|
+
properties: {
|
|
380
|
+
should_respond: { type: "boolean", description: "Whether Ei wants to check in about an item" },
|
|
381
|
+
id: { type: "string", description: "ID of the item you chose (when should_respond is true)" },
|
|
382
|
+
my_response: { type: "string", description: "The check-in message (for Person/Topic/Persona items)" },
|
|
383
|
+
},
|
|
384
|
+
required: ["should_respond"],
|
|
385
|
+
additionalProperties: false,
|
|
386
|
+
},
|
|
387
|
+
runtime: "any",
|
|
388
|
+
builtin: true,
|
|
389
|
+
enabled: true,
|
|
390
|
+
is_submit: true,
|
|
391
|
+
max_calls_per_interaction: 1,
|
|
392
|
+
created_at: now,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
stateManager.tools_upsertBuiltin({
|
|
396
|
+
id: crypto.randomUUID(),
|
|
397
|
+
provider_id: "ei",
|
|
398
|
+
name: "submit_dedup_decisions",
|
|
399
|
+
display_name: "Submit Dedup Decisions",
|
|
400
|
+
description: "Submit your merge, remove, and add decisions for this cluster of records.",
|
|
401
|
+
input_schema: {
|
|
402
|
+
type: "object",
|
|
403
|
+
properties: {
|
|
404
|
+
update: {
|
|
405
|
+
type: "array",
|
|
406
|
+
description: "Records to update with merged data. Must include at least one (the canonical record).",
|
|
407
|
+
items: {
|
|
408
|
+
type: "object",
|
|
409
|
+
properties: {
|
|
410
|
+
id: { type: "string" },
|
|
411
|
+
type: { type: "string", enum: ["topic", "person", "trait"] },
|
|
412
|
+
name: { type: "string" },
|
|
413
|
+
description: { type: "string" },
|
|
414
|
+
sentiment: { type: "number" },
|
|
415
|
+
strength: { type: "number" },
|
|
416
|
+
confidence: { type: "number" },
|
|
417
|
+
exposure_current: { type: "number" },
|
|
418
|
+
exposure_desired: { type: "number" },
|
|
419
|
+
relationship: { type: "string" },
|
|
420
|
+
category: { type: "string" },
|
|
421
|
+
last_updated: { type: "string" },
|
|
422
|
+
},
|
|
423
|
+
required: ["id", "type", "name", "description"],
|
|
424
|
+
additionalProperties: false,
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
remove: {
|
|
428
|
+
type: "array",
|
|
429
|
+
description: "Duplicates to remove. Each must reference its canonical record via replaced_by.",
|
|
430
|
+
items: {
|
|
431
|
+
type: "object",
|
|
432
|
+
properties: {
|
|
433
|
+
to_be_removed: { type: "string" },
|
|
434
|
+
replaced_by: { type: "string" },
|
|
435
|
+
},
|
|
436
|
+
required: ["to_be_removed", "replaced_by"],
|
|
437
|
+
additionalProperties: false,
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
add: {
|
|
441
|
+
type: "array",
|
|
442
|
+
description: "New records to create. Only when merging reveals a missing concept.",
|
|
443
|
+
items: {
|
|
444
|
+
type: "object",
|
|
445
|
+
properties: {
|
|
446
|
+
type: { type: "string", enum: ["topic", "person", "trait"] },
|
|
447
|
+
name: { type: "string" },
|
|
448
|
+
description: { type: "string" },
|
|
449
|
+
sentiment: { type: "number" },
|
|
450
|
+
strength: { type: "number" },
|
|
451
|
+
confidence: { type: "number" },
|
|
452
|
+
exposure_current: { type: "number" },
|
|
453
|
+
exposure_desired: { type: "number" },
|
|
454
|
+
relationship: { type: "string" },
|
|
455
|
+
category: { type: "string" },
|
|
456
|
+
},
|
|
457
|
+
required: ["type", "name", "description"],
|
|
458
|
+
additionalProperties: false,
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
required: ["update", "remove", "add"],
|
|
463
|
+
additionalProperties: false,
|
|
464
|
+
},
|
|
465
|
+
runtime: "any",
|
|
466
|
+
builtin: true,
|
|
467
|
+
enabled: true,
|
|
468
|
+
is_submit: true,
|
|
469
|
+
max_calls_per_interaction: 1,
|
|
470
|
+
created_at: now,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// --- Reconcile pass: prune stale tool references from persona tool lists ---
|
|
474
|
+
// Build manifest of all tool IDs currently in state (everything seeded above).
|
|
475
|
+
const manifestIds = new Set(stateManager.tools_getAll().map(t => t.id));
|
|
476
|
+
|
|
477
|
+
for (const persona of stateManager.persona_getAll()) {
|
|
478
|
+
if (!persona.tools?.length) continue;
|
|
479
|
+
const pruned = persona.tools.filter(id => manifestIds.has(id));
|
|
480
|
+
if (pruned.length !== persona.tools.length) {
|
|
481
|
+
const removed = persona.tools.length - pruned.length;
|
|
482
|
+
stateManager.persona_update(persona.id, { tools: pruned });
|
|
483
|
+
console.log(`[Processor] Pruned ${removed} stale tool reference(s) from persona "${persona.display_name}"`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
@@ -44,8 +44,7 @@ export function handleDocumentSegmentation(response: LLMResponse, state: StateMa
|
|
|
44
44
|
|
|
45
45
|
const emmett = state.persona_getById("emmet");
|
|
46
46
|
if (!emmett) {
|
|
47
|
-
|
|
48
|
-
return;
|
|
47
|
+
throw new Error("[handleDocumentSegmentation] Emmett persona not found — cannot write segments");
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
const now = new Date().toISOString();
|
|
@@ -8,6 +8,7 @@ import type { StateManager } from "../state-manager.js";
|
|
|
8
8
|
import type { HeartbeatCheckResult, EiHeartbeatResult } from "../../prompts/heartbeat/types.js";
|
|
9
9
|
import type { ReflectionCriticResult } from "../../prompts/reflection/types.js";
|
|
10
10
|
import { crossFind } from "../utils/index.js";
|
|
11
|
+
import { qualifyEiMessage } from "../utils/message-id.js";
|
|
11
12
|
|
|
12
13
|
export function handleHeartbeatCheck(response: LLMResponse, state: StateManager): void {
|
|
13
14
|
const personaId = response.request.data.personaId as string;
|
|
@@ -33,7 +34,7 @@ export function handleHeartbeatCheck(response: LLMResponse, state: StateManager)
|
|
|
33
34
|
|
|
34
35
|
if (result.message) {
|
|
35
36
|
const message: Message = {
|
|
36
|
-
id: crypto.randomUUID(),
|
|
37
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
37
38
|
role: "system",
|
|
38
39
|
content: result.message,
|
|
39
40
|
timestamp: now,
|
|
@@ -68,7 +69,7 @@ export function handleEiHeartbeat(response: LLMResponse, state: StateManager): v
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
const sendMessage = (content: string) => state.messages_append("ei", {
|
|
71
|
-
id: crypto.randomUUID(),
|
|
72
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
72
73
|
role: "system",
|
|
73
74
|
content,
|
|
74
75
|
timestamp: now,
|
|
@@ -8,6 +8,7 @@ import type { StateManager } from "../state-manager.js";
|
|
|
8
8
|
import type { PersonaResponseResult } from "../../prompts/response/index.js";
|
|
9
9
|
import { handlers } from "./index.js";
|
|
10
10
|
import { cleanResponseContent } from "../llm-client.js";
|
|
11
|
+
import { qualifyEiMessage } from "../utils/message-id.js";
|
|
11
12
|
|
|
12
13
|
export type ResponseHandler = (response: LLMResponse, state: StateManager) => void | Promise<void>;
|
|
13
14
|
|
|
@@ -30,7 +31,7 @@ export function handlePersonaResponse(response: LLMResponse, state: StateManager
|
|
|
30
31
|
const reason = lines.slice(1).join('\n').trim();
|
|
31
32
|
console.log(`[silence] ${personaDisplayName}: ${reason || "(no reason given)"}`);
|
|
32
33
|
const silentMessage: Message = {
|
|
33
|
-
id: crypto.randomUUID(),
|
|
34
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
34
35
|
role: "system",
|
|
35
36
|
silence_reason: reason || undefined,
|
|
36
37
|
timestamp: new Date().toISOString(),
|
|
@@ -40,7 +41,7 @@ export function handlePersonaResponse(response: LLMResponse, state: StateManager
|
|
|
40
41
|
state.messages_append(personaId, silentMessage);
|
|
41
42
|
} else {
|
|
42
43
|
const message: Message = {
|
|
43
|
-
id: crypto.randomUUID(),
|
|
44
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
44
45
|
role: "system",
|
|
45
46
|
content: raw,
|
|
46
47
|
timestamp: new Date().toISOString(),
|
|
@@ -61,7 +62,7 @@ export function handlePersonaResponse(response: LLMResponse, state: StateManager
|
|
|
61
62
|
if (reason) {
|
|
62
63
|
console.log(`[silence] ${personaDisplayName}: ${reason}`);
|
|
63
64
|
const silentMessage: Message = {
|
|
64
|
-
id: crypto.randomUUID(),
|
|
65
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
65
66
|
role: "system",
|
|
66
67
|
silence_reason: reason,
|
|
67
68
|
timestamp: new Date().toISOString(),
|
|
@@ -83,7 +84,7 @@ export function handlePersonaResponse(response: LLMResponse, state: StateManager
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
const message: Message = {
|
|
86
|
-
id: crypto.randomUUID(),
|
|
87
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
87
88
|
role: "system",
|
|
88
89
|
content,
|
|
89
90
|
timestamp: new Date().toISOString(),
|
|
@@ -9,6 +9,7 @@ import type { PersonaResponseResult } from "../../prompts/response/index.js";
|
|
|
9
9
|
import type { RoomJudgeResult } from "../../prompts/room/index.js";
|
|
10
10
|
import { buildRoomResponsePromptData } from "../prompt-context-builder.js";
|
|
11
11
|
import { cleanResponseContent } from "../llm-client.js";
|
|
12
|
+
import { qualifyEiMessage } from "../utils/message-id.js";
|
|
12
13
|
|
|
13
14
|
export function handleRoomResponse(response: LLMResponse, state: StateManager): void {
|
|
14
15
|
const roomId = response.request.data.roomId as string;
|
|
@@ -31,7 +32,7 @@ export function handleRoomResponse(response: LLMResponse, state: StateManager):
|
|
|
31
32
|
const reason = lines.slice(1).join('\n').trim();
|
|
32
33
|
console.log(`[silence] ${personaDisplayName}: ${reason || "(no reason given)"}`);
|
|
33
34
|
const msg: RoomMessage = {
|
|
34
|
-
id: crypto.randomUUID(),
|
|
35
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
35
36
|
parent_id: parentMessageId,
|
|
36
37
|
role: "persona",
|
|
37
38
|
persona_id: personaId,
|
|
@@ -43,7 +44,7 @@ export function handleRoomResponse(response: LLMResponse, state: StateManager):
|
|
|
43
44
|
state.appendRoomMessage(roomId, msg);
|
|
44
45
|
} else {
|
|
45
46
|
const msg: RoomMessage = {
|
|
46
|
-
id: crypto.randomUUID(),
|
|
47
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
47
48
|
parent_id: parentMessageId,
|
|
48
49
|
role: "persona",
|
|
49
50
|
persona_id: personaId,
|
|
@@ -66,7 +67,7 @@ export function handleRoomResponse(response: LLMResponse, state: StateManager):
|
|
|
66
67
|
console.log(`[silence] ${personaDisplayName}: ${reason ?? "(no reason given)"}`);
|
|
67
68
|
if (reason) {
|
|
68
69
|
const msg: RoomMessage = {
|
|
69
|
-
id: crypto.randomUUID(),
|
|
70
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
70
71
|
parent_id: parentMessageId,
|
|
71
72
|
role: "persona",
|
|
72
73
|
persona_id: personaId,
|
|
@@ -88,7 +89,7 @@ export function handleRoomResponse(response: LLMResponse, state: StateManager):
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
const msg: RoomMessage = {
|
|
91
|
-
id: crypto.randomUUID(),
|
|
92
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
92
93
|
parent_id: parentMessageId,
|
|
93
94
|
role: "persona",
|
|
94
95
|
persona_id: personaId,
|
|
@@ -147,7 +148,7 @@ export async function handleRoomJudge(response: LLMResponse, state: StateManager
|
|
|
147
148
|
if (result.reason) {
|
|
148
149
|
console.log(`[handleRoomJudge] ${judgeDisplayName} verdict: ${result.reason}`);
|
|
149
150
|
const verdictMsg = {
|
|
150
|
-
id: crypto.randomUUID(),
|
|
151
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
151
152
|
parent_id: verdictParentId,
|
|
152
153
|
role: "persona" as const,
|
|
153
154
|
persona_id: judgePersonaId,
|