@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.
- package/changelog.json +10 -0
- package/dist/arc/cares.js +7 -3
- package/dist/arc/episodes.js +4 -3
- package/dist/arc/intentions.js +2 -1
- package/dist/arc/obligations.js +18 -6
- package/dist/arc/packets.js +9 -8
- package/dist/heart/awaiting/await-runtime-state.js +4 -1
- package/dist/heart/bridges/store.js +14 -2
- package/dist/heart/daemon/daemon.js +47 -0
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/sense-manager.js +12 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +107 -2
- package/dist/heart/mailbox/readers/sessions.js +2 -2
- package/dist/heart/session-events.js +73 -66
- package/dist/heart/session-transcript.js +6 -116
- package/dist/mailbox-ui/assets/{index-CtUWEo-S.js → index-9-AxCxuB.js} +3 -3
- package/dist/mailbox-ui/assets/index-CWzt267f.css +1 -0
- package/dist/mailbox-ui/index.html +2 -2
- package/dist/mind/diary.js +6 -1
- package/dist/mind/friends/store-file.js +9 -1
- package/dist/mind/journal-index.js +2 -1
- package/dist/mind/pending.js +2 -1
- package/dist/mind/prompt.js +6 -5
- package/dist/repertoire/coding/context-pack.js +3 -2
- package/dist/repertoire/coding/manager.js +6 -2
- package/dist/repertoire/tools-awaiting.js +11 -6
- package/dist/repertoire/tools-base.js +2 -0
- package/dist/repertoire/tools-bridge.js +0 -1
- package/dist/repertoire/tools-record.js +463 -0
- package/dist/repertoire/tools-runtime.js +87 -0
- package/dist/repertoire/tools-session.js +9 -37
- package/dist/senses/bluebubbles/index.js +1 -1
- package/package.json +1 -1
- 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
|
|
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
|
|
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: "
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
|
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]");
|