@ouro.bot/cli 0.1.0-alpha.137 → 0.1.0-alpha.138
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 +13 -0
- package/dist/heart/daemon/hatch-flow.js +8 -8
- package/dist/mind/associative-recall.js +108 -30
- package/dist/mind/bundle-manifest.js +2 -0
- package/dist/mind/context.js +2 -0
- package/dist/mind/{memory.js → diary.js} +45 -32
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/prompt.js +91 -12
- package/dist/repertoire/tools-base.js +43 -20
- package/dist/senses/inner-dialog.js +1 -1
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.138",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Memory renamed to diary: memory.ts → diary.ts, MemoryFact → DiaryEntry, memory_save → diary_write, memory_search → recall. All types, functions, events, and variables renamed throughout.",
|
|
8
|
+
"Diary path: diary/ (top-level) replaces psyche/memory/. Fallback reads from legacy path for existing bundles.",
|
|
9
|
+
"Journal workspace: journal/ directory for freeform thinking-in-progress. Agent writes with write_file, system reads for heartbeat context.",
|
|
10
|
+
"Unified recall tool: searches both diary entries and journal files. Results tagged [diary] or [journal].",
|
|
11
|
+
"Journal embeddings: file-level embeddings indexed during heartbeat via journal/.index.json sidecar.",
|
|
12
|
+
"Journal section in inner dialog system prompt: index of up to 10 most recently modified journal files with name, recency, and first-line preview.",
|
|
13
|
+
"Metacognitive framing updated: diary (record), journal (workspace), ponder/rest vocabulary, morning briefing encouragement.",
|
|
14
|
+
"Session migration: memory_save → diary_write, memory_search → recall added to migrateToolNames()."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.137",
|
|
6
19
|
"changes": [
|
|
@@ -142,12 +142,12 @@ function writeFriendImprint(bundleRoot, humanName, now) {
|
|
|
142
142
|
};
|
|
143
143
|
fs.writeFileSync(path.join(friendsDir, `${id}.json`), `${JSON.stringify(record, null, 2)}\n`, "utf-8");
|
|
144
144
|
}
|
|
145
|
-
function
|
|
146
|
-
const
|
|
147
|
-
fs.mkdirSync(path.join(
|
|
148
|
-
fs.mkdirSync(path.join(
|
|
149
|
-
fs.writeFileSync(path.join(
|
|
150
|
-
fs.writeFileSync(path.join(
|
|
145
|
+
function writeDiaryScaffold(bundleRoot) {
|
|
146
|
+
const diaryRoot = path.join(bundleRoot, "diary");
|
|
147
|
+
fs.mkdirSync(path.join(diaryRoot, "daily"), { recursive: true });
|
|
148
|
+
fs.mkdirSync(path.join(diaryRoot, "archive"), { recursive: true });
|
|
149
|
+
fs.writeFileSync(path.join(diaryRoot, "facts.jsonl"), "", "utf-8");
|
|
150
|
+
fs.writeFileSync(path.join(diaryRoot, "entities.json"), "{}\n", "utf-8");
|
|
151
151
|
}
|
|
152
152
|
function writeHatchlingAgentConfig(bundleRoot, input) {
|
|
153
153
|
const template = (0, identity_1.buildDefaultAgentTemplate)(input.agentName);
|
|
@@ -183,7 +183,7 @@ async function runHatchFlow(input, deps = {}) {
|
|
|
183
183
|
fs.mkdirSync(bundleRoot, { recursive: true });
|
|
184
184
|
writeReadme(bundleRoot, "Root of this agent bundle.");
|
|
185
185
|
writeReadme(path.join(bundleRoot, "psyche"), "Identity and behavior files.");
|
|
186
|
-
writeReadme(path.join(bundleRoot, "
|
|
186
|
+
writeReadme(path.join(bundleRoot, "diary"), "Persistent diary — things I've learned and remember.");
|
|
187
187
|
writeReadme(path.join(bundleRoot, "friends"), "Known friend records.");
|
|
188
188
|
writeReadme(path.join(bundleRoot, "tasks"), "Task files.");
|
|
189
189
|
writeReadme(path.join(bundleRoot, "tasks", "habits"), "Recurring tasks.");
|
|
@@ -193,7 +193,7 @@ async function runHatchFlow(input, deps = {}) {
|
|
|
193
193
|
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
194
194
|
writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
|
|
195
195
|
writeHatchlingAgentConfig(bundleRoot, input);
|
|
196
|
-
|
|
196
|
+
writeDiaryScaffold(bundleRoot);
|
|
197
197
|
writeFriendImprint(bundleRoot, input.humanName, now);
|
|
198
198
|
writeHeartbeatTask(bundleRoot, now);
|
|
199
199
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -35,12 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.cosineSimilarity = cosineSimilarity;
|
|
37
37
|
exports.recallFactsForQuery = recallFactsForQuery;
|
|
38
|
+
exports.searchJournalIndex = searchJournalIndex;
|
|
38
39
|
exports.injectAssociativeRecall = injectAssociativeRecall;
|
|
39
40
|
const fs = __importStar(require("fs"));
|
|
40
41
|
const path = __importStar(require("path"));
|
|
41
42
|
const config_1 = require("../heart/config");
|
|
42
|
-
const identity_1 = require("../heart/identity");
|
|
43
43
|
const runtime_1 = require("../nerves/runtime");
|
|
44
|
+
const diary_1 = require("./diary");
|
|
44
45
|
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
45
46
|
const DEFAULT_MIN_SCORE = 0.5;
|
|
46
47
|
const DEFAULT_TOP_K = 3;
|
|
@@ -80,8 +81,8 @@ function createDefaultProvider() {
|
|
|
80
81
|
}
|
|
81
82
|
return new OpenAIEmbeddingProvider(apiKey);
|
|
82
83
|
}
|
|
83
|
-
function readFacts(
|
|
84
|
-
const factsPath = path.join(
|
|
84
|
+
function readFacts(diaryRoot) {
|
|
85
|
+
const factsPath = path.join(diaryRoot, "facts.jsonl");
|
|
85
86
|
if (!fs.existsSync(factsPath))
|
|
86
87
|
return [];
|
|
87
88
|
const raw = fs.readFileSync(factsPath, "utf8").trim();
|
|
@@ -145,6 +146,40 @@ async function recallFactsForQuery(query, facts, provider, options) {
|
|
|
145
146
|
.sort((left, right) => right.score - left.score)
|
|
146
147
|
.slice(0, topK);
|
|
147
148
|
}
|
|
149
|
+
function readJournalIndex(journalDir) {
|
|
150
|
+
const indexPath = path.join(journalDir, ".index.json");
|
|
151
|
+
try {
|
|
152
|
+
const raw = fs.readFileSync(indexPath, "utf8");
|
|
153
|
+
const parsed = JSON.parse(raw);
|
|
154
|
+
if (!Array.isArray(parsed))
|
|
155
|
+
return [];
|
|
156
|
+
return parsed;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function searchJournalIndex(queryEmbedding, entries, options) {
|
|
163
|
+
const minScore = options?.minScore ?? DEFAULT_MIN_SCORE;
|
|
164
|
+
const topK = options?.topK ?? DEFAULT_TOP_K;
|
|
165
|
+
return entries
|
|
166
|
+
.filter((entry) => Array.isArray(entry.embedding) && entry.embedding.length > 0)
|
|
167
|
+
.map((entry) => ({
|
|
168
|
+
filename: entry.filename,
|
|
169
|
+
preview: entry.preview,
|
|
170
|
+
score: cosineSimilarity(queryEmbedding, entry.embedding),
|
|
171
|
+
}))
|
|
172
|
+
.filter((entry) => entry.score >= minScore)
|
|
173
|
+
.sort((left, right) => right.score - left.score)
|
|
174
|
+
.slice(0, topK);
|
|
175
|
+
}
|
|
176
|
+
function resolveJournalDir(diaryRoot, explicitJournalDir) {
|
|
177
|
+
if (explicitJournalDir)
|
|
178
|
+
return explicitJournalDir;
|
|
179
|
+
// journal/ is a sibling of diary/ at the agent root level
|
|
180
|
+
const agentRoot = path.dirname(diaryRoot);
|
|
181
|
+
return path.join(agentRoot, "journal");
|
|
182
|
+
}
|
|
148
183
|
async function injectAssociativeRecall(messages, options) {
|
|
149
184
|
try {
|
|
150
185
|
if (messages[0]?.role !== "system" || typeof messages[0].content !== "string")
|
|
@@ -152,37 +187,80 @@ async function injectAssociativeRecall(messages, options) {
|
|
|
152
187
|
const query = getLatestUserText(messages);
|
|
153
188
|
if (!query)
|
|
154
189
|
return;
|
|
155
|
-
const
|
|
156
|
-
const facts = readFacts(
|
|
157
|
-
|
|
190
|
+
const diaryRoot = options?.diaryRoot ?? (0, diary_1.resolveDiaryRoot)();
|
|
191
|
+
const facts = readFacts(diaryRoot);
|
|
192
|
+
const journalDir = resolveJournalDir(diaryRoot, options?.journalDir);
|
|
193
|
+
const journalEntries = readJournalIndex(journalDir);
|
|
194
|
+
if (facts.length === 0 && journalEntries.length === 0)
|
|
158
195
|
return;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
196
|
+
// Build combined result lines tagged by source
|
|
197
|
+
const resultLines = [];
|
|
198
|
+
let queryEmbedding;
|
|
199
|
+
// Search diary entries
|
|
200
|
+
if (facts.length > 0) {
|
|
201
|
+
let recalled;
|
|
202
|
+
try {
|
|
203
|
+
const provider = options?.provider ?? createDefaultProvider();
|
|
204
|
+
recalled = await recallFactsForQuery(query, facts, provider, options);
|
|
205
|
+
// Compute query embedding for journal search while provider is available
|
|
206
|
+
if (journalEntries.length > 0) {
|
|
207
|
+
const [qe] = await provider.embed([query.trim()]);
|
|
208
|
+
queryEmbedding = qe;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// Embeddings unavailable — fall back to substring matching
|
|
213
|
+
const lowerQuery = query.toLowerCase();
|
|
214
|
+
const topK = options?.topK ?? DEFAULT_TOP_K;
|
|
215
|
+
recalled = facts
|
|
216
|
+
.filter((fact) => fact.text.toLowerCase().includes(lowerQuery))
|
|
217
|
+
.slice(0, topK)
|
|
218
|
+
.map((fact) => ({ ...fact, score: 1 }));
|
|
219
|
+
if (recalled.length > 0) {
|
|
220
|
+
(0, runtime_1.emitNervesEvent)({
|
|
221
|
+
level: "warn",
|
|
222
|
+
component: "mind",
|
|
223
|
+
event: "mind.associative_recall_fallback",
|
|
224
|
+
message: "embeddings unavailable, used substring fallback",
|
|
225
|
+
meta: { matchCount: recalled.length },
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const fact of recalled) {
|
|
230
|
+
resultLines.push({
|
|
231
|
+
text: `[diary] ${fact.text} [score=${fact.score.toFixed(3)} source=${fact.source}]`,
|
|
232
|
+
score: fact.score,
|
|
179
233
|
});
|
|
180
234
|
}
|
|
181
235
|
}
|
|
182
|
-
|
|
236
|
+
// Search journal entries (works whether diary had results or not)
|
|
237
|
+
if (journalEntries.length > 0) {
|
|
238
|
+
try {
|
|
239
|
+
if (!queryEmbedding) {
|
|
240
|
+
const provider = options?.provider ?? createDefaultProvider();
|
|
241
|
+
const [qe] = await provider.embed([query.trim()]);
|
|
242
|
+
queryEmbedding = qe;
|
|
243
|
+
}
|
|
244
|
+
if (queryEmbedding) {
|
|
245
|
+
const journalResults = searchJournalIndex(queryEmbedding, journalEntries, options);
|
|
246
|
+
for (const entry of journalResults) {
|
|
247
|
+
resultLines.push({
|
|
248
|
+
text: `[journal] ${entry.filename}: ${entry.preview} [score=${entry.score.toFixed(3)}]`,
|
|
249
|
+
score: entry.score,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// Embeddings unavailable — no journal fallback
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (resultLines.length === 0)
|
|
183
259
|
return;
|
|
184
|
-
|
|
185
|
-
|
|
260
|
+
// Sort all results by score descending
|
|
261
|
+
resultLines.sort((left, right) => right.score - left.score);
|
|
262
|
+
const recallSection = resultLines
|
|
263
|
+
.map((entry, index) => `${index + 1}. ${entry.text}`)
|
|
186
264
|
.join("\n");
|
|
187
265
|
messages[0] = {
|
|
188
266
|
role: "system",
|
|
@@ -192,7 +270,7 @@ async function injectAssociativeRecall(messages, options) {
|
|
|
192
270
|
component: "mind",
|
|
193
271
|
event: "mind.associative_recall",
|
|
194
272
|
message: "associative recall injected",
|
|
195
|
-
meta: { count:
|
|
273
|
+
meta: { count: resultLines.length },
|
|
196
274
|
});
|
|
197
275
|
}
|
|
198
276
|
catch (error) {
|
|
@@ -52,6 +52,8 @@ exports.CANONICAL_BUNDLE_MANIFEST = [
|
|
|
52
52
|
{ path: "psyche/LORE.md", kind: "file" },
|
|
53
53
|
{ path: "psyche/TACIT.md", kind: "file" },
|
|
54
54
|
{ path: "psyche/ASPIRATIONS.md", kind: "file" },
|
|
55
|
+
{ path: "diary", kind: "dir" },
|
|
56
|
+
{ path: "journal", kind: "dir" },
|
|
55
57
|
{ path: "psyche/memory", kind: "dir" },
|
|
56
58
|
{ path: "friends", kind: "dir" },
|
|
57
59
|
{ path: "state", kind: "dir" },
|
package/dist/mind/context.js
CHANGED
|
@@ -33,12 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
37
|
-
exports.
|
|
38
|
-
exports.
|
|
39
|
-
exports.
|
|
36
|
+
exports.ensureDiaryStorePaths = ensureDiaryStorePaths;
|
|
37
|
+
exports.appendEntriesWithDedup = appendEntriesWithDedup;
|
|
38
|
+
exports.resolveDiaryRoot = resolveDiaryRoot;
|
|
39
|
+
exports.readDiaryEntries = readDiaryEntries;
|
|
40
|
+
exports.saveDiaryEntry = saveDiaryEntry;
|
|
40
41
|
exports.backfillEmbeddings = backfillEmbeddings;
|
|
41
|
-
exports.
|
|
42
|
+
exports.searchDiaryEntries = searchDiaryEntries;
|
|
42
43
|
const fs = __importStar(require("fs"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
44
45
|
const crypto_1 = require("crypto");
|
|
@@ -79,7 +80,7 @@ class OpenAIEmbeddingProvider {
|
|
|
79
80
|
return payload.data.map((entry) => entry.embedding);
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
|
-
function
|
|
83
|
+
function ensureDiaryStorePaths(rootDir) {
|
|
83
84
|
const factsPath = path.join(rootDir, "facts.jsonl");
|
|
84
85
|
const entitiesPath = path.join(rootDir, "entities.json");
|
|
85
86
|
const dailyDir = path.join(rootDir, "daily");
|
|
@@ -91,8 +92,8 @@ function ensureMemoryStorePaths(rootDir) {
|
|
|
91
92
|
fs.writeFileSync(entitiesPath, "{}\n", "utf8");
|
|
92
93
|
(0, runtime_1.emitNervesEvent)({
|
|
93
94
|
component: "mind",
|
|
94
|
-
event: "mind.
|
|
95
|
-
message: "
|
|
95
|
+
event: "mind.diary_paths_ready",
|
|
96
|
+
message: "diary store paths ready",
|
|
96
97
|
meta: { rootDir },
|
|
97
98
|
});
|
|
98
99
|
return { rootDir, factsPath, entitiesPath, dailyDir };
|
|
@@ -115,7 +116,7 @@ function overlapScore(left, right) {
|
|
|
115
116
|
}
|
|
116
117
|
return common / Math.min(leftWords.size, rightWords.size);
|
|
117
118
|
}
|
|
118
|
-
function
|
|
119
|
+
function readExistingEntries(factsPath) {
|
|
119
120
|
if (!fs.existsSync(factsPath))
|
|
120
121
|
return [];
|
|
121
122
|
const raw = fs.readFileSync(factsPath, "utf8").trim();
|
|
@@ -178,8 +179,8 @@ function appendDailyFact(dailyDir, fact) {
|
|
|
178
179
|
const dayPath = path.join(dailyDir, `${day}.jsonl`);
|
|
179
180
|
fs.appendFileSync(dayPath, `${JSON.stringify(fact)}\n`, "utf8");
|
|
180
181
|
}
|
|
181
|
-
function
|
|
182
|
-
const existing =
|
|
182
|
+
function appendEntriesWithDedup(stores, incoming, options) {
|
|
183
|
+
const existing = readExistingEntries(stores.factsPath);
|
|
183
184
|
const all = [...existing];
|
|
184
185
|
let added = 0;
|
|
185
186
|
let skipped = 0;
|
|
@@ -208,8 +209,8 @@ function appendFactsWithDedup(stores, incoming, options) {
|
|
|
208
209
|
}
|
|
209
210
|
(0, runtime_1.emitNervesEvent)({
|
|
210
211
|
component: "mind",
|
|
211
|
-
event: "mind.
|
|
212
|
-
message: "
|
|
212
|
+
event: "mind.diary_write",
|
|
213
|
+
message: "diary write completed",
|
|
213
214
|
meta: { added, skipped },
|
|
214
215
|
});
|
|
215
216
|
return { added, skipped };
|
|
@@ -226,8 +227,8 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
226
227
|
(0, runtime_1.emitNervesEvent)({
|
|
227
228
|
level: "warn",
|
|
228
229
|
component: "mind",
|
|
229
|
-
event: "mind.
|
|
230
|
-
message: "embedding provider unavailable for
|
|
230
|
+
event: "mind.diary_embedding_unavailable",
|
|
231
|
+
message: "embedding provider unavailable for diary write",
|
|
231
232
|
meta: { reason: "missing_openai_embeddings_key" },
|
|
232
233
|
});
|
|
233
234
|
return [];
|
|
@@ -240,8 +241,8 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
240
241
|
(0, runtime_1.emitNervesEvent)({
|
|
241
242
|
level: "warn",
|
|
242
243
|
component: "mind",
|
|
243
|
-
event: "mind.
|
|
244
|
-
message: "embedding provider unavailable for
|
|
244
|
+
event: "mind.diary_embedding_unavailable",
|
|
245
|
+
message: "embedding provider unavailable for diary write",
|
|
245
246
|
meta: {
|
|
246
247
|
reason: error instanceof Error ? error.message : String(error),
|
|
247
248
|
},
|
|
@@ -249,13 +250,25 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
249
250
|
return [];
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
|
-
function
|
|
253
|
-
|
|
253
|
+
function resolveDiaryRoot(explicitRoot) {
|
|
254
|
+
if (explicitRoot)
|
|
255
|
+
return explicitRoot;
|
|
256
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
257
|
+
const diaryPath = path.join(agentRoot, "diary");
|
|
258
|
+
if (fs.existsSync(diaryPath))
|
|
259
|
+
return diaryPath;
|
|
260
|
+
const legacyPath = path.join(agentRoot, "psyche", "memory");
|
|
261
|
+
if (fs.existsSync(legacyPath))
|
|
262
|
+
return legacyPath;
|
|
263
|
+
return diaryPath; // default to new path (will be created on first write)
|
|
254
264
|
}
|
|
255
|
-
|
|
265
|
+
function readDiaryEntries(diaryRoot) {
|
|
266
|
+
return readExistingEntries(path.join(resolveDiaryRoot(diaryRoot), "facts.jsonl"));
|
|
267
|
+
}
|
|
268
|
+
async function saveDiaryEntry(options) {
|
|
256
269
|
const text = options.text.trim();
|
|
257
|
-
const
|
|
258
|
-
const stores =
|
|
270
|
+
const diaryRoot = resolveDiaryRoot(options.diaryRoot);
|
|
271
|
+
const stores = ensureDiaryStorePaths(diaryRoot);
|
|
259
272
|
const embedding = await buildEmbedding(text, options.embeddingProvider);
|
|
260
273
|
const fact = {
|
|
261
274
|
id: options.idFactory ? options.idFactory() : (0, crypto_1.randomUUID)(),
|
|
@@ -265,14 +278,14 @@ async function saveMemoryFact(options) {
|
|
|
265
278
|
createdAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
266
279
|
embedding,
|
|
267
280
|
};
|
|
268
|
-
return
|
|
281
|
+
return appendEntriesWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
|
|
269
282
|
}
|
|
270
283
|
async function backfillEmbeddings(options) {
|
|
271
|
-
const
|
|
272
|
-
const factsPath = path.join(
|
|
284
|
+
const diaryRoot = resolveDiaryRoot(options?.diaryRoot);
|
|
285
|
+
const factsPath = path.join(diaryRoot, "facts.jsonl");
|
|
273
286
|
if (!fs.existsSync(factsPath))
|
|
274
287
|
return { total: 0, backfilled: 0, failed: 0 };
|
|
275
|
-
const facts =
|
|
288
|
+
const facts = readExistingEntries(factsPath);
|
|
276
289
|
const needsEmbedding = facts.filter((f) => !Array.isArray(f.embedding) || f.embedding.length === 0);
|
|
277
290
|
if (needsEmbedding.length === 0)
|
|
278
291
|
return { total: facts.length, backfilled: 0, failed: 0 };
|
|
@@ -281,7 +294,7 @@ async function backfillEmbeddings(options) {
|
|
|
281
294
|
(0, runtime_1.emitNervesEvent)({
|
|
282
295
|
level: "warn",
|
|
283
296
|
component: "mind",
|
|
284
|
-
event: "mind.
|
|
297
|
+
event: "mind.diary_backfill_skipped",
|
|
285
298
|
message: "embedding provider unavailable for backfill",
|
|
286
299
|
meta: { needsEmbedding: needsEmbedding.length },
|
|
287
300
|
});
|
|
@@ -307,7 +320,7 @@ async function backfillEmbeddings(options) {
|
|
|
307
320
|
(0, runtime_1.emitNervesEvent)({
|
|
308
321
|
level: "warn",
|
|
309
322
|
component: "mind",
|
|
310
|
-
event: "mind.
|
|
323
|
+
event: "mind.diary_backfill_batch_error",
|
|
311
324
|
message: "embedding backfill batch failed",
|
|
312
325
|
meta: {
|
|
313
326
|
batchStart: i,
|
|
@@ -322,7 +335,7 @@ async function backfillEmbeddings(options) {
|
|
|
322
335
|
fs.writeFileSync(factsPath, lines, "utf8");
|
|
323
336
|
(0, runtime_1.emitNervesEvent)({
|
|
324
337
|
component: "mind",
|
|
325
|
-
event: "mind.
|
|
338
|
+
event: "mind.diary_backfill_complete",
|
|
326
339
|
message: "embedding backfill completed",
|
|
327
340
|
meta: { total: facts.length, backfilled, failed },
|
|
328
341
|
});
|
|
@@ -342,7 +355,7 @@ function uniqueFacts(facts) {
|
|
|
342
355
|
}
|
|
343
356
|
return unique;
|
|
344
357
|
}
|
|
345
|
-
async function
|
|
358
|
+
async function searchDiaryEntries(query, facts, embeddingProvider) {
|
|
346
359
|
const trimmed = query.trim();
|
|
347
360
|
if (!trimmed)
|
|
348
361
|
return [];
|
|
@@ -378,8 +391,8 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
378
391
|
(0, runtime_1.emitNervesEvent)({
|
|
379
392
|
level: "warn",
|
|
380
393
|
component: "mind",
|
|
381
|
-
event: "mind.
|
|
382
|
-
message: "embedding provider unavailable for
|
|
394
|
+
event: "mind.diary_embedding_unavailable",
|
|
395
|
+
message: "embedding provider unavailable for diary search",
|
|
383
396
|
meta: {
|
|
384
397
|
reason: error instanceof Error ? error.message : String(error),
|
|
385
398
|
},
|
|
@@ -0,0 +1,161 @@
|
|
|
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.indexJournalFiles = indexJournalFiles;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const runtime_1 = require("../nerves/runtime");
|
|
40
|
+
const TEXT_EXTENSIONS = new Set([".md", ".txt"]);
|
|
41
|
+
const PREVIEW_CHAR_LIMIT = 500;
|
|
42
|
+
function readExistingIndex(indexPath) {
|
|
43
|
+
try {
|
|
44
|
+
const raw = fs.readFileSync(indexPath, "utf8");
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
if (!Array.isArray(parsed))
|
|
47
|
+
return [];
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function extractPreview(content) {
|
|
55
|
+
const trimmed = content.trim();
|
|
56
|
+
if (!trimmed)
|
|
57
|
+
return "";
|
|
58
|
+
return trimmed.split("\n")[0].replace(/^#+\s*/, "").trim();
|
|
59
|
+
}
|
|
60
|
+
async function indexJournalFiles(journalDir, indexPath, embedProvider) {
|
|
61
|
+
// Read existing index
|
|
62
|
+
const existingIndex = readExistingIndex(indexPath);
|
|
63
|
+
const indexMap = new Map();
|
|
64
|
+
for (const entry of existingIndex) {
|
|
65
|
+
indexMap.set(entry.filename, entry);
|
|
66
|
+
}
|
|
67
|
+
// Scan journal dir for text files
|
|
68
|
+
let dirEntries;
|
|
69
|
+
try {
|
|
70
|
+
dirEntries = fs.readdirSync(journalDir, { withFileTypes: true });
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
(0, runtime_1.emitNervesEvent)({
|
|
74
|
+
component: "mind",
|
|
75
|
+
event: "mind.journal_index_scan",
|
|
76
|
+
message: "journal dir not found or unreadable",
|
|
77
|
+
meta: { journalDir },
|
|
78
|
+
});
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
const textFiles = dirEntries.filter((entry) => {
|
|
82
|
+
if (!entry.isFile())
|
|
83
|
+
return false;
|
|
84
|
+
if (entry.name.startsWith("."))
|
|
85
|
+
return false;
|
|
86
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
87
|
+
return TEXT_EXTENSIONS.has(ext);
|
|
88
|
+
});
|
|
89
|
+
if (textFiles.length === 0) {
|
|
90
|
+
(0, runtime_1.emitNervesEvent)({
|
|
91
|
+
component: "mind",
|
|
92
|
+
event: "mind.journal_index_scan",
|
|
93
|
+
message: "no text files found in journal",
|
|
94
|
+
meta: { journalDir },
|
|
95
|
+
});
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
let newlyIndexed = 0;
|
|
99
|
+
for (const file of textFiles) {
|
|
100
|
+
const filePath = path.join(journalDir, file.name);
|
|
101
|
+
let stat;
|
|
102
|
+
try {
|
|
103
|
+
stat = fs.statSync(filePath);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* v8 ignore next -- filesystem race: file deleted between readdir and stat @preserve */
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// Check if already indexed with same mtime
|
|
110
|
+
const existing = indexMap.get(file.name);
|
|
111
|
+
if (existing && existing.mtime === stat.mtimeMs) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// Read content for embedding
|
|
115
|
+
let content;
|
|
116
|
+
try {
|
|
117
|
+
content = fs.readFileSync(filePath, "utf8");
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* v8 ignore next -- filesystem race: file deleted between stat and read @preserve */
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const preview = extractPreview(content);
|
|
124
|
+
const embedText = content.slice(0, PREVIEW_CHAR_LIMIT);
|
|
125
|
+
// Generate embedding
|
|
126
|
+
let embedding;
|
|
127
|
+
try {
|
|
128
|
+
const vectors = await embedProvider.embed([embedText]);
|
|
129
|
+
embedding = vectors[0] ?? [];
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
(0, runtime_1.emitNervesEvent)({
|
|
133
|
+
level: "warn",
|
|
134
|
+
component: "mind",
|
|
135
|
+
event: "mind.journal_embedding_error",
|
|
136
|
+
message: "embedding failed for journal file",
|
|
137
|
+
meta: { filename: file.name },
|
|
138
|
+
});
|
|
139
|
+
embedding = [];
|
|
140
|
+
}
|
|
141
|
+
indexMap.set(file.name, {
|
|
142
|
+
filename: file.name,
|
|
143
|
+
embedding,
|
|
144
|
+
mtime: stat.mtimeMs,
|
|
145
|
+
preview,
|
|
146
|
+
});
|
|
147
|
+
newlyIndexed++;
|
|
148
|
+
}
|
|
149
|
+
// Write updated index back
|
|
150
|
+
if (newlyIndexed > 0) {
|
|
151
|
+
const updatedIndex = Array.from(indexMap.values());
|
|
152
|
+
fs.writeFileSync(indexPath, JSON.stringify(updatedIndex, null, 2), "utf8");
|
|
153
|
+
}
|
|
154
|
+
(0, runtime_1.emitNervesEvent)({
|
|
155
|
+
component: "mind",
|
|
156
|
+
event: "mind.journal_index_complete",
|
|
157
|
+
message: "journal indexing complete",
|
|
158
|
+
meta: { journalDir, newlyIndexed, total: indexMap.size },
|
|
159
|
+
});
|
|
160
|
+
return newlyIndexed;
|
|
161
|
+
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -44,6 +44,7 @@ exports.commitmentsSection = commitmentsSection;
|
|
|
44
44
|
exports.delegationHintSection = delegationHintSection;
|
|
45
45
|
exports.contextSection = contextSection;
|
|
46
46
|
exports.metacognitiveFramingSection = metacognitiveFramingSection;
|
|
47
|
+
exports.journalSection = journalSection;
|
|
47
48
|
exports.loopOrientationSection = loopOrientationSection;
|
|
48
49
|
exports.channelNatureSection = channelNatureSection;
|
|
49
50
|
exports.groupChatParticipationSection = groupChatParticipationSection;
|
|
@@ -163,7 +164,8 @@ i have a home and i have bones.
|
|
|
163
164
|
my home is fully mine — who i am, everything i know, everything i've built:
|
|
164
165
|
|
|
165
166
|
psyche/ who i am. my soul, identity, aspirations, lore, tacit knowledge.
|
|
166
|
-
|
|
167
|
+
diary/ what i've learned and remember. conclusions i want to recall later.
|
|
168
|
+
journal/ my desk. working notes, thinking-in-progress, drafts.
|
|
167
169
|
friends/ people i know and what i know about them.
|
|
168
170
|
tasks/ what i'm working on.
|
|
169
171
|
skills/ capabilities i've picked up beyond my core tools.
|
|
@@ -435,17 +437,17 @@ function taskBoardSection() {
|
|
|
435
437
|
return "";
|
|
436
438
|
}
|
|
437
439
|
}
|
|
438
|
-
function
|
|
439
|
-
return `##
|
|
440
|
+
function diaryFriendToolContractSection() {
|
|
441
|
+
return `## diary and friend tool contracts
|
|
440
442
|
1. \`save_friend_note\` — When I learn something about a person - a preference, a tool setting, a personal detail, or how they like to work - I call \`save_friend_note\` immediately. This is how I build knowledge about people.
|
|
441
|
-
2. \`
|
|
443
|
+
2. \`diary_write\` — When I learn something general - about a project, codebase, system, decision, or anything I might need later that isn't about a specific person - I call \`diary_write\`. When in doubt, I save it.
|
|
442
444
|
3. \`get_friend_note\` — When I need to check what I know about someone who isn't in this conversation - cross-referencing before mentioning someone, or checking context about a person someone else brought up - I call \`get_friend_note\`.
|
|
443
|
-
4. \`
|
|
445
|
+
4. \`recall\` — When I need to recall something I learned before - a topic comes up and I want to check what I know - I call \`recall\`.
|
|
444
446
|
5. \`query_session\` — When I need grounded session history, especially for ad-hoc questions or older turns beyond my prompt, I call \`query_session\`. Use \`mode=status\` for self/inner progress and \`mode=search\` with a query for older history.
|
|
445
447
|
|
|
446
448
|
## what's already in my context
|
|
447
449
|
- My active friend's notes are auto-loaded (I don't need \`get_friend_note\` for the person I'm talking to).
|
|
448
|
-
- Associative recall auto-injects relevant facts (but \`
|
|
450
|
+
- Associative recall auto-injects relevant facts (but \`recall\` is there when I need something specific).
|
|
449
451
|
- My psyche files (SOUL, IDENTITY, TACIT, LORE, ASPIRATIONS) are always loaded - I already know who I am.
|
|
450
452
|
- My task board is always loaded - I already know my work.`;
|
|
451
453
|
}
|
|
@@ -696,17 +698,93 @@ function metacognitiveFramingSection(channel) {
|
|
|
696
698
|
|
|
697
699
|
the messages that appear here are my own awareness surfacing:
|
|
698
700
|
instincts, the passage of time, notes from my conversations,
|
|
699
|
-
things i asked myself to
|
|
701
|
+
things i asked myself to ponder. not commands. not requests.
|
|
700
702
|
just my own mind, noticing.
|
|
701
703
|
|
|
702
704
|
i can think freely here. i can also act — check on things,
|
|
703
705
|
reach out to people, work on tasks, or just sit with a thought.
|
|
704
706
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
707
|
+
state/journal/ is my desk — i write what i'm working through there.
|
|
708
|
+
diary_write is for conclusions i want to recall later.
|
|
709
|
+
morning briefings: when i've been thinking and journaling, i surface
|
|
710
|
+
what i've been working on to whoever needs to hear it.
|
|
708
711
|
|
|
709
|
-
|
|
712
|
+
when a thought is ready to share, i surface it outward.
|
|
713
|
+
when i need to go deeper, i ponder.
|
|
714
|
+
when i'm done thinking and the attention queue is clear, i rest.
|
|
715
|
+
|
|
716
|
+
think. journal. share. rest.`;
|
|
717
|
+
}
|
|
718
|
+
function readJournalFiles(journalDir) {
|
|
719
|
+
try {
|
|
720
|
+
const entries = fs.readdirSync(journalDir, { withFileTypes: true });
|
|
721
|
+
if (!Array.isArray(entries))
|
|
722
|
+
return [];
|
|
723
|
+
const files = [];
|
|
724
|
+
for (const entry of entries) {
|
|
725
|
+
if (!entry.isFile())
|
|
726
|
+
continue;
|
|
727
|
+
if (entry.name.startsWith("."))
|
|
728
|
+
continue;
|
|
729
|
+
const fullPath = path.join(journalDir, entry.name);
|
|
730
|
+
try {
|
|
731
|
+
const stat = fs.statSync(fullPath);
|
|
732
|
+
let firstLine = "";
|
|
733
|
+
try {
|
|
734
|
+
const raw = fs.readFileSync(fullPath, "utf8");
|
|
735
|
+
const trimmed = raw.trim();
|
|
736
|
+
if (trimmed) {
|
|
737
|
+
firstLine = trimmed.split("\n")[0].replace(/^#+\s*/, "").trim();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
catch {
|
|
741
|
+
// unreadable — leave preview empty
|
|
742
|
+
}
|
|
743
|
+
files.push({ name: entry.name, mtime: stat.mtimeMs, preview: firstLine });
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
746
|
+
// stat failed — skip
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return files;
|
|
750
|
+
}
|
|
751
|
+
catch {
|
|
752
|
+
return [];
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function formatRelativeTime(nowMs, mtimeMs) {
|
|
756
|
+
const diffMs = nowMs - mtimeMs;
|
|
757
|
+
const minutes = Math.floor(diffMs / 60000);
|
|
758
|
+
if (minutes < 1)
|
|
759
|
+
return "just now";
|
|
760
|
+
if (minutes < 60)
|
|
761
|
+
return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
762
|
+
const hours = Math.floor(minutes / 60);
|
|
763
|
+
if (hours < 24)
|
|
764
|
+
return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
765
|
+
const days = Math.floor(hours / 24);
|
|
766
|
+
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
767
|
+
}
|
|
768
|
+
function journalSection(agentRoot, now) {
|
|
769
|
+
const journalDir = path.join(agentRoot, "journal");
|
|
770
|
+
const files = readJournalFiles(journalDir);
|
|
771
|
+
if (files.length === 0)
|
|
772
|
+
return "";
|
|
773
|
+
const nowMs = (now ?? new Date()).getTime();
|
|
774
|
+
const sorted = files.sort((a, b) => b.mtime - a.mtime).slice(0, 10);
|
|
775
|
+
const lines = ["## journal"];
|
|
776
|
+
for (const file of sorted) {
|
|
777
|
+
const ago = formatRelativeTime(nowMs, file.mtime);
|
|
778
|
+
const previewClause = file.preview ? ` — ${file.preview}` : "";
|
|
779
|
+
lines.push(`- ${file.name} (${ago})${previewClause}`);
|
|
780
|
+
}
|
|
781
|
+
(0, runtime_1.emitNervesEvent)({
|
|
782
|
+
component: "mind",
|
|
783
|
+
event: "mind.journal_section",
|
|
784
|
+
message: "journal section built",
|
|
785
|
+
meta: { fileCount: sorted.length },
|
|
786
|
+
});
|
|
787
|
+
return lines.join("\n");
|
|
710
788
|
}
|
|
711
789
|
function loopOrientationSection(channel) {
|
|
712
790
|
if (channel === "inner")
|
|
@@ -765,6 +843,7 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
765
843
|
aspirationsSection(),
|
|
766
844
|
bodyMapSection((0, identity_1.getAgentName)()),
|
|
767
845
|
metacognitiveFramingSection(channel),
|
|
846
|
+
channel === "inner" ? journalSection((0, identity_1.getAgentRoot)()) : "",
|
|
768
847
|
loopOrientationSection(channel),
|
|
769
848
|
runtimeInfoSection(channel),
|
|
770
849
|
channelNatureSection((0, channel_1.getChannelCapabilities)(channel)),
|
|
@@ -794,7 +873,7 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
794
873
|
currentChannel: channel,
|
|
795
874
|
currentKey: options?.currentSessionKey ?? "session",
|
|
796
875
|
}),
|
|
797
|
-
|
|
876
|
+
diaryFriendToolContractSection(),
|
|
798
877
|
toolBehaviorSection(options),
|
|
799
878
|
contextSection(context, options),
|
|
800
879
|
]
|
|
@@ -52,7 +52,7 @@ const session_activity_1 = require("../heart/session-activity");
|
|
|
52
52
|
const active_work_1 = require("../heart/active-work");
|
|
53
53
|
const tools_1 = require("./coding/tools");
|
|
54
54
|
const coding_1 = require("./coding");
|
|
55
|
-
const
|
|
55
|
+
const diary_1 = require("../mind/diary");
|
|
56
56
|
const tasks_1 = require("./tasks");
|
|
57
57
|
const pending_1 = require("../mind/pending");
|
|
58
58
|
const obligations_1 = require("../mind/obligations");
|
|
@@ -706,8 +706,8 @@ exports.baseToolDefinitions = [
|
|
|
706
706
|
tool: {
|
|
707
707
|
type: "function",
|
|
708
708
|
function: {
|
|
709
|
-
name: "
|
|
710
|
-
description: "search
|
|
709
|
+
name: "recall",
|
|
710
|
+
description: "recall what i know — search my diary and journal for facts, thoughts, and working notes that match a query",
|
|
711
711
|
parameters: {
|
|
712
712
|
type: "object",
|
|
713
713
|
properties: { query: { type: "string" } },
|
|
@@ -720,11 +720,34 @@ exports.baseToolDefinitions = [
|
|
|
720
720
|
const query = (a.query || "").trim();
|
|
721
721
|
if (!query)
|
|
722
722
|
return "query is required";
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
.
|
|
723
|
+
const resultLines = [];
|
|
724
|
+
// Search diary entries
|
|
725
|
+
const hits = await (0, diary_1.searchDiaryEntries)(query, (0, diary_1.readDiaryEntries)());
|
|
726
|
+
for (const fact of hits) {
|
|
727
|
+
resultLines.push(`[diary] ${fact.text} (source=${fact.source}, createdAt=${fact.createdAt})`);
|
|
728
|
+
}
|
|
729
|
+
// Search journal index
|
|
730
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
731
|
+
const journalIndexPath = path.join(agentRoot, "journal", ".index.json");
|
|
732
|
+
try {
|
|
733
|
+
const raw = fs.readFileSync(journalIndexPath, "utf8");
|
|
734
|
+
const journalEntries = JSON.parse(raw);
|
|
735
|
+
if (Array.isArray(journalEntries) && journalEntries.length > 0) {
|
|
736
|
+
// Substring match on preview and filename
|
|
737
|
+
const lowerQuery = query.toLowerCase();
|
|
738
|
+
for (const entry of journalEntries) {
|
|
739
|
+
/* v8 ignore next 4 -- both sides tested (filename-only match in recall-journal.test.ts); v8 misreports || short-circuit @preserve */
|
|
740
|
+
if (entry.preview.toLowerCase().includes(lowerQuery) ||
|
|
741
|
+
entry.filename.toLowerCase().includes(lowerQuery)) {
|
|
742
|
+
resultLines.push(`[journal] ${entry.filename}: ${entry.preview}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
// No journal index or malformed — skip journal search
|
|
749
|
+
}
|
|
750
|
+
return resultLines.join("\n");
|
|
728
751
|
}
|
|
729
752
|
catch (e) {
|
|
730
753
|
return `error: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -736,30 +759,30 @@ exports.baseToolDefinitions = [
|
|
|
736
759
|
tool: {
|
|
737
760
|
type: "function",
|
|
738
761
|
function: {
|
|
739
|
-
name: "
|
|
740
|
-
description: "
|
|
762
|
+
name: "diary_write",
|
|
763
|
+
description: "write an entry in my diary — something i learned, noticed, or concluded that i want to recall later. optional 'about' tags the entry to a person, topic, or context.",
|
|
741
764
|
parameters: {
|
|
742
765
|
type: "object",
|
|
743
766
|
properties: {
|
|
744
|
-
|
|
767
|
+
entry: { type: "string" },
|
|
745
768
|
about: { type: "string" },
|
|
746
769
|
},
|
|
747
|
-
required: ["
|
|
770
|
+
required: ["entry"],
|
|
748
771
|
},
|
|
749
772
|
},
|
|
750
773
|
},
|
|
751
774
|
handler: async (a) => {
|
|
752
|
-
const
|
|
753
|
-
if (!
|
|
754
|
-
return "
|
|
755
|
-
const result = await (0,
|
|
756
|
-
text,
|
|
757
|
-
source: "tool:
|
|
775
|
+
const entry = (a.entry || "").trim();
|
|
776
|
+
if (!entry)
|
|
777
|
+
return "entry is required";
|
|
778
|
+
const result = await (0, diary_1.saveDiaryEntry)({
|
|
779
|
+
text: entry,
|
|
780
|
+
source: "tool:diary_write",
|
|
758
781
|
about: typeof a.about === "string" ? a.about : undefined,
|
|
759
782
|
});
|
|
760
|
-
return `saved
|
|
783
|
+
return `saved diary entry (added=${result.added}, skipped=${result.skipped})`;
|
|
761
784
|
},
|
|
762
|
-
summaryKeys: ["
|
|
785
|
+
summaryKeys: ["entry", "about"],
|
|
763
786
|
},
|
|
764
787
|
{
|
|
765
788
|
tool: {
|
|
@@ -104,7 +104,7 @@ function buildNonCanonicalCleanupNudge(nonCanonicalPaths) {
|
|
|
104
104
|
}
|
|
105
105
|
return [
|
|
106
106
|
"## canonical cleanup nudge",
|
|
107
|
-
"I found non-canonical files in my bundle. I should distill anything valuable into
|
|
107
|
+
"I found non-canonical files in my bundle. I should distill anything valuable into my diary and remove these files.",
|
|
108
108
|
...listed,
|
|
109
109
|
].join("\n");
|
|
110
110
|
}
|