@ouro.bot/cli 0.1.0-alpha.611 → 0.1.0-alpha.612

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.
Files changed (34) hide show
  1. package/changelog.json +10 -0
  2. package/dist/arc/cares.js +7 -3
  3. package/dist/arc/episodes.js +4 -3
  4. package/dist/arc/intentions.js +2 -1
  5. package/dist/arc/obligations.js +18 -6
  6. package/dist/arc/packets.js +9 -8
  7. package/dist/heart/awaiting/await-runtime-state.js +4 -1
  8. package/dist/heart/bridges/store.js +14 -2
  9. package/dist/heart/daemon/daemon.js +47 -0
  10. package/dist/heart/daemon/process-manager.js +13 -0
  11. package/dist/heart/daemon/sense-manager.js +12 -0
  12. package/dist/heart/mailbox/readers/runtime-readers.js +107 -2
  13. package/dist/heart/mailbox/readers/sessions.js +2 -2
  14. package/dist/heart/session-events.js +73 -66
  15. package/dist/heart/session-transcript.js +6 -116
  16. package/dist/mailbox-ui/assets/{index-CtUWEo-S.js → index-9-AxCxuB.js} +3 -3
  17. package/dist/mailbox-ui/assets/index-CWzt267f.css +1 -0
  18. package/dist/mailbox-ui/index.html +2 -2
  19. package/dist/mind/diary.js +6 -1
  20. package/dist/mind/friends/store-file.js +9 -1
  21. package/dist/mind/journal-index.js +2 -1
  22. package/dist/mind/pending.js +2 -1
  23. package/dist/mind/prompt.js +6 -5
  24. package/dist/repertoire/coding/context-pack.js +3 -2
  25. package/dist/repertoire/coding/manager.js +6 -2
  26. package/dist/repertoire/tools-awaiting.js +11 -6
  27. package/dist/repertoire/tools-base.js +2 -0
  28. package/dist/repertoire/tools-bridge.js +0 -1
  29. package/dist/repertoire/tools-record.js +463 -0
  30. package/dist/repertoire/tools-runtime.js +87 -0
  31. package/dist/repertoire/tools-session.js +9 -37
  32. package/dist/senses/bluebubbles/index.js +1 -1
  33. package/package.json +1 -1
  34. package/dist/mailbox-ui/assets/index-BPr5vNuM.css +0 -1
