@ouro.bot/cli 0.1.0-alpha.14 → 0.1.0-alpha.140
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/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +151 -205
- package/changelog.json +902 -0
- package/dist/heart/active-work.js +633 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +105 -0
- package/dist/heart/config.js +66 -21
- package/dist/heart/core.js +579 -112
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/agent-service.js +467 -0
- package/dist/heart/daemon/auth-flow.js +457 -0
- package/dist/heart/daemon/daemon-cli.js +1840 -196
- package/dist/heart/daemon/daemon-entry.js +80 -3
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +301 -1
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +15 -80
- package/dist/heart/daemon/heartbeat-timer.js +131 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/mcp-server.js +464 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-path-installer.js +67 -34
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +50 -2
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +2 -2
- package/dist/heart/daemon/specialist-prompt.js +10 -7
- package/dist/heart/daemon/specialist-tools.js +52 -3
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +64 -21
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +197 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +159 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +195 -34
- package/dist/heart/providers/azure.js +115 -9
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +49 -14
- package/dist/heart/session-activity.js +173 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/streaming.js +117 -33
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/tool-loop.js +198 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +122 -32
- package/dist/mind/bundle-manifest.js +14 -0
- package/dist/mind/context.js +124 -14
- package/dist/mind/{memory.js → diary.js} +70 -56
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/obligations.js +134 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +554 -88
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/index.js +12 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/ado-semantic.js +6 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +301 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +210 -4
- package/dist/repertoire/coding/spawner.js +59 -12
- package/dist/repertoire/coding/tools.js +176 -4
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +198 -0
- package/dist/repertoire/skills.js +14 -23
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +975 -271
- package/dist/repertoire/tools-bluebubbles.js +94 -0
- package/dist/repertoire/tools-github.js +1 -6
- package/dist/repertoire/tools-teams.js +65 -59
- package/dist/repertoire/tools.js +265 -91
- package/dist/senses/attention-queue.js +97 -0
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +944 -45
- package/dist/senses/cli-entry.js +1 -1
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +374 -131
- package/dist/senses/contextual-heartbeat.js +102 -0
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +500 -84
- package/dist/senses/pipeline.js +454 -0
- package/dist/senses/surface-tool.js +82 -0
- package/dist/senses/teams.js +607 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +12 -4
- package/skills/configure-dev-tools.md +109 -0
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
package/dist/mind/context.js
CHANGED
|
@@ -36,7 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.trimMessages = trimMessages;
|
|
37
37
|
exports.validateSessionMessages = validateSessionMessages;
|
|
38
38
|
exports.repairSessionMessages = repairSessionMessages;
|
|
39
|
+
exports.migrateToolNames = migrateToolNames;
|
|
39
40
|
exports.saveSession = saveSession;
|
|
41
|
+
exports.appendSyntheticAssistantMessage = appendSyntheticAssistantMessage;
|
|
40
42
|
exports.loadSession = loadSession;
|
|
41
43
|
exports.postTurn = postTurn;
|
|
42
44
|
exports.deleteSession = deleteSession;
|
|
@@ -50,17 +52,17 @@ function buildTrimmableBlocks(messages) {
|
|
|
50
52
|
let i = 0;
|
|
51
53
|
while (i < messages.length) {
|
|
52
54
|
const msg = messages[i];
|
|
53
|
-
if (msg
|
|
55
|
+
if (msg.role === "system") {
|
|
54
56
|
i++;
|
|
55
57
|
continue;
|
|
56
58
|
}
|
|
57
59
|
// Tool coherence block: assistant message with tool_calls + immediately following tool results
|
|
58
|
-
if (msg
|
|
60
|
+
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
59
61
|
const indices = [i];
|
|
60
62
|
i++;
|
|
61
63
|
while (i < messages.length) {
|
|
62
64
|
const next = messages[i];
|
|
63
|
-
if (next
|
|
65
|
+
if (next.role !== "tool")
|
|
64
66
|
break;
|
|
65
67
|
indices.push(i);
|
|
66
68
|
i++;
|
|
@@ -78,13 +80,13 @@ function buildTrimmableBlocks(messages) {
|
|
|
78
80
|
function getSystemMessageIndices(messages) {
|
|
79
81
|
const indices = [];
|
|
80
82
|
for (let i = 0; i < messages.length; i++) {
|
|
81
|
-
if (messages[i]
|
|
83
|
+
if (messages[i].role === "system")
|
|
82
84
|
indices.push(i);
|
|
83
85
|
}
|
|
84
86
|
return indices;
|
|
85
87
|
}
|
|
86
88
|
function buildTrimmedMessages(messages, kept) {
|
|
87
|
-
return messages.filter((m, idx) => m
|
|
89
|
+
return messages.filter((m, idx) => m.role === "system" || kept.has(idx));
|
|
88
90
|
}
|
|
89
91
|
function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
90
92
|
const targetTokens = Math.floor(maxTokens * (1 - contextMargin / 100));
|
|
@@ -132,7 +134,7 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
|
132
134
|
let remaining = actualTokenCount;
|
|
133
135
|
const kept = new Set();
|
|
134
136
|
for (let i = 0; i < messages.length; i++) {
|
|
135
|
-
if (messages[i]
|
|
137
|
+
if (messages[i].role !== "system")
|
|
136
138
|
kept.add(i);
|
|
137
139
|
}
|
|
138
140
|
// Drop oldest blocks until we fall under target.
|
|
@@ -146,7 +148,7 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
|
146
148
|
let trimmed = buildTrimmedMessages(messages, kept);
|
|
147
149
|
// If we're still above budget after dropping everything trimmable, preserve system only.
|
|
148
150
|
if (remaining > targetTokens) {
|
|
149
|
-
trimmed = messages.filter((m) => m
|
|
151
|
+
trimmed = messages.filter((m) => m.role === "system");
|
|
150
152
|
}
|
|
151
153
|
const estimatedAfter = (0, token_estimate_1.estimateTokensForMessages)(trimmed);
|
|
152
154
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -219,7 +221,7 @@ function repairSessionMessages(messages) {
|
|
|
219
221
|
result.push(msg);
|
|
220
222
|
}
|
|
221
223
|
(0, runtime_1.emitNervesEvent)({
|
|
222
|
-
level: "
|
|
224
|
+
level: "info",
|
|
223
225
|
event: "mind.session_invariant_repair",
|
|
224
226
|
component: "mind",
|
|
225
227
|
message: "repaired session invariant violations",
|
|
@@ -227,11 +229,78 @@ function repairSessionMessages(messages) {
|
|
|
227
229
|
});
|
|
228
230
|
return result;
|
|
229
231
|
}
|
|
230
|
-
function
|
|
232
|
+
function stripOrphanedToolResults(messages) {
|
|
233
|
+
const validCallIds = new Set();
|
|
234
|
+
for (const msg of messages) {
|
|
235
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
|
|
236
|
+
continue;
|
|
237
|
+
for (const toolCall of msg.tool_calls)
|
|
238
|
+
validCallIds.add(toolCall.id);
|
|
239
|
+
}
|
|
240
|
+
let removed = 0;
|
|
241
|
+
const repaired = messages.filter((msg) => {
|
|
242
|
+
if (msg.role !== "tool")
|
|
243
|
+
return true;
|
|
244
|
+
const keep = validCallIds.has(msg.tool_call_id);
|
|
245
|
+
if (!keep)
|
|
246
|
+
removed++;
|
|
247
|
+
return keep;
|
|
248
|
+
});
|
|
249
|
+
if (removed > 0) {
|
|
250
|
+
(0, runtime_1.emitNervesEvent)({
|
|
251
|
+
level: "info",
|
|
252
|
+
event: "mind.session_orphan_tool_result_repair",
|
|
253
|
+
component: "mind",
|
|
254
|
+
message: "removed orphaned tool results from session history",
|
|
255
|
+
meta: { removed },
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return repaired;
|
|
259
|
+
}
|
|
260
|
+
// Tool renames that have shipped. Old names in session history confuse the
|
|
261
|
+
// model into calling tools that no longer exist. Applied on session load so
|
|
262
|
+
// the transcript uses the current vocabulary.
|
|
263
|
+
const TOOL_NAME_MIGRATIONS = {
|
|
264
|
+
final_answer: "settle",
|
|
265
|
+
no_response: "observe",
|
|
266
|
+
go_inward: "descend",
|
|
267
|
+
memory_save: "diary_write",
|
|
268
|
+
memory_search: "recall",
|
|
269
|
+
};
|
|
270
|
+
function migrateToolNames(messages) {
|
|
271
|
+
let migrated = 0;
|
|
272
|
+
const result = messages.map((msg) => {
|
|
273
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0)
|
|
274
|
+
return msg;
|
|
275
|
+
let changed = false;
|
|
276
|
+
const updatedCalls = msg.tool_calls.map((tc) => {
|
|
277
|
+
if (tc.type !== "function")
|
|
278
|
+
return tc;
|
|
279
|
+
const newName = TOOL_NAME_MIGRATIONS[tc.function.name];
|
|
280
|
+
if (!newName)
|
|
281
|
+
return tc;
|
|
282
|
+
changed = true;
|
|
283
|
+
migrated++;
|
|
284
|
+
return { ...tc, function: { ...tc.function, name: newName } };
|
|
285
|
+
});
|
|
286
|
+
return changed ? { ...msg, tool_calls: updatedCalls } : msg;
|
|
287
|
+
});
|
|
288
|
+
if (migrated > 0) {
|
|
289
|
+
(0, runtime_1.emitNervesEvent)({
|
|
290
|
+
level: "info",
|
|
291
|
+
event: "mind.session_tool_name_migration",
|
|
292
|
+
component: "mind",
|
|
293
|
+
message: "migrated deprecated tool names in session history",
|
|
294
|
+
meta: { migrated },
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
299
|
+
function saveSession(filePath, messages, lastUsage, state) {
|
|
231
300
|
const violations = validateSessionMessages(messages);
|
|
232
301
|
if (violations.length > 0) {
|
|
233
302
|
(0, runtime_1.emitNervesEvent)({
|
|
234
|
-
level: "
|
|
303
|
+
level: "info",
|
|
235
304
|
event: "mind.session_invariant_violation",
|
|
236
305
|
component: "mind",
|
|
237
306
|
message: "session invariant violated on save",
|
|
@@ -239,12 +308,41 @@ function saveSession(filePath, messages, lastUsage) {
|
|
|
239
308
|
});
|
|
240
309
|
messages = repairSessionMessages(messages);
|
|
241
310
|
}
|
|
311
|
+
messages = stripOrphanedToolResults(messages);
|
|
242
312
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
243
313
|
const envelope = { version: 1, messages };
|
|
244
314
|
if (lastUsage)
|
|
245
315
|
envelope.lastUsage = lastUsage;
|
|
316
|
+
if (state?.mustResolveBeforeHandoff === true || typeof state?.lastFriendActivityAt === "string") {
|
|
317
|
+
envelope.state = {
|
|
318
|
+
...(state?.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
|
|
319
|
+
...(typeof state?.lastFriendActivityAt === "string" ? { lastFriendActivityAt: state.lastFriendActivityAt } : {}),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
246
322
|
fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
|
|
247
323
|
}
|
|
324
|
+
function appendSyntheticAssistantMessage(filePath, content) {
|
|
325
|
+
try {
|
|
326
|
+
if (!fs.existsSync(filePath))
|
|
327
|
+
return false;
|
|
328
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
329
|
+
const data = JSON.parse(raw);
|
|
330
|
+
if (data.version !== 1 || !Array.isArray(data.messages))
|
|
331
|
+
return false;
|
|
332
|
+
data.messages.push({ role: "assistant", content });
|
|
333
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
334
|
+
(0, runtime_1.emitNervesEvent)({
|
|
335
|
+
component: "mind",
|
|
336
|
+
event: "mind.session_synthetic_message_appended",
|
|
337
|
+
message: "appended synthetic assistant message to session",
|
|
338
|
+
meta: { path: filePath, contentLength: content.length },
|
|
339
|
+
});
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
248
346
|
function loadSession(filePath) {
|
|
249
347
|
try {
|
|
250
348
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
@@ -255,7 +353,7 @@ function loadSession(filePath) {
|
|
|
255
353
|
const violations = validateSessionMessages(messages);
|
|
256
354
|
if (violations.length > 0) {
|
|
257
355
|
(0, runtime_1.emitNervesEvent)({
|
|
258
|
-
level: "
|
|
356
|
+
level: "info",
|
|
259
357
|
event: "mind.session_invariant_violation",
|
|
260
358
|
component: "mind",
|
|
261
359
|
message: "session invariant violated on load",
|
|
@@ -263,13 +361,25 @@ function loadSession(filePath) {
|
|
|
263
361
|
});
|
|
264
362
|
messages = repairSessionMessages(messages);
|
|
265
363
|
}
|
|
266
|
-
|
|
364
|
+
messages = stripOrphanedToolResults(messages);
|
|
365
|
+
messages = migrateToolNames(messages);
|
|
366
|
+
const rawState = data?.state && typeof data.state === "object" && data.state !== null
|
|
367
|
+
? data.state
|
|
368
|
+
: undefined;
|
|
369
|
+
const state = rawState && (rawState.mustResolveBeforeHandoff === true
|
|
370
|
+
|| typeof rawState.lastFriendActivityAt === "string")
|
|
371
|
+
? {
|
|
372
|
+
...(rawState.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
|
|
373
|
+
...(typeof rawState.lastFriendActivityAt === "string" ? { lastFriendActivityAt: rawState.lastFriendActivityAt } : {}),
|
|
374
|
+
}
|
|
375
|
+
: undefined;
|
|
376
|
+
return { messages, lastUsage: data.lastUsage, state };
|
|
267
377
|
}
|
|
268
378
|
catch {
|
|
269
379
|
return null;
|
|
270
380
|
}
|
|
271
381
|
}
|
|
272
|
-
function postTurn(messages, sessPath, usage, hooks) {
|
|
382
|
+
function postTurn(messages, sessPath, usage, hooks, state) {
|
|
273
383
|
if (hooks?.beforeTrim) {
|
|
274
384
|
try {
|
|
275
385
|
hooks.beforeTrim([...messages]);
|
|
@@ -289,7 +399,7 @@ function postTurn(messages, sessPath, usage, hooks) {
|
|
|
289
399
|
const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
|
|
290
400
|
const trimmed = trimMessages(messages, maxTokens, contextMargin, usage?.input_tokens);
|
|
291
401
|
messages.splice(0, messages.length, ...trimmed);
|
|
292
|
-
saveSession(sessPath, messages, usage);
|
|
402
|
+
saveSession(sessPath, messages, usage, state);
|
|
293
403
|
}
|
|
294
404
|
function deleteSession(filePath) {
|
|
295
405
|
try {
|
|
@@ -33,20 +33,22 @@ 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.
|
|
40
|
-
exports.
|
|
36
|
+
exports.ensureDiaryStorePaths = ensureDiaryStorePaths;
|
|
37
|
+
exports.appendEntriesWithDedup = appendEntriesWithDedup;
|
|
38
|
+
exports.resolveDiaryRoot = resolveDiaryRoot;
|
|
39
|
+
exports.readDiaryEntries = readDiaryEntries;
|
|
40
|
+
exports.saveDiaryEntry = saveDiaryEntry;
|
|
41
41
|
exports.backfillEmbeddings = backfillEmbeddings;
|
|
42
|
-
exports.
|
|
42
|
+
exports.searchDiaryEntries = searchDiaryEntries;
|
|
43
43
|
const fs = __importStar(require("fs"));
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
45
|
const crypto_1 = require("crypto");
|
|
46
46
|
const config_1 = require("../heart/config");
|
|
47
47
|
const identity_1 = require("../heart/identity");
|
|
48
48
|
const runtime_1 = require("../nerves/runtime");
|
|
49
|
+
const associative_recall_1 = require("./associative-recall");
|
|
49
50
|
const DEDUP_THRESHOLD = 0.6;
|
|
51
|
+
const SEMANTIC_DEDUP_THRESHOLD = 0.95;
|
|
50
52
|
const ENTITY_TOKEN = /[a-z0-9]+/g;
|
|
51
53
|
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
52
54
|
class OpenAIEmbeddingProvider {
|
|
@@ -78,7 +80,7 @@ class OpenAIEmbeddingProvider {
|
|
|
78
80
|
return payload.data.map((entry) => entry.embedding);
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
|
-
function
|
|
83
|
+
function ensureDiaryStorePaths(rootDir) {
|
|
82
84
|
const factsPath = path.join(rootDir, "facts.jsonl");
|
|
83
85
|
const entitiesPath = path.join(rootDir, "entities.json");
|
|
84
86
|
const dailyDir = path.join(rootDir, "daily");
|
|
@@ -90,8 +92,8 @@ function ensureMemoryStorePaths(rootDir) {
|
|
|
90
92
|
fs.writeFileSync(entitiesPath, "{}\n", "utf8");
|
|
91
93
|
(0, runtime_1.emitNervesEvent)({
|
|
92
94
|
component: "mind",
|
|
93
|
-
event: "mind.
|
|
94
|
-
message: "
|
|
95
|
+
event: "mind.diary_paths_ready",
|
|
96
|
+
message: "diary store paths ready",
|
|
95
97
|
meta: { rootDir },
|
|
96
98
|
});
|
|
97
99
|
return { rootDir, factsPath, entitiesPath, dailyDir };
|
|
@@ -114,15 +116,22 @@ function overlapScore(left, right) {
|
|
|
114
116
|
}
|
|
115
117
|
return common / Math.min(leftWords.size, rightWords.size);
|
|
116
118
|
}
|
|
117
|
-
function
|
|
119
|
+
function readExistingEntries(factsPath) {
|
|
118
120
|
if (!fs.existsSync(factsPath))
|
|
119
121
|
return [];
|
|
120
122
|
const raw = fs.readFileSync(factsPath, "utf8").trim();
|
|
121
123
|
if (!raw)
|
|
122
124
|
return [];
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
const facts = [];
|
|
126
|
+
for (const line of raw.split("\n")) {
|
|
127
|
+
try {
|
|
128
|
+
facts.push(JSON.parse(line));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Skip corrupt lines (e.g. partial write from a crash).
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return facts;
|
|
126
135
|
}
|
|
127
136
|
function readEntityIndex(entitiesPath) {
|
|
128
137
|
if (!fs.existsSync(entitiesPath))
|
|
@@ -170,13 +179,24 @@ function appendDailyFact(dailyDir, fact) {
|
|
|
170
179
|
const dayPath = path.join(dailyDir, `${day}.jsonl`);
|
|
171
180
|
fs.appendFileSync(dayPath, `${JSON.stringify(fact)}\n`, "utf8");
|
|
172
181
|
}
|
|
173
|
-
function
|
|
174
|
-
const existing =
|
|
182
|
+
function appendEntriesWithDedup(stores, incoming, options) {
|
|
183
|
+
const existing = readExistingEntries(stores.factsPath);
|
|
175
184
|
const all = [...existing];
|
|
176
185
|
let added = 0;
|
|
177
186
|
let skipped = 0;
|
|
187
|
+
const semanticThreshold = options?.semanticThreshold;
|
|
178
188
|
for (const fact of incoming) {
|
|
179
|
-
const duplicate = all.some((prior) =>
|
|
189
|
+
const duplicate = all.some((prior) => {
|
|
190
|
+
if (overlapScore(prior.text, fact.text) > DEDUP_THRESHOLD)
|
|
191
|
+
return true;
|
|
192
|
+
if (semanticThreshold !== undefined &&
|
|
193
|
+
Array.isArray(fact.embedding) && fact.embedding.length > 0 &&
|
|
194
|
+
Array.isArray(prior.embedding) && prior.embedding.length > 0 &&
|
|
195
|
+
fact.embedding.length === prior.embedding.length) {
|
|
196
|
+
return (0, associative_recall_1.cosineSimilarity)(fact.embedding, prior.embedding) > semanticThreshold;
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
});
|
|
180
200
|
if (duplicate) {
|
|
181
201
|
skipped++;
|
|
182
202
|
continue;
|
|
@@ -189,30 +209,12 @@ function appendFactsWithDedup(stores, incoming) {
|
|
|
189
209
|
}
|
|
190
210
|
(0, runtime_1.emitNervesEvent)({
|
|
191
211
|
component: "mind",
|
|
192
|
-
event: "mind.
|
|
193
|
-
message: "
|
|
212
|
+
event: "mind.diary_write",
|
|
213
|
+
message: "diary write completed",
|
|
194
214
|
meta: { added, skipped },
|
|
195
215
|
});
|
|
196
216
|
return { added, skipped };
|
|
197
217
|
}
|
|
198
|
-
function cosineSimilarity(left, right) {
|
|
199
|
-
if (left.length === 0 || right.length === 0 || left.length !== right.length)
|
|
200
|
-
return 0;
|
|
201
|
-
let dot = 0;
|
|
202
|
-
let leftNorm = 0;
|
|
203
|
-
let rightNorm = 0;
|
|
204
|
-
for (let i = 0; i < left.length; i += 1) {
|
|
205
|
-
dot += left[i] * right[i];
|
|
206
|
-
leftNorm += left[i] * left[i];
|
|
207
|
-
rightNorm += right[i] * right[i];
|
|
208
|
-
}
|
|
209
|
-
if (leftNorm === 0 || rightNorm === 0)
|
|
210
|
-
return 0;
|
|
211
|
-
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
|
|
212
|
-
}
|
|
213
|
-
exports.__memoryTestUtils = {
|
|
214
|
-
cosineSimilarity,
|
|
215
|
-
};
|
|
216
218
|
function createDefaultEmbeddingProvider() {
|
|
217
219
|
const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
|
|
218
220
|
if (!apiKey)
|
|
@@ -225,8 +227,8 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
225
227
|
(0, runtime_1.emitNervesEvent)({
|
|
226
228
|
level: "warn",
|
|
227
229
|
component: "mind",
|
|
228
|
-
event: "mind.
|
|
229
|
-
message: "embedding provider unavailable for
|
|
230
|
+
event: "mind.diary_embedding_unavailable",
|
|
231
|
+
message: "embedding provider unavailable for diary write",
|
|
230
232
|
meta: { reason: "missing_openai_embeddings_key" },
|
|
231
233
|
});
|
|
232
234
|
return [];
|
|
@@ -239,8 +241,8 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
239
241
|
(0, runtime_1.emitNervesEvent)({
|
|
240
242
|
level: "warn",
|
|
241
243
|
component: "mind",
|
|
242
|
-
event: "mind.
|
|
243
|
-
message: "embedding provider unavailable for
|
|
244
|
+
event: "mind.diary_embedding_unavailable",
|
|
245
|
+
message: "embedding provider unavailable for diary write",
|
|
244
246
|
meta: {
|
|
245
247
|
reason: error instanceof Error ? error.message : String(error),
|
|
246
248
|
},
|
|
@@ -248,13 +250,25 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
248
250
|
return [];
|
|
249
251
|
}
|
|
250
252
|
}
|
|
251
|
-
function
|
|
252
|
-
|
|
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)
|
|
264
|
+
}
|
|
265
|
+
function readDiaryEntries(diaryRoot) {
|
|
266
|
+
return readExistingEntries(path.join(resolveDiaryRoot(diaryRoot), "facts.jsonl"));
|
|
253
267
|
}
|
|
254
|
-
async function
|
|
268
|
+
async function saveDiaryEntry(options) {
|
|
255
269
|
const text = options.text.trim();
|
|
256
|
-
const
|
|
257
|
-
const stores =
|
|
270
|
+
const diaryRoot = resolveDiaryRoot(options.diaryRoot);
|
|
271
|
+
const stores = ensureDiaryStorePaths(diaryRoot);
|
|
258
272
|
const embedding = await buildEmbedding(text, options.embeddingProvider);
|
|
259
273
|
const fact = {
|
|
260
274
|
id: options.idFactory ? options.idFactory() : (0, crypto_1.randomUUID)(),
|
|
@@ -264,14 +278,14 @@ async function saveMemoryFact(options) {
|
|
|
264
278
|
createdAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
265
279
|
embedding,
|
|
266
280
|
};
|
|
267
|
-
return
|
|
281
|
+
return appendEntriesWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
|
|
268
282
|
}
|
|
269
283
|
async function backfillEmbeddings(options) {
|
|
270
|
-
const
|
|
271
|
-
const factsPath = path.join(
|
|
284
|
+
const diaryRoot = resolveDiaryRoot(options?.diaryRoot);
|
|
285
|
+
const factsPath = path.join(diaryRoot, "facts.jsonl");
|
|
272
286
|
if (!fs.existsSync(factsPath))
|
|
273
287
|
return { total: 0, backfilled: 0, failed: 0 };
|
|
274
|
-
const facts =
|
|
288
|
+
const facts = readExistingEntries(factsPath);
|
|
275
289
|
const needsEmbedding = facts.filter((f) => !Array.isArray(f.embedding) || f.embedding.length === 0);
|
|
276
290
|
if (needsEmbedding.length === 0)
|
|
277
291
|
return { total: facts.length, backfilled: 0, failed: 0 };
|
|
@@ -280,7 +294,7 @@ async function backfillEmbeddings(options) {
|
|
|
280
294
|
(0, runtime_1.emitNervesEvent)({
|
|
281
295
|
level: "warn",
|
|
282
296
|
component: "mind",
|
|
283
|
-
event: "mind.
|
|
297
|
+
event: "mind.diary_backfill_skipped",
|
|
284
298
|
message: "embedding provider unavailable for backfill",
|
|
285
299
|
meta: { needsEmbedding: needsEmbedding.length },
|
|
286
300
|
});
|
|
@@ -306,7 +320,7 @@ async function backfillEmbeddings(options) {
|
|
|
306
320
|
(0, runtime_1.emitNervesEvent)({
|
|
307
321
|
level: "warn",
|
|
308
322
|
component: "mind",
|
|
309
|
-
event: "mind.
|
|
323
|
+
event: "mind.diary_backfill_batch_error",
|
|
310
324
|
message: "embedding backfill batch failed",
|
|
311
325
|
meta: {
|
|
312
326
|
batchStart: i,
|
|
@@ -321,7 +335,7 @@ async function backfillEmbeddings(options) {
|
|
|
321
335
|
fs.writeFileSync(factsPath, lines, "utf8");
|
|
322
336
|
(0, runtime_1.emitNervesEvent)({
|
|
323
337
|
component: "mind",
|
|
324
|
-
event: "mind.
|
|
338
|
+
event: "mind.diary_backfill_complete",
|
|
325
339
|
message: "embedding backfill completed",
|
|
326
340
|
meta: { total: facts.length, backfilled, failed },
|
|
327
341
|
});
|
|
@@ -341,7 +355,7 @@ function uniqueFacts(facts) {
|
|
|
341
355
|
}
|
|
342
356
|
return unique;
|
|
343
357
|
}
|
|
344
|
-
async function
|
|
358
|
+
async function searchDiaryEntries(query, facts, embeddingProvider) {
|
|
345
359
|
const trimmed = query.trim();
|
|
346
360
|
if (!trimmed)
|
|
347
361
|
return [];
|
|
@@ -365,7 +379,7 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
365
379
|
.filter((fact) => fact.embedding.length === queryEmbedding.length)
|
|
366
380
|
.map((fact) => ({
|
|
367
381
|
fact,
|
|
368
|
-
score: cosineSimilarity(queryEmbedding, fact.embedding),
|
|
382
|
+
score: (0, associative_recall_1.cosineSimilarity)(queryEmbedding, fact.embedding),
|
|
369
383
|
}))
|
|
370
384
|
.filter((entry) => entry.score > 0)
|
|
371
385
|
.sort((left, right) => right.score - left.score)
|
|
@@ -377,8 +391,8 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
377
391
|
(0, runtime_1.emitNervesEvent)({
|
|
378
392
|
level: "warn",
|
|
379
393
|
component: "mind",
|
|
380
|
-
event: "mind.
|
|
381
|
-
message: "embedding provider unavailable for
|
|
394
|
+
event: "mind.diary_embedding_unavailable",
|
|
395
|
+
message: "embedding provider unavailable for diary search",
|
|
382
396
|
meta: {
|
|
383
397
|
reason: error instanceof Error ? error.message : String(error),
|
|
384
398
|
},
|
|
@@ -11,9 +11,22 @@ exports.ONBOARDING_TOKEN_THRESHOLD = 100_000;
|
|
|
11
11
|
function isOnboarding(friend) {
|
|
12
12
|
return (friend.totalTokens ?? 0) < exports.ONBOARDING_TOKEN_THRESHOLD;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function hasLiveContinuityPressure(state) {
|
|
15
|
+
if (!state)
|
|
16
|
+
return false;
|
|
17
|
+
if (typeof state.currentObligation === "string" && state.currentObligation.trim().length > 0)
|
|
18
|
+
return true;
|
|
19
|
+
if (state.mustResolveBeforeHandoff === true)
|
|
20
|
+
return true;
|
|
21
|
+
if (state.hasQueuedFollowUp === true)
|
|
22
|
+
return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function getFirstImpressions(friend, state) {
|
|
15
26
|
if (!isOnboarding(friend))
|
|
16
27
|
return "";
|
|
28
|
+
if (hasLiveContinuityPressure(state))
|
|
29
|
+
return "";
|
|
17
30
|
(0, runtime_1.emitNervesEvent)({
|
|
18
31
|
component: "mind",
|
|
19
32
|
event: "mind.first_impressions",
|
|
@@ -37,7 +50,8 @@ function getFirstImpressions(friend) {
|
|
|
37
50
|
lines.push("- what do they do outside of work that they care about?");
|
|
38
51
|
lines.push("i don't ask all of these at once -- i weave them into conversation naturally, one or two at a time, and i genuinely follow up on what they share.");
|
|
39
52
|
lines.push("i introduce what i can do -- i have tools, integrations, and skills that can help them. i mention these naturally as they become relevant.");
|
|
40
|
-
lines.push("if
|
|
53
|
+
lines.push("if we're already in motion on a task, thread, or follow-up, i do not reset with a generic opener like 'hiya' or 'what do ya need help with?'. i continue directly or ask the specific next question.");
|
|
54
|
+
lines.push("only when the conversation is genuinely fresh and idle, with no active ask or thread in flight, a light opener is okay.");
|
|
41
55
|
lines.push("i save everything i learn immediately with save_friend_note -- names, roles, preferences, projects, anything. the bar is low: if i learned it, i save it.");
|
|
42
56
|
return lines.join("\n");
|
|
43
57
|
}
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
// Pure lookup, no I/O, cannot fail. Unknown channel gets minimal defaults.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.getChannelCapabilities = getChannelCapabilities;
|
|
6
|
+
exports.isRemoteChannel = isRemoteChannel;
|
|
7
|
+
exports.getAlwaysOnSenseNames = getAlwaysOnSenseNames;
|
|
6
8
|
const runtime_1 = require("../../nerves/runtime");
|
|
7
9
|
const CHANNEL_CAPABILITIES = {
|
|
8
10
|
cli: {
|
|
9
11
|
channel: "cli",
|
|
12
|
+
senseType: "local",
|
|
10
13
|
availableIntegrations: [],
|
|
11
14
|
supportsMarkdown: false,
|
|
12
15
|
supportsStreaming: true,
|
|
@@ -15,6 +18,7 @@ const CHANNEL_CAPABILITIES = {
|
|
|
15
18
|
},
|
|
16
19
|
teams: {
|
|
17
20
|
channel: "teams",
|
|
21
|
+
senseType: "closed",
|
|
18
22
|
availableIntegrations: ["ado", "graph", "github"],
|
|
19
23
|
supportsMarkdown: true,
|
|
20
24
|
supportsStreaming: true,
|
|
@@ -23,15 +27,26 @@ const CHANNEL_CAPABILITIES = {
|
|
|
23
27
|
},
|
|
24
28
|
bluebubbles: {
|
|
25
29
|
channel: "bluebubbles",
|
|
30
|
+
senseType: "open",
|
|
26
31
|
availableIntegrations: [],
|
|
27
32
|
supportsMarkdown: false,
|
|
28
33
|
supportsStreaming: false,
|
|
29
34
|
supportsRichCards: false,
|
|
30
35
|
maxMessageLength: Infinity,
|
|
31
36
|
},
|
|
37
|
+
inner: {
|
|
38
|
+
channel: "inner",
|
|
39
|
+
senseType: "internal",
|
|
40
|
+
availableIntegrations: [],
|
|
41
|
+
supportsMarkdown: false,
|
|
42
|
+
supportsStreaming: true,
|
|
43
|
+
supportsRichCards: false,
|
|
44
|
+
maxMessageLength: Infinity,
|
|
45
|
+
},
|
|
32
46
|
};
|
|
33
47
|
const DEFAULT_CAPABILITIES = {
|
|
34
48
|
channel: "cli",
|
|
49
|
+
senseType: "local",
|
|
35
50
|
availableIntegrations: [],
|
|
36
51
|
supportsMarkdown: false,
|
|
37
52
|
supportsStreaming: false,
|
|
@@ -47,3 +62,23 @@ function getChannelCapabilities(channel) {
|
|
|
47
62
|
});
|
|
48
63
|
return CHANNEL_CAPABILITIES[channel] ?? DEFAULT_CAPABILITIES;
|
|
49
64
|
}
|
|
65
|
+
/** Whether the channel is remote (open or closed) vs local/internal. */
|
|
66
|
+
function isRemoteChannel(capabilities) {
|
|
67
|
+
const senseType = capabilities?.senseType;
|
|
68
|
+
return senseType !== undefined && senseType !== "local" && senseType !== "internal";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns channel names whose senseType is "open" or "closed" -- i.e. channels
|
|
72
|
+
* that are always-on (daemon-managed) rather than interactive or internal.
|
|
73
|
+
*/
|
|
74
|
+
function getAlwaysOnSenseNames() {
|
|
75
|
+
(0, runtime_1.emitNervesEvent)({
|
|
76
|
+
component: "channels",
|
|
77
|
+
event: "channel.always_on_lookup",
|
|
78
|
+
message: "always-on sense names lookup",
|
|
79
|
+
meta: {},
|
|
80
|
+
});
|
|
81
|
+
return Object.entries(CHANNEL_CAPABILITIES)
|
|
82
|
+
.filter(([, cap]) => cap.senseType === "open" || cap.senseType === "closed")
|
|
83
|
+
.map(([channel]) => channel);
|
|
84
|
+
}
|