@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.200
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/README.md +187 -191
- package/SerpentGuide.ouro/agent.json +82 -0
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +6 -3
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +2 -2
- package/assets/ouroboros.png +0 -0
- package/changelog.json +1248 -0
- package/dist/heart/active-work.js +832 -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/cares.js +179 -0
- package/dist/heart/commitments.js +111 -0
- package/dist/heart/config.js +114 -28
- package/dist/heart/core.js +689 -120
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-config-check.js +149 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/auth-flow.js +477 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/daemon-cli.js +2897 -193
- package/dist/heart/daemon/daemon-entry.js +194 -8
- package/dist/heart/daemon/daemon-health.js +136 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +231 -0
- package/dist/heart/daemon/daemon-tombstone.js +123 -0
- package/dist/heart/daemon/daemon.js +488 -13
- package/dist/heart/daemon/habit-migration.js +181 -0
- package/dist/heart/daemon/habit-parser.js +119 -0
- package/dist/heart/daemon/habit-scheduler.js +369 -0
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +32 -122
- package/dist/heart/daemon/hatch-specialist.js +7 -2
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +206 -0
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/launchd.js +165 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/mcp-server.js +591 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- package/dist/heart/daemon/ouro-path-installer.js +221 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/outlook-http.js +439 -0
- package/dist/heart/daemon/outlook-read.js +1618 -0
- package/dist/heart/daemon/outlook-render.js +1032 -0
- package/dist/heart/daemon/outlook-types.js +7 -0
- package/dist/heart/daemon/outlook-view.js +194 -0
- package/dist/heart/daemon/process-manager.js +94 -1
- package/dist/heart/daemon/run-hooks.js +39 -0
- package/dist/heart/daemon/runtime-logging.js +61 -14
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +314 -0
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- 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 +129 -0
- package/dist/heart/daemon/specialist-prompt.js +101 -0
- package/dist/heart/daemon/specialist-tools.js +283 -0
- package/dist/heart/daemon/staged-restart.js +138 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- 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 +220 -52
- package/dist/heart/intentions.js +173 -0
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/migrate-config.js +127 -0
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +225 -0
- package/dist/heart/presence.js +185 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +178 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +199 -36
- package/dist/heart/providers/azure.js +116 -10
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +51 -16
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/session-activity.js +172 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/start-of-turn-packet.js +232 -0
- package/dist/heart/streaming.js +120 -33
- package/dist/heart/sync.js +217 -0
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/tool-activity-callbacks.js +36 -0
- package/dist/heart/tool-description.js +135 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +122 -32
- package/dist/mind/bundle-manifest.js +77 -1
- package/dist/mind/context.js +125 -14
- package/dist/mind/{memory.js → diary.js} +69 -56
- package/dist/mind/episodes.js +119 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +64 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +36 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +9 -1
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/obligations.js +138 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +861 -105
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/audit.js +1 -1
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +63 -3
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
- package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
- package/dist/outlook-ui/index.html +15 -0
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/ado-semantic.js +11 -5
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +330 -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 +58 -12
- package/dist/repertoire/coding/tools.js +209 -7
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +291 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +251 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +14 -23
- package/dist/repertoire/tasks/board.js +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +28 -10
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +11 -28
- package/dist/repertoire/tools-base.js +1360 -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 +318 -91
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +97 -0
- package/dist/senses/bluebubbles-client.js +532 -0
- package/dist/senses/bluebubbles-entry.js +13 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +261 -0
- package/dist/senses/bluebubbles-mutation-log.js +116 -0
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +1327 -0
- package/dist/senses/cli/adapter.js +274 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +349 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +83 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +198 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +1 -1
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +687 -262
- package/dist/senses/commands.js +65 -1
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +112 -19
- package/dist/senses/inner-dialog.js +592 -84
- package/dist/senses/pipeline.js +693 -0
- package/dist/senses/shared-turn.js +196 -0
- package/dist/senses/surface-tool.js +95 -0
- package/dist/senses/teams.js +817 -151
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +24 -7
- package/skills/configure-dev-tools.md +81 -0
- package/subagents/README.md +4 -70
- package/AdoptionSpecialist.ouro/agent.json +0 -20
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
|
@@ -33,20 +33,23 @@ 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 sync_1 = require("../heart/sync");
|
|
50
|
+
const associative_recall_1 = require("./associative-recall");
|
|
49
51
|
const DEDUP_THRESHOLD = 0.6;
|
|
52
|
+
const SEMANTIC_DEDUP_THRESHOLD = 0.95;
|
|
50
53
|
const ENTITY_TOKEN = /[a-z0-9]+/g;
|
|
51
54
|
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
52
55
|
class OpenAIEmbeddingProvider {
|
|
@@ -78,7 +81,7 @@ class OpenAIEmbeddingProvider {
|
|
|
78
81
|
return payload.data.map((entry) => entry.embedding);
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
|
-
function
|
|
84
|
+
function ensureDiaryStorePaths(rootDir) {
|
|
82
85
|
const factsPath = path.join(rootDir, "facts.jsonl");
|
|
83
86
|
const entitiesPath = path.join(rootDir, "entities.json");
|
|
84
87
|
const dailyDir = path.join(rootDir, "daily");
|
|
@@ -90,8 +93,8 @@ function ensureMemoryStorePaths(rootDir) {
|
|
|
90
93
|
fs.writeFileSync(entitiesPath, "{}\n", "utf8");
|
|
91
94
|
(0, runtime_1.emitNervesEvent)({
|
|
92
95
|
component: "mind",
|
|
93
|
-
event: "mind.
|
|
94
|
-
message: "
|
|
96
|
+
event: "mind.diary_paths_ready",
|
|
97
|
+
message: "diary store paths ready",
|
|
95
98
|
meta: { rootDir },
|
|
96
99
|
});
|
|
97
100
|
return { rootDir, factsPath, entitiesPath, dailyDir };
|
|
@@ -114,15 +117,22 @@ function overlapScore(left, right) {
|
|
|
114
117
|
}
|
|
115
118
|
return common / Math.min(leftWords.size, rightWords.size);
|
|
116
119
|
}
|
|
117
|
-
function
|
|
120
|
+
function readExistingEntries(factsPath) {
|
|
118
121
|
if (!fs.existsSync(factsPath))
|
|
119
122
|
return [];
|
|
120
123
|
const raw = fs.readFileSync(factsPath, "utf8").trim();
|
|
121
124
|
if (!raw)
|
|
122
125
|
return [];
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
const facts = [];
|
|
127
|
+
for (const line of raw.split("\n")) {
|
|
128
|
+
try {
|
|
129
|
+
facts.push(JSON.parse(line));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Skip corrupt lines (e.g. partial write from a crash).
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return facts;
|
|
126
136
|
}
|
|
127
137
|
function readEntityIndex(entitiesPath) {
|
|
128
138
|
if (!fs.existsSync(entitiesPath))
|
|
@@ -170,13 +180,24 @@ function appendDailyFact(dailyDir, fact) {
|
|
|
170
180
|
const dayPath = path.join(dailyDir, `${day}.jsonl`);
|
|
171
181
|
fs.appendFileSync(dayPath, `${JSON.stringify(fact)}\n`, "utf8");
|
|
172
182
|
}
|
|
173
|
-
function
|
|
174
|
-
const existing =
|
|
183
|
+
function appendEntriesWithDedup(stores, incoming, options) {
|
|
184
|
+
const existing = readExistingEntries(stores.factsPath);
|
|
175
185
|
const all = [...existing];
|
|
176
186
|
let added = 0;
|
|
177
187
|
let skipped = 0;
|
|
188
|
+
const semanticThreshold = options?.semanticThreshold;
|
|
178
189
|
for (const fact of incoming) {
|
|
179
|
-
const duplicate = all.some((prior) =>
|
|
190
|
+
const duplicate = all.some((prior) => {
|
|
191
|
+
if (overlapScore(prior.text, fact.text) > DEDUP_THRESHOLD)
|
|
192
|
+
return true;
|
|
193
|
+
if (semanticThreshold !== undefined &&
|
|
194
|
+
Array.isArray(fact.embedding) && fact.embedding.length > 0 &&
|
|
195
|
+
Array.isArray(prior.embedding) && prior.embedding.length > 0 &&
|
|
196
|
+
fact.embedding.length === prior.embedding.length) {
|
|
197
|
+
return (0, associative_recall_1.cosineSimilarity)(fact.embedding, prior.embedding) > semanticThreshold;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
});
|
|
180
201
|
if (duplicate) {
|
|
181
202
|
skipped++;
|
|
182
203
|
continue;
|
|
@@ -184,35 +205,20 @@ function appendFactsWithDedup(stores, incoming) {
|
|
|
184
205
|
all.push(fact);
|
|
185
206
|
added++;
|
|
186
207
|
fs.appendFileSync(stores.factsPath, `${JSON.stringify(fact)}\n`, "utf8");
|
|
208
|
+
(0, sync_1.trackSyncWrite)(stores.factsPath);
|
|
187
209
|
updateEntityIndex(stores.entitiesPath, fact);
|
|
210
|
+
(0, sync_1.trackSyncWrite)(stores.entitiesPath);
|
|
188
211
|
appendDailyFact(stores.dailyDir, fact);
|
|
212
|
+
(0, sync_1.trackSyncWrite)(path.join(stores.dailyDir, `${(fact.createdAt.slice(0, 10) || "unknown")}.jsonl`));
|
|
189
213
|
}
|
|
190
214
|
(0, runtime_1.emitNervesEvent)({
|
|
191
215
|
component: "mind",
|
|
192
|
-
event: "mind.
|
|
193
|
-
message: "
|
|
216
|
+
event: "mind.diary_write",
|
|
217
|
+
message: "diary write completed",
|
|
194
218
|
meta: { added, skipped },
|
|
195
219
|
});
|
|
196
220
|
return { added, skipped };
|
|
197
221
|
}
|
|
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
222
|
function createDefaultEmbeddingProvider() {
|
|
217
223
|
const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
|
|
218
224
|
if (!apiKey)
|
|
@@ -225,8 +231,8 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
225
231
|
(0, runtime_1.emitNervesEvent)({
|
|
226
232
|
level: "warn",
|
|
227
233
|
component: "mind",
|
|
228
|
-
event: "mind.
|
|
229
|
-
message: "embedding provider unavailable for
|
|
234
|
+
event: "mind.diary_embedding_unavailable",
|
|
235
|
+
message: "embedding provider unavailable for diary write",
|
|
230
236
|
meta: { reason: "missing_openai_embeddings_key" },
|
|
231
237
|
});
|
|
232
238
|
return [];
|
|
@@ -239,8 +245,8 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
239
245
|
(0, runtime_1.emitNervesEvent)({
|
|
240
246
|
level: "warn",
|
|
241
247
|
component: "mind",
|
|
242
|
-
event: "mind.
|
|
243
|
-
message: "embedding provider unavailable for
|
|
248
|
+
event: "mind.diary_embedding_unavailable",
|
|
249
|
+
message: "embedding provider unavailable for diary write",
|
|
244
250
|
meta: {
|
|
245
251
|
reason: error instanceof Error ? error.message : String(error),
|
|
246
252
|
},
|
|
@@ -248,13 +254,19 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
248
254
|
return [];
|
|
249
255
|
}
|
|
250
256
|
}
|
|
251
|
-
function
|
|
252
|
-
|
|
257
|
+
function resolveDiaryRoot(explicitRoot) {
|
|
258
|
+
if (explicitRoot)
|
|
259
|
+
return explicitRoot;
|
|
260
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
261
|
+
return path.join(agentRoot, "diary");
|
|
262
|
+
}
|
|
263
|
+
function readDiaryEntries(diaryRoot) {
|
|
264
|
+
return readExistingEntries(path.join(resolveDiaryRoot(diaryRoot), "facts.jsonl"));
|
|
253
265
|
}
|
|
254
|
-
async function
|
|
266
|
+
async function saveDiaryEntry(options) {
|
|
255
267
|
const text = options.text.trim();
|
|
256
|
-
const
|
|
257
|
-
const stores =
|
|
268
|
+
const diaryRoot = resolveDiaryRoot(options.diaryRoot);
|
|
269
|
+
const stores = ensureDiaryStorePaths(diaryRoot);
|
|
258
270
|
const embedding = await buildEmbedding(text, options.embeddingProvider);
|
|
259
271
|
const fact = {
|
|
260
272
|
id: options.idFactory ? options.idFactory() : (0, crypto_1.randomUUID)(),
|
|
@@ -264,14 +276,14 @@ async function saveMemoryFact(options) {
|
|
|
264
276
|
createdAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
265
277
|
embedding,
|
|
266
278
|
};
|
|
267
|
-
return
|
|
279
|
+
return appendEntriesWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
|
|
268
280
|
}
|
|
269
281
|
async function backfillEmbeddings(options) {
|
|
270
|
-
const
|
|
271
|
-
const factsPath = path.join(
|
|
282
|
+
const diaryRoot = resolveDiaryRoot(options?.diaryRoot);
|
|
283
|
+
const factsPath = path.join(diaryRoot, "facts.jsonl");
|
|
272
284
|
if (!fs.existsSync(factsPath))
|
|
273
285
|
return { total: 0, backfilled: 0, failed: 0 };
|
|
274
|
-
const facts =
|
|
286
|
+
const facts = readExistingEntries(factsPath);
|
|
275
287
|
const needsEmbedding = facts.filter((f) => !Array.isArray(f.embedding) || f.embedding.length === 0);
|
|
276
288
|
if (needsEmbedding.length === 0)
|
|
277
289
|
return { total: facts.length, backfilled: 0, failed: 0 };
|
|
@@ -280,7 +292,7 @@ async function backfillEmbeddings(options) {
|
|
|
280
292
|
(0, runtime_1.emitNervesEvent)({
|
|
281
293
|
level: "warn",
|
|
282
294
|
component: "mind",
|
|
283
|
-
event: "mind.
|
|
295
|
+
event: "mind.diary_backfill_skipped",
|
|
284
296
|
message: "embedding provider unavailable for backfill",
|
|
285
297
|
meta: { needsEmbedding: needsEmbedding.length },
|
|
286
298
|
});
|
|
@@ -306,7 +318,7 @@ async function backfillEmbeddings(options) {
|
|
|
306
318
|
(0, runtime_1.emitNervesEvent)({
|
|
307
319
|
level: "warn",
|
|
308
320
|
component: "mind",
|
|
309
|
-
event: "mind.
|
|
321
|
+
event: "mind.diary_backfill_batch_error",
|
|
310
322
|
message: "embedding backfill batch failed",
|
|
311
323
|
meta: {
|
|
312
324
|
batchStart: i,
|
|
@@ -319,9 +331,10 @@ async function backfillEmbeddings(options) {
|
|
|
319
331
|
// Rewrite facts file with updated embeddings
|
|
320
332
|
const lines = facts.map((f) => JSON.stringify(f)).join("\n") + "\n";
|
|
321
333
|
fs.writeFileSync(factsPath, lines, "utf8");
|
|
334
|
+
(0, sync_1.trackSyncWrite)(factsPath);
|
|
322
335
|
(0, runtime_1.emitNervesEvent)({
|
|
323
336
|
component: "mind",
|
|
324
|
-
event: "mind.
|
|
337
|
+
event: "mind.diary_backfill_complete",
|
|
325
338
|
message: "embedding backfill completed",
|
|
326
339
|
meta: { total: facts.length, backfilled, failed },
|
|
327
340
|
});
|
|
@@ -341,7 +354,7 @@ function uniqueFacts(facts) {
|
|
|
341
354
|
}
|
|
342
355
|
return unique;
|
|
343
356
|
}
|
|
344
|
-
async function
|
|
357
|
+
async function searchDiaryEntries(query, facts, embeddingProvider) {
|
|
345
358
|
const trimmed = query.trim();
|
|
346
359
|
if (!trimmed)
|
|
347
360
|
return [];
|
|
@@ -365,7 +378,7 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
365
378
|
.filter((fact) => fact.embedding.length === queryEmbedding.length)
|
|
366
379
|
.map((fact) => ({
|
|
367
380
|
fact,
|
|
368
|
-
score: cosineSimilarity(queryEmbedding, fact.embedding),
|
|
381
|
+
score: (0, associative_recall_1.cosineSimilarity)(queryEmbedding, fact.embedding),
|
|
369
382
|
}))
|
|
370
383
|
.filter((entry) => entry.score > 0)
|
|
371
384
|
.sort((left, right) => right.score - left.score)
|
|
@@ -377,8 +390,8 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
377
390
|
(0, runtime_1.emitNervesEvent)({
|
|
378
391
|
level: "warn",
|
|
379
392
|
component: "mind",
|
|
380
|
-
event: "mind.
|
|
381
|
-
message: "embedding provider unavailable for
|
|
393
|
+
event: "mind.diary_embedding_unavailable",
|
|
394
|
+
message: "embedding provider unavailable for diary search",
|
|
382
395
|
meta: {
|
|
383
396
|
reason: error instanceof Error ? error.message : String(error),
|
|
384
397
|
},
|
|
@@ -0,0 +1,119 @@
|
|
|
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.emitEpisode = emitEpisode;
|
|
37
|
+
exports.readRecentEpisodes = readRecentEpisodes;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const runtime_1 = require("../nerves/runtime");
|
|
41
|
+
const sync_1 = require("../heart/sync");
|
|
42
|
+
function episodesDir(agentRoot) {
|
|
43
|
+
return path.join(agentRoot, "arc", "episodes");
|
|
44
|
+
}
|
|
45
|
+
function generateId() {
|
|
46
|
+
const timestamp = Date.now();
|
|
47
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
48
|
+
return `ep-${timestamp}-${random}`;
|
|
49
|
+
}
|
|
50
|
+
function emitEpisode(agentRoot, input) {
|
|
51
|
+
const now = new Date().toISOString();
|
|
52
|
+
const id = generateId();
|
|
53
|
+
const episode = {
|
|
54
|
+
id,
|
|
55
|
+
timestamp: now,
|
|
56
|
+
kind: input.kind,
|
|
57
|
+
summary: input.summary,
|
|
58
|
+
whyItMattered: input.whyItMattered,
|
|
59
|
+
relatedEntities: input.relatedEntities,
|
|
60
|
+
salience: input.salience,
|
|
61
|
+
...(input.meta ? { meta: input.meta } : {}),
|
|
62
|
+
};
|
|
63
|
+
const dir = episodesDir(agentRoot);
|
|
64
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
65
|
+
const filePath = path.join(dir, `${id}.json`);
|
|
66
|
+
fs.writeFileSync(filePath, JSON.stringify(episode, null, 2), "utf-8");
|
|
67
|
+
(0, sync_1.trackSyncWrite)(filePath);
|
|
68
|
+
(0, runtime_1.emitNervesEvent)({
|
|
69
|
+
component: "mind",
|
|
70
|
+
event: "mind.episode_emitted",
|
|
71
|
+
message: `episode emitted: ${input.kind}`,
|
|
72
|
+
meta: {
|
|
73
|
+
episodeId: id,
|
|
74
|
+
kind: input.kind,
|
|
75
|
+
salience: input.salience,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return episode;
|
|
79
|
+
}
|
|
80
|
+
function readRecentEpisodes(agentRoot, options) {
|
|
81
|
+
const dir = episodesDir(agentRoot);
|
|
82
|
+
if (!fs.existsSync(dir)) {
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
component: "mind",
|
|
85
|
+
event: "mind.episodes_read",
|
|
86
|
+
message: "read episodes: directory missing, returning empty",
|
|
87
|
+
meta: { count: 0 },
|
|
88
|
+
});
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
const limit = options?.limit ?? 50;
|
|
92
|
+
const since = options?.since;
|
|
93
|
+
const kinds = options?.kinds;
|
|
94
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
95
|
+
const episodes = [];
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
try {
|
|
98
|
+
const content = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
99
|
+
const episode = JSON.parse(content);
|
|
100
|
+
if (since && episode.timestamp < since)
|
|
101
|
+
continue;
|
|
102
|
+
if (kinds && !kinds.includes(episode.kind))
|
|
103
|
+
continue;
|
|
104
|
+
episodes.push(episode);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Skip malformed JSON files gracefully
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
episodes.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
111
|
+
const result = episodes.slice(0, limit);
|
|
112
|
+
(0, runtime_1.emitNervesEvent)({
|
|
113
|
+
component: "mind",
|
|
114
|
+
event: "mind.episodes_read",
|
|
115
|
+
message: `read ${result.length} recent episodes`,
|
|
116
|
+
meta: { count: result.length, total: episodes.length, limit },
|
|
117
|
+
});
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fileStateCache = exports.FileStateCache = void 0;
|
|
4
|
+
exports.contentHash = contentHash;
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const runtime_1 = require("../nerves/runtime");
|
|
8
|
+
/** Compute sha256 hex hash of content */
|
|
9
|
+
function contentHash(content) {
|
|
10
|
+
return (0, crypto_1.createHash)("sha256").update(content).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Session-scoped LRU cache tracking file reads.
|
|
14
|
+
* Stores content hashes (not full content) to limit memory.
|
|
15
|
+
* Keyed by absolute file path.
|
|
16
|
+
*
|
|
17
|
+
* Also maintains a separate snapshot list for future rewind support.
|
|
18
|
+
* Snapshots are indexed by content hash and linked to conversation messages.
|
|
19
|
+
*/
|
|
20
|
+
class FileStateCache {
|
|
21
|
+
entries;
|
|
22
|
+
maxSize;
|
|
23
|
+
snapshots = [];
|
|
24
|
+
maxSnapshots = 100;
|
|
25
|
+
constructor(maxSize = 50) {
|
|
26
|
+
this.entries = new Map();
|
|
27
|
+
this.maxSize = maxSize;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Record a file read. Computes content hash and stores metadata.
|
|
31
|
+
*/
|
|
32
|
+
record(filePath, content, mtime, offset, limit, messageId) {
|
|
33
|
+
// If key already exists, delete it so re-insertion moves it to end (most recent)
|
|
34
|
+
if (this.entries.has(filePath)) {
|
|
35
|
+
this.entries.delete(filePath);
|
|
36
|
+
}
|
|
37
|
+
const hash = contentHash(content);
|
|
38
|
+
const fullRead = offset === undefined && limit === undefined;
|
|
39
|
+
this.entries.set(filePath, {
|
|
40
|
+
hash,
|
|
41
|
+
mtime,
|
|
42
|
+
offset: fullRead ? undefined : offset,
|
|
43
|
+
limit: fullRead ? undefined : limit,
|
|
44
|
+
fullRead,
|
|
45
|
+
recordedAt: Date.now(),
|
|
46
|
+
messageId,
|
|
47
|
+
});
|
|
48
|
+
(0, runtime_1.emitNervesEvent)({
|
|
49
|
+
component: "mind",
|
|
50
|
+
event: "mind.file_state.record",
|
|
51
|
+
message: "recorded file state",
|
|
52
|
+
meta: { path: filePath, hash, fullRead },
|
|
53
|
+
});
|
|
54
|
+
// Evict LRU (first entry in Map iteration order) if over capacity
|
|
55
|
+
if (this.entries.size > this.maxSize) {
|
|
56
|
+
const firstKey = this.entries.keys().next().value;
|
|
57
|
+
this.entries.delete(firstKey);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the cached state for a file path. Also promotes it in LRU order.
|
|
62
|
+
*/
|
|
63
|
+
get(filePath) {
|
|
64
|
+
const entry = this.entries.get(filePath);
|
|
65
|
+
if (entry === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
// Promote to most-recently-used by re-inserting
|
|
68
|
+
this.entries.delete(filePath);
|
|
69
|
+
this.entries.set(filePath, entry);
|
|
70
|
+
(0, runtime_1.emitNervesEvent)({
|
|
71
|
+
component: "mind",
|
|
72
|
+
event: "mind.file_state.get",
|
|
73
|
+
message: "retrieved file state",
|
|
74
|
+
meta: { path: filePath, hash: entry.hash },
|
|
75
|
+
});
|
|
76
|
+
return entry;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a file has been modified since the last recorded read.
|
|
80
|
+
* Uses mtime as primary signal, content hash as fallback for cloud sync / touch scenarios.
|
|
81
|
+
* Returns { stale: false } if the path is not in cache or the file cannot be stat'd.
|
|
82
|
+
*/
|
|
83
|
+
isStale(filePath) {
|
|
84
|
+
const entry = this.entries.get(filePath);
|
|
85
|
+
if (entry === undefined)
|
|
86
|
+
return { stale: false };
|
|
87
|
+
let currentMtime;
|
|
88
|
+
try {
|
|
89
|
+
currentMtime = (0, fs_1.statSync)(filePath).mtimeMs;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// File doesn't exist or can't be stat'd -- no basis for staleness
|
|
93
|
+
return { stale: false };
|
|
94
|
+
}
|
|
95
|
+
// Fast path: mtime unchanged means not stale
|
|
96
|
+
if (currentMtime === entry.mtime)
|
|
97
|
+
return { stale: false };
|
|
98
|
+
// mtime differs -- check content hash as fallback (handles touch / cloud sync)
|
|
99
|
+
try {
|
|
100
|
+
const currentContent = (0, fs_1.readFileSync)(filePath, "utf-8");
|
|
101
|
+
const currentHash = contentHash(currentContent);
|
|
102
|
+
if (currentHash === entry.hash)
|
|
103
|
+
return { stale: false };
|
|
104
|
+
(0, runtime_1.emitNervesEvent)({
|
|
105
|
+
component: "mind",
|
|
106
|
+
event: "mind.file_state.stale_detected",
|
|
107
|
+
message: "file staleness detected",
|
|
108
|
+
meta: { path: filePath, previousHash: entry.hash, currentHash },
|
|
109
|
+
});
|
|
110
|
+
return { stale: true, reason: `file modified since last read (mtime and content differ)` };
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Can't read file -- treat as not stale (file may have been deleted)
|
|
114
|
+
return { stale: false };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Create a pre-edit snapshot of the current cache state for a file.
|
|
119
|
+
* Snapshots are stored separately from the LRU cache for future rewind support.
|
|
120
|
+
* Returns undefined if the path is not in cache.
|
|
121
|
+
*/
|
|
122
|
+
snapshot(filePath) {
|
|
123
|
+
const entry = this.entries.get(filePath);
|
|
124
|
+
if (entry === undefined)
|
|
125
|
+
return undefined;
|
|
126
|
+
const snap = {
|
|
127
|
+
filePath,
|
|
128
|
+
hash: entry.hash,
|
|
129
|
+
mtime: entry.mtime,
|
|
130
|
+
messageId: entry.messageId,
|
|
131
|
+
createdAt: Date.now(),
|
|
132
|
+
};
|
|
133
|
+
this.snapshots.push(snap);
|
|
134
|
+
(0, runtime_1.emitNervesEvent)({
|
|
135
|
+
component: "mind",
|
|
136
|
+
event: "mind.file_state.snapshot_created",
|
|
137
|
+
message: "created file state snapshot",
|
|
138
|
+
meta: { path: filePath, hash: snap.hash },
|
|
139
|
+
});
|
|
140
|
+
// Evict oldest snapshots if over capacity
|
|
141
|
+
if (this.snapshots.length > this.maxSnapshots) {
|
|
142
|
+
this.snapshots = this.snapshots.slice(this.snapshots.length - this.maxSnapshots);
|
|
143
|
+
}
|
|
144
|
+
return snap;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get all snapshots in creation order.
|
|
148
|
+
*/
|
|
149
|
+
getSnapshots() {
|
|
150
|
+
return this.snapshots;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Look up a snapshot by content hash. Returns the first match.
|
|
154
|
+
*/
|
|
155
|
+
lookupSnapshotByHash(hash) {
|
|
156
|
+
return this.snapshots.find(s => s.hash === hash);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Clear all snapshots.
|
|
160
|
+
*/
|
|
161
|
+
clearSnapshots() {
|
|
162
|
+
this.snapshots = [];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Clear all cached entries (does not clear snapshots).
|
|
166
|
+
*/
|
|
167
|
+
clear() {
|
|
168
|
+
(0, runtime_1.emitNervesEvent)({
|
|
169
|
+
component: "mind",
|
|
170
|
+
event: "mind.file_state.clear",
|
|
171
|
+
message: "cleared file state cache",
|
|
172
|
+
meta: { entryCount: this.entries.size },
|
|
173
|
+
});
|
|
174
|
+
this.entries.clear();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.FileStateCache = FileStateCache;
|
|
178
|
+
/** Session-scoped singleton instance used by tool handlers */
|
|
179
|
+
exports.fileStateCache = new FileStateCache();
|
|
@@ -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
|
}
|