@@ -0,0 +1,463 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.recordToolDefinitions = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const session_events_1 = require("../heart/session-events");
40
+ const identity_1 = require("../heart/identity");
41
+ const embedding_provider_1 = require("../mind/embedding-provider");
42
+ const note_search_1 = require("../mind/note-search");
43
+ const runtime_1 = require("../nerves/runtime");
44
+ const NOTES_INDEX_VERSION = 1;
45
+ const NOTE_SLUG_MAX_CHARS = 40;
46
+ const DEFAULT_LIMIT = 5;
47
+ const MAX_LIMIT = 25;
48
+ const DEFAULT_MIN_SCORE = 0.5;
49
+ const PREVIEW_CHAR_LIMIT = 500;
50
+ function hasSelfTrust(ctx) {
51
+ const channel = ctx?.context?.channel?.channel;
52
+ if (channel !== "inner")
53
+ return false;
54
+ const friend = ctx?.context?.friend;
55
+ return !friend || friend.id === "self";
56
+ }
57
+ function normalizeTags(value) {
58
+ if (value === undefined || value === null)
59
+ return undefined;
60
+ const rawTags = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
61
+ const tags = rawTags
62
+ .map((tag) => (typeof tag === "string" ? tag.trim() : ""))
63
+ .filter((tag) => tag.length > 0);
64
+ return tags.length > 0 ? tags : undefined;
65
+ }
66
+ function slugForContent(content) {
67
+ const slug = content
68
+ .toLowerCase()
69
+ .replace(/[^a-z0-9]+/g, "-")
70
+ .replace(/^-+|-+$/g, "")
71
+ .slice(0, NOTE_SLUG_MAX_CHARS)
72
+ .replace(/-+$/g, "");
73
+ return slug || "note";
74
+ }
75
+ function renderNote(createdAt, content, tags) {
76
+ const frontmatter = [`created_at: ${createdAt}`];
77
+ if (tags?.length) {
78
+ frontmatter.push("tags:");
79
+ for (const tag of tags) {
80
+ frontmatter.push(` - ${JSON.stringify(tag)}`);
81
+ }
82
+ }
83
+ return `---\n${frontmatter.join("\n")}\n---\n${content}\n`;
84
+ }
85
+ function ensureUniquePath(notesDir, date, slug) {
86
+ let candidate = path.join(notesDir, `${date}-${slug}.md`);
87
+ let suffix = 2;
88
+ while (fs.existsSync(candidate)) {
89
+ const suffixText = `-${suffix}`;
90
+ const cappedSlug = `${slug.slice(0, Math.max(0, NOTE_SLUG_MAX_CHARS - suffixText.length)).replace(/-+$/g, "")}${suffixText}`;
91
+ candidate = path.join(notesDir, `${date}-${cappedSlug}.md`);
92
+ suffix += 1;
93
+ }
94
+ return candidate;
95
+ }
96
+ function extractPreview(body) {
97
+ const firstLine = body.trim().split("\n").find((line) => line.trim().length > 0);
98
+ return (0, session_events_1.capStructuredRecordString)(firstLine.trim()).slice(0, PREVIEW_CHAR_LIMIT);
99
+ }
100
+ function parseTagsFromFrontmatter(lines, startIndex) {
101
+ const tags = [];
102
+ let i = startIndex;
103
+ while (lines[i + 1]?.trimStart().startsWith("- ")) {
104
+ i += 1;
105
+ tags.push(lines[i].trimStart().slice(2).trim().replace(/^"|"$/g, ""));
106
+ }
107
+ return { tags, nextIndex: i };
108
+ }
109
+ function parseCanonicalNote(filePath, stat) {
110
+ const raw = fs.readFileSync(filePath, "utf8");
111
+ let body = raw;
112
+ let createdAt;
113
+ let tags;
114
+ if (raw.startsWith("---\n")) {
115
+ const end = raw.indexOf("\n---\n", 4);
116
+ if (end > 0) {
117
+ const frontmatterRaw = raw.slice(4, end);
118
+ body = raw.slice(end + "\n---\n".length);
119
+ const lines = frontmatterRaw.split("\n");
120
+ for (let i = 0; i < lines.length; i++) {
121
+ const line = lines[i];
122
+ if (!line.trim())
123
+ continue;
124
+ const colonIndex = line.indexOf(":");
125
+ if (colonIndex < 0)
126
+ continue;
127
+ const key = line.slice(0, colonIndex);
128
+ const value = line.slice(colonIndex + 1).trim();
129
+ if (key === "created_at" && value) {
130
+ createdAt = value.replace(/^"|"$/g, "");
131
+ }
132
+ if (key === "tags") {
133
+ if (value.startsWith("[") && value.endsWith("]")) {
134
+ try {
135
+ const parsed = JSON.parse(value);
136
+ tags = parsed.filter((tag) => typeof tag === "string");
137
+ }
138
+ catch {
139
+ tags = undefined;
140
+ }
141
+ }
142
+ else {
143
+ const parsedTags = parseTagsFromFrontmatter(lines, i);
144
+ tags = parsedTags.tags.length > 0 ? parsedTags.tags : undefined;
145
+ i = parsedTags.nextIndex;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ const trimmedBody = body.trim();
152
+ if (!trimmedBody)
153
+ return null;
154
+ return {
155
+ filename: path.basename(filePath),
156
+ filePath,
157
+ body: trimmedBody,
158
+ preview: extractPreview(trimmedBody),
159
+ createdAt,
160
+ tags,
161
+ mtimeMs: stat.mtimeMs,
162
+ size: stat.size,
163
+ };
164
+ }
165
+ function listCanonicalNotes(notesDir) {
166
+ let dirEntries;
167
+ try {
168
+ dirEntries = fs.readdirSync(notesDir, { withFileTypes: true });
169
+ }
170
+ catch {
171
+ return [];
172
+ }
173
+ return dirEntries
174
+ .filter((entry) => entry.isFile() && !entry.name.startsWith(".") && entry.name.endsWith(".md"))
175
+ .map((entry) => {
176
+ const filePath = path.join(notesDir, entry.name);
177
+ try {
178
+ return parseCanonicalNote(filePath, fs.statSync(filePath));
179
+ }
180
+ catch {
181
+ return null;
182
+ }
183
+ })
184
+ .filter((record) => record !== null)
185
+ .sort((left, right) => left.filename.localeCompare(right.filename));
186
+ }
187
+ function readIndex(indexPath) {
188
+ try {
189
+ const parsed = JSON.parse(fs.readFileSync(indexPath, "utf8"));
190
+ if (parsed.version !== NOTES_INDEX_VERSION || !Array.isArray(parsed.entries))
191
+ return null;
192
+ if (!parsed.entries.every(isIndexEntry))
193
+ return null;
194
+ return parsed;
195
+ }
196
+ catch {
197
+ return null;
198
+ }
199
+ }
200
+ function isIndexEntry(value) {
201
+ if (!value || typeof value !== "object")
202
+ return false;
203
+ const entry = value;
204
+ return (typeof entry.filename === "string" &&
205
+ typeof entry.path === "string" &&
206
+ typeof entry.preview === "string" &&
207
+ Array.isArray(entry.embedding) &&
208
+ entry.embedding.every((item) => typeof item === "number") &&
209
+ typeof entry.mtimeMs === "number" &&
210
+ typeof entry.size === "number");
211
+ }
212
+ function entryMatchesRecord(entry, record) {
213
+ return (entry.filename === record.filename &&
214
+ entry.path === record.filePath &&
215
+ entry.mtimeMs === record.mtimeMs &&
216
+ entry.size === record.size &&
217
+ entry.preview === record.preview);
218
+ }
219
+ function indexFreshForRecords(index, records) {
220
+ if (!index)
221
+ return false;
222
+ if (index.entries.length !== records.length)
223
+ return false;
224
+ const recordsByFilename = new Map(records.map((record) => [record.filename, record]));
225
+ const seenFilenames = new Set();
226
+ for (const entry of index.entries) {
227
+ if (seenFilenames.has(entry.filename))
228
+ return false;
229
+ const record = recordsByFilename.get(entry.filename);
230
+ if (!record || !entryMatchesRecord(entry, record))
231
+ return false;
232
+ seenFilenames.add(entry.filename);
233
+ }
234
+ return seenFilenames.size === recordsByFilename.size;
235
+ }
236
+ function indexFreshExcept(index, records, filename) {
237
+ if (!index)
238
+ return false;
239
+ const otherRecords = records.filter((record) => record.filename !== filename);
240
+ const otherEntries = index.entries.filter((entry) => entry.filename !== filename);
241
+ return indexFreshForRecords({ version: NOTES_INDEX_VERSION, entries: otherEntries }, otherRecords);
242
+ }
243
+ function makeEntry(record, embedding) {
244
+ return {
245
+ filename: record.filename,
246
+ path: record.filePath,
247
+ preview: record.preview,
248
+ embedding,
249
+ ...(record.createdAt ? { created_at: record.createdAt } : {}),
250
+ ...(record.tags ? { tags: record.tags } : {}),
251
+ mtimeMs: record.mtimeMs,
252
+ size: record.size,
253
+ };
254
+ }
255
+ function writeIndex(indexPath, index) {
256
+ fs.writeFileSync(indexPath, `${JSON.stringify(index, null, 2)}\n`, "utf8");
257
+ }
258
+ async function embedRecord(provider, record) {
259
+ const [embedding] = await provider.embed([record.body]);
260
+ return makeEntry(record, embedding);
261
+ }
262
+ async function rebuildNotesIndex(notesDir, indexPath, provider) {
263
+ const records = listCanonicalNotes(notesDir);
264
+ const entries = [];
265
+ for (const record of records) {
266
+ entries.push(await embedRecord(provider, record));
267
+ }
268
+ const index = { version: NOTES_INDEX_VERSION, entries };
269
+ writeIndex(indexPath, index);
270
+ (0, runtime_1.emitNervesEvent)({
271
+ component: "repertoire",
272
+ event: "repertoire.record_notes_index_rebuilt",
273
+ message: "notes native index rebuilt",
274
+ meta: { notesDir, count: entries.length },
275
+ });
276
+ return index;
277
+ }
278
+ async function updateIndexForSavedNote(notesDir, indexPath, savedPath, provider) {
279
+ const savedRecord = parseCanonicalNote(savedPath, fs.statSync(savedPath));
280
+ const records = [
281
+ ...listCanonicalNotes(notesDir).filter((record) => record.filePath !== savedPath),
282
+ savedRecord,
283
+ ].sort((left, right) => left.filename.localeCompare(right.filename));
284
+ const existing = readIndex(indexPath);
285
+ if (!indexFreshExcept(existing, records, savedRecord.filename)) {
286
+ await rebuildNotesIndex(notesDir, indexPath, provider);
287
+ return;
288
+ }
289
+ const savedEntry = await embedRecord(provider, savedRecord);
290
+ const entries = [
291
+ ...existing.entries.filter((entry) => entry.filename !== savedRecord.filename),
292
+ savedEntry,
293
+ ].sort((left, right) => left.filename.localeCompare(right.filename));
294
+ writeIndex(indexPath, { version: NOTES_INDEX_VERSION, entries });
295
+ }
296
+ async function getFreshIndex(notesDir, indexPath, provider) {
297
+ const records = listCanonicalNotes(notesDir);
298
+ const existing = readIndex(indexPath);
299
+ if (indexFreshForRecords(existing, records))
300
+ return existing;
301
+ return rebuildNotesIndex(notesDir, indexPath, provider);
302
+ }
303
+ function createProviderForTool(toolName) {
304
+ const provider = (0, embedding_provider_1.createDefaultEmbeddingProvider)();
305
+ if (!provider) {
306
+ return `error: ${toolName} couldn't use notes because embeddings are not configured.`;
307
+ }
308
+ return provider;
309
+ }
310
+ function parseLimit(raw) {
311
+ const parsed = typeof raw === "string" ? Number.parseInt(raw, 10) : typeof raw === "number" ? raw : DEFAULT_LIMIT;
312
+ if (!Number.isFinite(parsed) || parsed <= 0)
313
+ return DEFAULT_LIMIT;
314
+ return Math.min(Math.floor(parsed), MAX_LIMIT);
315
+ }
316
+ function parseMinScore(raw) {
317
+ const parsed = typeof raw === "string" ? Number.parseFloat(raw) : typeof raw === "number" ? raw : DEFAULT_MIN_SCORE;
318
+ if (!Number.isFinite(parsed))
319
+ return DEFAULT_MIN_SCORE;
320
+ return parsed;
321
+ }
322
+ function parseCursor(raw) {
323
+ const parsed = typeof raw === "string" ? Number.parseInt(raw, 10) : typeof raw === "number" ? raw : 0;
324
+ if (!Number.isFinite(parsed) || parsed < 0)
325
+ return 0;
326
+ return Math.floor(parsed);
327
+ }
328
+ exports.recordToolDefinitions = [
329
+ {
330
+ tool: {
331
+ type: "function",
332
+ function: {
333
+ name: "note",
334
+ description: "Write a durable self note as canonical markdown in my notes folder. Only available to my self/inner context, not external callers.",
335
+ parameters: {
336
+ type: "object",
337
+ properties: {
338
+ content: { type: "string" },
339
+ tags: {
340
+ type: "array",
341
+ items: { type: "string" },
342
+ },
343
+ },
344
+ required: ["content"],
345
+ },
346
+ },
347
+ },
348
+ handler: async (args, ctx) => {
349
+ if (!hasSelfTrust(ctx))
350
+ return "error: note requires self trust and cannot be used from an external caller context.";
351
+ const rawArgs = args;
352
+ const content = typeof rawArgs.content === "string" ? rawArgs.content.trim() : "";
353
+ if (!content)
354
+ return "content is required";
355
+ const provider = createProviderForTool("note");
356
+ if (typeof provider === "string")
357
+ return provider;
358
+ const cappedContent = (0, session_events_1.capStructuredRecordString)(content);
359
+ const createdAt = new Date().toISOString();
360
+ const date = createdAt.slice(0, 10);
361
+ const tags = normalizeTags(rawArgs.tags);
362
+ const notesDir = path.join((0, identity_1.getAgentRoot)(), "notes");
363
+ const indexPath = path.join(notesDir, ".index.json");
364
+ try {
365
+ fs.mkdirSync(notesDir, { recursive: true });
366
+ const savedPath = ensureUniquePath(notesDir, date, slugForContent(content));
367
+ fs.writeFileSync(savedPath, renderNote(createdAt, cappedContent, tags), "utf8");
368
+ await updateIndexForSavedNote(notesDir, indexPath, savedPath, provider);
369
+ (0, runtime_1.emitNervesEvent)({
370
+ component: "repertoire",
371
+ event: "repertoire.record_note_saved",
372
+ message: "canonical note saved",
373
+ meta: { path: savedPath, hasTags: Boolean(tags?.length) },
374
+ });
375
+ return savedPath;
376
+ }
377
+ catch (error) {
378
+ (0, runtime_1.emitNervesEvent)({
379
+ level: "warn",
380
+ component: "repertoire",
381
+ event: "repertoire.record_note_save_error",
382
+ message: "canonical note save failed",
383
+ meta: { reason: error instanceof Error ? error.message : String(error) },
384
+ });
385
+ return "error: couldn't save the note right now.";
386
+ }
387
+ },
388
+ summaryKeys: ["content", "tags"],
389
+ },
390
+ {
391
+ tool: {
392
+ type: "function",
393
+ function: {
394
+ name: "consult_notes",
395
+ description: "Search my canonical markdown notes semantically using the notes-native index. Only available to my self/inner context, not external callers.",
396
+ parameters: {
397
+ type: "object",
398
+ properties: {
399
+ query: { type: "string" },
400
+ cursor: { type: "string" },
401
+ limit: { type: "string" },
402
+ minScore: { type: "string" },
403
+ },
404
+ required: ["query"],
405
+ },
406
+ },
407
+ },
408
+ handler: async (args, ctx) => {
409
+ if (!hasSelfTrust(ctx))
410
+ return "error: consult_notes requires self trust and cannot be used from an external caller context.";
411
+ const rawArgs = args;
412
+ const query = typeof rawArgs.query === "string" ? rawArgs.query.trim() : "";
413
+ if (!query)
414
+ return JSON.stringify({ items: [] });
415
+ const notesDir = path.join((0, identity_1.getAgentRoot)(), "notes");
416
+ const indexPath = path.join(notesDir, ".index.json");
417
+ const records = listCanonicalNotes(notesDir);
418
+ if (records.length === 0)
419
+ return JSON.stringify({ items: [] });
420
+ const provider = createProviderForTool("consult_notes");
421
+ if (typeof provider === "string")
422
+ return provider;
423
+ try {
424
+ const index = await getFreshIndex(notesDir, indexPath, provider);
425
+ const [queryEmbedding] = await provider.embed([query]);
426
+ const minScore = parseMinScore(rawArgs.minScore);
427
+ const offset = parseCursor(rawArgs.cursor);
428
+ const limit = parseLimit(rawArgs.limit);
429
+ const ranked = index.entries
430
+ .filter((entry) => entry.embedding.length > 0)
431
+ .map((entry) => ({
432
+ path: entry.path,
433
+ filename: entry.filename,
434
+ excerpt: entry.preview,
435
+ score: (0, note_search_1.cosineSimilarity)(queryEmbedding, entry.embedding),
436
+ }))
437
+ .filter((entry) => entry.score >= minScore)
438
+ .sort((left, right) => right.score - left.score);
439
+ const items = ranked.slice(offset, offset + limit);
440
+ const nextOffset = offset + limit;
441
+ const result = nextOffset < ranked.length ? { items, nextCursor: String(nextOffset) } : { items };
442
+ (0, runtime_1.emitNervesEvent)({
443
+ component: "repertoire",
444
+ event: "repertoire.record_notes_consulted",
445
+ message: "canonical notes consulted",
446
+ meta: { count: items.length, totalMatches: ranked.length },
447
+ });
448
+ return JSON.stringify(result);
449
+ }
450
+ catch (error) {
451
+ (0, runtime_1.emitNervesEvent)({
452
+ level: "warn",
453
+ component: "repertoire",
454
+ event: "repertoire.record_notes_consult_error",
455
+ message: "canonical notes consult failed",
456
+ meta: { reason: error instanceof Error ? error.message : String(error) },
457
+ });
458
+ return "error: consult_notes couldn't search notes right now.";
459
+ }
460
+ },
461
+ summaryKeys: ["query"],
462
+ },
463
+ ];
@@ -34,6 +34,63 @@ async function restartRuntime(args, agentName) {
34
34
  });
35
35
  }
36
36
  }
37
+ async function reviveSense(args, agentName) {
38
+ if (args.agent !== undefined) {
39
+ return "cross-agent revive is unsupported; revive_sense can only revive this agent's own senses.";
40
+ }
41
+ if (typeof args.sense !== "string" || args.sense.trim().length === 0) {
42
+ return JSON.stringify({ error: "sense is required (for example, 'bluebubbles')" });
43
+ }
44
+ if (typeof args.reason !== "string" || args.reason.trim().length === 0) {
45
+ return JSON.stringify({ error: "reason is required (one-line audit string)" });
46
+ }
47
+ const sense = args.sense.trim();
48
+ const reason = args.reason.trim();
49
+ (0, runtime_1.emitNervesEvent)({
50
+ component: "repertoire",
51
+ event: "repertoire.sense_revive_requested",
52
+ message: "agent requested sense revive",
53
+ meta: { agent: agentName, sense, reason },
54
+ });
55
+ try {
56
+ const response = await (0, socket_client_1.sendDaemonCommand)(socket_client_1.DEFAULT_DAEMON_SOCKET_PATH, {
57
+ kind: "daemon.sense_revive",
58
+ agent: agentName,
59
+ sense,
60
+ reason,
61
+ });
62
+ if (!response.ok) {
63
+ if (response.error === "Unknown daemon command kind 'daemon.sense_revive'.") {
64
+ return JSON.stringify({
65
+ error: "daemon does not support this command; try restart_runtime",
66
+ detail: response.error,
67
+ agent: agentName,
68
+ sense,
69
+ });
70
+ }
71
+ return JSON.stringify({
72
+ error: response.error ?? "daemon failed to revive sense",
73
+ agent: agentName,
74
+ sense,
75
+ });
76
+ }
77
+ return JSON.stringify({
78
+ revived: true,
79
+ agent: agentName,
80
+ sense,
81
+ detail: response.message ?? "sense revive requested",
82
+ snapshot: response.data,
83
+ });
84
+ }
85
+ catch (error) {
86
+ return JSON.stringify({
87
+ error: "failed to reach daemon socket",
88
+ detail: error instanceof Error ? error.message : String(error),
89
+ agent: agentName,
90
+ sense,
91
+ });
92
+ }
93
+ }
37
94
  exports.runtimeToolDefinitions = [
38
95
  {
39
96
  tool: {
@@ -58,4 +115,34 @@ exports.runtimeToolDefinitions = [
58
115
  return restartRuntime({ reason: args.reason }, agentName);
59
116
  },
60
117
  },
118
+ {
119
+ tool: {
120
+ type: "function",
121
+ function: {
122
+ name: "revive_sense",
123
+ description: "revive one of my managed senses after it has wedged or landed in permanent failure. this only works for my own senses, requires family trust, and sends the daemon a one-line reason for the audit log. use restart_runtime instead if the daemon is too old to support sense-level revive.",
124
+ parameters: {
125
+ type: "object",
126
+ properties: {
127
+ sense: {
128
+ type: "string",
129
+ description: "managed sense name to revive, for example 'bluebubbles'.",
130
+ },
131
+ reason: {
132
+ type: "string",
133
+ description: "one-line audit reason for the revive request.",
134
+ },
135
+ },
136
+ required: ["sense", "reason"],
137
+ },
138
+ },
139
+ },
140
+ handler: async (args, ctx) => {
141
+ if (ctx?.context?.friend?.trustLevel !== "family") {
142
+ return "revive_sense requires family trust before I can revive runtime senses.";
143
+ }
144
+ const agentName = (0, identity_1.getAgentName)();
145
+ return reviveSense({ agent: args.agent, sense: args.sense, reason: args.reason }, agentName);
146
+ },
147
+ },
61
148
  ];
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
40
  const config_1 = require("../heart/config");
41
41
  const identity_1 = require("../heart/identity");
42
+ const session_events_1 = require("../heart/session-events");
42
43
  const runtime_1 = require("../nerves/runtime");
43
44
  const socket_client_1 = require("../heart/daemon/socket-client");
44
45
  const thoughts_1 = require("../heart/daemon/thoughts");
@@ -88,14 +89,6 @@ async function summarizeSessionTailSafely(options) {
88
89
  return { kind: "missing" };
89
90
  }
90
91
  }
91
- async function searchSessionSafely(options) {
92
- try {
93
- return await (0, session_transcript_1.searchSessionTranscript)(options);
94
- }
95
- catch {
96
- return { kind: "missing" };
97
- }
98
- }
99
92
  function normalizeProgressOutcome(text) {
100
93
  const trimmed = text.trim();
101
94
  /* v8 ignore next -- defensive: normalizeProgressOutcome null branch @preserve */
@@ -111,7 +104,7 @@ function writePendingEnvelope(queueDir, message) {
111
104
  fs.mkdirSync(queueDir, { recursive: true });
112
105
  const fileName = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
113
106
  const filePath = path.join(queueDir, fileName);
114
- fs.writeFileSync(filePath, JSON.stringify(message, null, 2));
107
+ fs.writeFileSync(filePath, JSON.stringify({ ...message, content: (0, session_events_1.capStructuredRecordString)(message.content) }, null, 2));
115
108
  }
116
109
  function renderCrossChatDeliveryStatus(target, result) {
117
110
  const phase = result.status === "delivered_now"
@@ -384,7 +377,7 @@ exports.sessionToolDefinitions = [
384
377
  type: "function",
385
378
  function: {
386
379
  name: "query_session",
387
- description: "inspect another session. use transcript for recent context, status for self/inner progress, or search to find older history by query.",
380
+ description: "inspect another session. use transcript for recent context or status for self/inner progress. deprecated search invocations should use search_notes or consult_notes instead.",
388
381
  parameters: {
389
382
  type: "object",
390
383
  properties: {
@@ -395,9 +388,9 @@ exports.sessionToolDefinitions = [
395
388
  mode: {
396
389
  type: "string",
397
390
  enum: ["transcript", "status", "search"],
398
- description: "transcript (default), lightweight status for self/inner checks, or search for older history",
391
+ description: "transcript (default), lightweight status for self/inner checks, or deprecated search; use search_notes or consult_notes instead",
399
392
  },
400
- query: { type: "string", description: "required when mode=search; search term for older session history" },
393
+ query: { type: "string", description: "deprecated when mode=search; use search_notes or consult_notes instead" },
401
394
  },
402
395
  required: ["friendId", "channel"],
403
396
  },
@@ -426,31 +419,11 @@ exports.sessionToolDefinitions = [
426
419
  return renderInnerProgressStatus((0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir));
427
420
  }
428
421
  if (mode === "search") {
429
- const query = (args.query || "").trim();
430
- if (!query) {
431
- return "search mode requires a non-empty query.";
432
- }
433
- const search = await searchSessionSafely({
434
- sessionPath: (0, config_1.resolveSessionPath)(friendId, channel, key),
435
- friendId,
436
- channel,
437
- key,
438
- query,
422
+ return JSON.stringify({
423
+ kind: "deprecated",
424
+ message: "query_session mode=search is no longer available; use search_notes or consult_notes instead.",
425
+ removalCycle: "alpha.616",
439
426
  });
440
- if (search.kind === "missing") {
441
- return NO_SESSION_FOUND_MESSAGE;
442
- }
443
- if (search.kind === "empty") {
444
- return EMPTY_SESSION_MESSAGE;
445
- }
446
- if (search.kind === "no_match") {
447
- return `no matches for "${search.query}" in that session.\n\n${search.snapshot}`;
448
- }
449
- return [
450
- `history search: "${search.query}"`,
451
- search.snapshot,
452
- ...search.matches.map((match, index) => `match ${index + 1}\n${match}`),
453
- ].join("\n\n");
454
427
  }
455
428
  const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
456
429
  const sessionTail = await summarizeSessionTailSafely({
@@ -461,7 +434,6 @@ exports.sessionToolDefinitions = [
461
434
  messageCount: count,
462
435
  trustLevel: ctx?.context?.friend?.trustLevel,
463
436
  summarize: ctx?.summarize,
464
- archiveFallback: true,
465
437
  });
466
438
  if (sessionTail.kind === "missing") {
467
439
  return NO_SESSION_FOUND_MESSAGE;
@@ -401,7 +401,7 @@ function buildConversationScopePrefix(event, existingMessages, repliedToText) {
401
401
  if (repliedToText) {
402
402
  lines.push(`[replying to: "${repliedToText}"]`);
403
403
  }
404
- lines.push(`[if you need more context about what was being discussed, use query_session to search your session history, or search_notes to search diary/journal notes.]`);
404
+ lines.push(`[if you need more context about what was being discussed, use search_notes or consult_notes to search durable diary/journal notes.]`);
405
405
  }
406
406
  else {
407
407
  lines.push("[conversation scope: existing chat trunk | current inbound lane: top_level | default outbound target for this turn: top_level]");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.611",
3
+ "version": "0.1.0-alpha.612",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",