@ouro.bot/cli 0.1.0-alpha.22 → 0.1.0-alpha.220
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/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
- package/changelog.json +1354 -2
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +140 -0
- package/dist/arc/episodes.js +119 -0
- package/dist/arc/intentions.js +133 -0
- package/dist/arc/json-store.js +120 -0
- package/dist/arc/obligations.js +237 -0
- package/dist/arc/presence.js +185 -0
- package/dist/heart/active-work.js +832 -0
- package/dist/heart/agent-entry.js +37 -2
- package/dist/heart/auth/auth-flow.js +444 -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 +111 -0
- package/dist/heart/config-registry.js +305 -0
- package/dist/heart/config.js +59 -30
- package/dist/heart/core.js +601 -145
- package/dist/heart/cross-chat-delivery.js +131 -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/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +557 -0
- package/dist/heart/daemon/cli-exec.js +1847 -0
- package/dist/heart/daemon/cli-parse.js +759 -0
- package/dist/heart/daemon/cli-render.js +316 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/daemon-cli.js +29 -1163
- package/dist/heart/daemon/daemon-entry.js +202 -4
- package/dist/heart/daemon/daemon-health.js +136 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +153 -12
- package/dist/heart/daemon/daemon-tombstone.js +123 -0
- package/dist/heart/daemon/daemon.js +487 -20
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/launchd.js +39 -8
- package/dist/heart/daemon/log-tailer.js +4 -3
- 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-entry.js +1 -1
- package/dist/heart/daemon/process-manager.js +123 -0
- package/dist/heart/daemon/run-hooks.js +2 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +101 -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 +72 -3
- 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/task-scheduler.js +3 -25
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/habits/habit-migration.js +181 -0
- package/dist/heart/habits/habit-parser.js +119 -0
- package/dist/heart/habits/habit-scheduler.js +369 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +29 -119
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
- package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
- package/dist/heart/identity.js +144 -55
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/mcp/mcp-server.js +608 -0
- package/dist/heart/migrate-config.js +127 -0
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/outlook/outlook-http.js +439 -0
- package/dist/heart/outlook/outlook-read.js +1618 -0
- package/dist/heart/outlook/outlook-render.js +1032 -0
- package/dist/heart/outlook/outlook-types.js +7 -0
- package/dist/heart/outlook/outlook-view.js +194 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +162 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +187 -41
- 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/session-activity.js +172 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/start-of-turn-packet.js +330 -0
- package/dist/heart/streaming.js +36 -27
- 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-context.js +358 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
- package/dist/heart/versioning/ouro-version-manager.js +171 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +30 -6
- package/dist/heart/{daemon → versioning}/update-checker.js +9 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +20 -2
- package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +41 -3
- package/dist/mind/associative-recall.js +126 -66
- package/dist/mind/bundle-manifest.js +8 -1
- package/dist/mind/context.js +125 -14
- package/dist/mind/{memory.js → diary.js} +73 -96
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +56 -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 +8 -0
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +779 -109
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/coverage/audit.js +1 -1
- package/dist/nerves/coverage/file-completeness.js +47 -5
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +64 -4
- 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 +15 -56
- package/dist/repertoire/ado-semantic.js +11 -5
- package/dist/repertoire/api-client.js +100 -0
- package/dist/repertoire/bitwarden-client.js +290 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +330 -0
- package/dist/repertoire/coding/feedback.js +197 -30
- package/dist/repertoire/coding/manager.js +149 -2
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +170 -7
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +342 -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 +35 -771
- package/dist/repertoire/tools-bluebubbles.js +94 -0
- package/dist/repertoire/tools-bridge.js +141 -0
- package/dist/repertoire/tools-config.js +266 -0
- package/dist/repertoire/tools-continuity.js +248 -0
- package/dist/repertoire/tools-files.js +342 -0
- package/dist/repertoire/tools-flow.js +62 -0
- package/dist/repertoire/tools-github.js +1 -6
- package/dist/repertoire/tools-memory.js +347 -0
- package/dist/repertoire/tools-session.js +721 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-surface.js +242 -0
- package/dist/repertoire/tools-teams.js +12 -59
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-vault.js +185 -0
- package/dist/repertoire/tools.js +132 -124
- package/dist/repertoire/travel-api-client.js +224 -0
- 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 → bluebubbles/client.js} +109 -10
- package/dist/senses/bluebubbles/entry.js +13 -0
- package/dist/senses/bluebubbles/inbound-log.js +109 -0
- package/dist/senses/bluebubbles/index.js +1327 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +119 -24
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +13 -5
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
- package/dist/senses/bluebubbles/runtime-state.js +109 -0
- package/dist/senses/bluebubbles/session-cleanup.js +72 -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 +358 -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 +503 -244
- 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 +591 -84
- package/dist/senses/pipeline.js +559 -0
- package/dist/senses/shared-turn.js +196 -0
- package/dist/senses/surface-tool.js +68 -0
- package/dist/senses/teams.js +667 -120
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +12 -6
- package/skills/browser-navigation.md +110 -0
- package/skills/configure-dev-tools.md +81 -0
- package/skills/travel-planning.md +143 -0
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -421
- package/dist/senses/debug-activity.js +0 -107
- package/subagents/README.md +0 -73
- package/subagents/work-doer.md +0 -235
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -382
- /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/monty.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
- /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
- /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
- /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
|
@@ -33,52 +33,25 @@ 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
|
-
const config_1 = require("../heart/config");
|
|
47
46
|
const identity_1 = require("../heart/identity");
|
|
48
47
|
const runtime_1 = require("../nerves/runtime");
|
|
48
|
+
const sync_1 = require("../heart/sync");
|
|
49
|
+
const associative_recall_1 = require("./associative-recall");
|
|
50
|
+
const embedding_provider_1 = require("./embedding-provider");
|
|
49
51
|
const DEDUP_THRESHOLD = 0.6;
|
|
52
|
+
const SEMANTIC_DEDUP_THRESHOLD = 0.95;
|
|
50
53
|
const ENTITY_TOKEN = /[a-z0-9]+/g;
|
|
51
|
-
|
|
52
|
-
class OpenAIEmbeddingProvider {
|
|
53
|
-
apiKey;
|
|
54
|
-
model;
|
|
55
|
-
constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
|
|
56
|
-
this.apiKey = apiKey;
|
|
57
|
-
this.model = model;
|
|
58
|
-
}
|
|
59
|
-
async embed(texts) {
|
|
60
|
-
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
61
|
-
method: "POST",
|
|
62
|
-
headers: {
|
|
63
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
64
|
-
"Content-Type": "application/json",
|
|
65
|
-
},
|
|
66
|
-
body: JSON.stringify({
|
|
67
|
-
model: this.model,
|
|
68
|
-
input: texts,
|
|
69
|
-
}),
|
|
70
|
-
});
|
|
71
|
-
if (!response.ok) {
|
|
72
|
-
throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
|
|
73
|
-
}
|
|
74
|
-
const payload = (await response.json());
|
|
75
|
-
if (!payload.data || payload.data.length !== texts.length) {
|
|
76
|
-
throw new Error("embedding response missing expected vectors");
|
|
77
|
-
}
|
|
78
|
-
return payload.data.map((entry) => entry.embedding);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
function ensureMemoryStorePaths(rootDir) {
|
|
54
|
+
function ensureDiaryStorePaths(rootDir) {
|
|
82
55
|
const factsPath = path.join(rootDir, "facts.jsonl");
|
|
83
56
|
const entitiesPath = path.join(rootDir, "entities.json");
|
|
84
57
|
const dailyDir = path.join(rootDir, "daily");
|
|
@@ -90,8 +63,8 @@ function ensureMemoryStorePaths(rootDir) {
|
|
|
90
63
|
fs.writeFileSync(entitiesPath, "{}\n", "utf8");
|
|
91
64
|
(0, runtime_1.emitNervesEvent)({
|
|
92
65
|
component: "mind",
|
|
93
|
-
event: "mind.
|
|
94
|
-
message: "
|
|
66
|
+
event: "mind.diary_paths_ready",
|
|
67
|
+
message: "diary store paths ready",
|
|
95
68
|
meta: { rootDir },
|
|
96
69
|
});
|
|
97
70
|
return { rootDir, factsPath, entitiesPath, dailyDir };
|
|
@@ -114,15 +87,22 @@ function overlapScore(left, right) {
|
|
|
114
87
|
}
|
|
115
88
|
return common / Math.min(leftWords.size, rightWords.size);
|
|
116
89
|
}
|
|
117
|
-
function
|
|
90
|
+
function readExistingEntries(factsPath) {
|
|
118
91
|
if (!fs.existsSync(factsPath))
|
|
119
92
|
return [];
|
|
120
93
|
const raw = fs.readFileSync(factsPath, "utf8").trim();
|
|
121
94
|
if (!raw)
|
|
122
95
|
return [];
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
96
|
+
const facts = [];
|
|
97
|
+
for (const line of raw.split("\n")) {
|
|
98
|
+
try {
|
|
99
|
+
facts.push(JSON.parse(line));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Skip corrupt lines (e.g. partial write from a crash).
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return facts;
|
|
126
106
|
}
|
|
127
107
|
function readEntityIndex(entitiesPath) {
|
|
128
108
|
if (!fs.existsSync(entitiesPath))
|
|
@@ -170,13 +150,24 @@ function appendDailyFact(dailyDir, fact) {
|
|
|
170
150
|
const dayPath = path.join(dailyDir, `${day}.jsonl`);
|
|
171
151
|
fs.appendFileSync(dayPath, `${JSON.stringify(fact)}\n`, "utf8");
|
|
172
152
|
}
|
|
173
|
-
function
|
|
174
|
-
const existing =
|
|
153
|
+
function appendEntriesWithDedup(stores, incoming, options) {
|
|
154
|
+
const existing = readExistingEntries(stores.factsPath);
|
|
175
155
|
const all = [...existing];
|
|
176
156
|
let added = 0;
|
|
177
157
|
let skipped = 0;
|
|
158
|
+
const semanticThreshold = options?.semanticThreshold;
|
|
178
159
|
for (const fact of incoming) {
|
|
179
|
-
const duplicate = all.some((prior) =>
|
|
160
|
+
const duplicate = all.some((prior) => {
|
|
161
|
+
if (overlapScore(prior.text, fact.text) > DEDUP_THRESHOLD)
|
|
162
|
+
return true;
|
|
163
|
+
if (semanticThreshold !== undefined &&
|
|
164
|
+
Array.isArray(fact.embedding) && fact.embedding.length > 0 &&
|
|
165
|
+
Array.isArray(prior.embedding) && prior.embedding.length > 0 &&
|
|
166
|
+
fact.embedding.length === prior.embedding.length) {
|
|
167
|
+
return (0, associative_recall_1.cosineSimilarity)(fact.embedding, prior.embedding) > semanticThreshold;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
});
|
|
180
171
|
if (duplicate) {
|
|
181
172
|
skipped++;
|
|
182
173
|
continue;
|
|
@@ -184,49 +175,28 @@ function appendFactsWithDedup(stores, incoming) {
|
|
|
184
175
|
all.push(fact);
|
|
185
176
|
added++;
|
|
186
177
|
fs.appendFileSync(stores.factsPath, `${JSON.stringify(fact)}\n`, "utf8");
|
|
178
|
+
(0, sync_1.trackSyncWrite)(stores.factsPath);
|
|
187
179
|
updateEntityIndex(stores.entitiesPath, fact);
|
|
180
|
+
(0, sync_1.trackSyncWrite)(stores.entitiesPath);
|
|
188
181
|
appendDailyFact(stores.dailyDir, fact);
|
|
182
|
+
(0, sync_1.trackSyncWrite)(path.join(stores.dailyDir, `${(fact.createdAt.slice(0, 10) || "unknown")}.jsonl`));
|
|
189
183
|
}
|
|
190
184
|
(0, runtime_1.emitNervesEvent)({
|
|
191
185
|
component: "mind",
|
|
192
|
-
event: "mind.
|
|
193
|
-
message: "
|
|
186
|
+
event: "mind.diary_write",
|
|
187
|
+
message: "diary write completed",
|
|
194
188
|
meta: { added, skipped },
|
|
195
189
|
});
|
|
196
190
|
return { added, skipped };
|
|
197
191
|
}
|
|
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
|
-
function createDefaultEmbeddingProvider() {
|
|
217
|
-
const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
|
|
218
|
-
if (!apiKey)
|
|
219
|
-
return null;
|
|
220
|
-
return new OpenAIEmbeddingProvider(apiKey);
|
|
221
|
-
}
|
|
222
192
|
async function buildEmbedding(text, embeddingProvider) {
|
|
223
|
-
const provider = embeddingProvider ?? createDefaultEmbeddingProvider();
|
|
193
|
+
const provider = embeddingProvider ?? (0, embedding_provider_1.createDefaultEmbeddingProvider)();
|
|
224
194
|
if (!provider) {
|
|
225
195
|
(0, runtime_1.emitNervesEvent)({
|
|
226
196
|
level: "warn",
|
|
227
197
|
component: "mind",
|
|
228
|
-
event: "mind.
|
|
229
|
-
message: "embedding provider unavailable for
|
|
198
|
+
event: "mind.diary_embedding_unavailable",
|
|
199
|
+
message: "embedding provider unavailable for diary write",
|
|
230
200
|
meta: { reason: "missing_openai_embeddings_key" },
|
|
231
201
|
});
|
|
232
202
|
return [];
|
|
@@ -239,8 +209,8 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
239
209
|
(0, runtime_1.emitNervesEvent)({
|
|
240
210
|
level: "warn",
|
|
241
211
|
component: "mind",
|
|
242
|
-
event: "mind.
|
|
243
|
-
message: "embedding provider unavailable for
|
|
212
|
+
event: "mind.diary_embedding_unavailable",
|
|
213
|
+
message: "embedding provider unavailable for diary write",
|
|
244
214
|
meta: {
|
|
245
215
|
reason: error instanceof Error ? error.message : String(error),
|
|
246
216
|
},
|
|
@@ -248,13 +218,19 @@ async function buildEmbedding(text, embeddingProvider) {
|
|
|
248
218
|
return [];
|
|
249
219
|
}
|
|
250
220
|
}
|
|
251
|
-
function
|
|
252
|
-
|
|
221
|
+
function resolveDiaryRoot(explicitRoot) {
|
|
222
|
+
if (explicitRoot)
|
|
223
|
+
return explicitRoot;
|
|
224
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
225
|
+
return path.join(agentRoot, "diary");
|
|
226
|
+
}
|
|
227
|
+
function readDiaryEntries(diaryRoot) {
|
|
228
|
+
return readExistingEntries(path.join(resolveDiaryRoot(diaryRoot), "facts.jsonl"));
|
|
253
229
|
}
|
|
254
|
-
async function
|
|
230
|
+
async function saveDiaryEntry(options) {
|
|
255
231
|
const text = options.text.trim();
|
|
256
|
-
const
|
|
257
|
-
const stores =
|
|
232
|
+
const diaryRoot = resolveDiaryRoot(options.diaryRoot);
|
|
233
|
+
const stores = ensureDiaryStorePaths(diaryRoot);
|
|
258
234
|
const embedding = await buildEmbedding(text, options.embeddingProvider);
|
|
259
235
|
const fact = {
|
|
260
236
|
id: options.idFactory ? options.idFactory() : (0, crypto_1.randomUUID)(),
|
|
@@ -264,23 +240,23 @@ async function saveMemoryFact(options) {
|
|
|
264
240
|
createdAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
265
241
|
embedding,
|
|
266
242
|
};
|
|
267
|
-
return
|
|
243
|
+
return appendEntriesWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
|
|
268
244
|
}
|
|
269
245
|
async function backfillEmbeddings(options) {
|
|
270
|
-
const
|
|
271
|
-
const factsPath = path.join(
|
|
246
|
+
const diaryRoot = resolveDiaryRoot(options?.diaryRoot);
|
|
247
|
+
const factsPath = path.join(diaryRoot, "facts.jsonl");
|
|
272
248
|
if (!fs.existsSync(factsPath))
|
|
273
249
|
return { total: 0, backfilled: 0, failed: 0 };
|
|
274
|
-
const facts =
|
|
250
|
+
const facts = readExistingEntries(factsPath);
|
|
275
251
|
const needsEmbedding = facts.filter((f) => !Array.isArray(f.embedding) || f.embedding.length === 0);
|
|
276
252
|
if (needsEmbedding.length === 0)
|
|
277
253
|
return { total: facts.length, backfilled: 0, failed: 0 };
|
|
278
|
-
const provider = options?.embeddingProvider ?? createDefaultEmbeddingProvider();
|
|
254
|
+
const provider = options?.embeddingProvider ?? (0, embedding_provider_1.createDefaultEmbeddingProvider)();
|
|
279
255
|
if (!provider) {
|
|
280
256
|
(0, runtime_1.emitNervesEvent)({
|
|
281
257
|
level: "warn",
|
|
282
258
|
component: "mind",
|
|
283
|
-
event: "mind.
|
|
259
|
+
event: "mind.diary_backfill_skipped",
|
|
284
260
|
message: "embedding provider unavailable for backfill",
|
|
285
261
|
meta: { needsEmbedding: needsEmbedding.length },
|
|
286
262
|
});
|
|
@@ -306,7 +282,7 @@ async function backfillEmbeddings(options) {
|
|
|
306
282
|
(0, runtime_1.emitNervesEvent)({
|
|
307
283
|
level: "warn",
|
|
308
284
|
component: "mind",
|
|
309
|
-
event: "mind.
|
|
285
|
+
event: "mind.diary_backfill_batch_error",
|
|
310
286
|
message: "embedding backfill batch failed",
|
|
311
287
|
meta: {
|
|
312
288
|
batchStart: i,
|
|
@@ -319,9 +295,10 @@ async function backfillEmbeddings(options) {
|
|
|
319
295
|
// Rewrite facts file with updated embeddings
|
|
320
296
|
const lines = facts.map((f) => JSON.stringify(f)).join("\n") + "\n";
|
|
321
297
|
fs.writeFileSync(factsPath, lines, "utf8");
|
|
298
|
+
(0, sync_1.trackSyncWrite)(factsPath);
|
|
322
299
|
(0, runtime_1.emitNervesEvent)({
|
|
323
300
|
component: "mind",
|
|
324
|
-
event: "mind.
|
|
301
|
+
event: "mind.diary_backfill_complete",
|
|
325
302
|
message: "embedding backfill completed",
|
|
326
303
|
meta: { total: facts.length, backfilled, failed },
|
|
327
304
|
});
|
|
@@ -341,7 +318,7 @@ function uniqueFacts(facts) {
|
|
|
341
318
|
}
|
|
342
319
|
return unique;
|
|
343
320
|
}
|
|
344
|
-
async function
|
|
321
|
+
async function searchDiaryEntries(query, facts, embeddingProvider) {
|
|
345
322
|
const trimmed = query.trim();
|
|
346
323
|
if (!trimmed)
|
|
347
324
|
return [];
|
|
@@ -351,7 +328,7 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
351
328
|
if (embeddedFacts.length === 0) {
|
|
352
329
|
return substringFallback();
|
|
353
330
|
}
|
|
354
|
-
const provider = embeddingProvider ?? createDefaultEmbeddingProvider();
|
|
331
|
+
const provider = embeddingProvider ?? (0, embedding_provider_1.createDefaultEmbeddingProvider)();
|
|
355
332
|
if (!provider) {
|
|
356
333
|
return substringFallback();
|
|
357
334
|
}
|
|
@@ -365,7 +342,7 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
365
342
|
.filter((fact) => fact.embedding.length === queryEmbedding.length)
|
|
366
343
|
.map((fact) => ({
|
|
367
344
|
fact,
|
|
368
|
-
score: cosineSimilarity(queryEmbedding, fact.embedding),
|
|
345
|
+
score: (0, associative_recall_1.cosineSimilarity)(queryEmbedding, fact.embedding),
|
|
369
346
|
}))
|
|
370
347
|
.filter((entry) => entry.score > 0)
|
|
371
348
|
.sort((left, right) => right.score - left.score)
|
|
@@ -377,8 +354,8 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
377
354
|
(0, runtime_1.emitNervesEvent)({
|
|
378
355
|
level: "warn",
|
|
379
356
|
component: "mind",
|
|
380
|
-
event: "mind.
|
|
381
|
-
message: "embedding provider unavailable for
|
|
357
|
+
event: "mind.diary_embedding_unavailable",
|
|
358
|
+
message: "embedding provider unavailable for diary search",
|
|
382
359
|
meta: {
|
|
383
360
|
reason: error instanceof Error ? error.message : String(error),
|
|
384
361
|
},
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared OpenAI embedding provider.
|
|
4
|
+
*
|
|
5
|
+
* Both diary.ts and associative-recall.ts need to call the OpenAI embeddings
|
|
6
|
+
* API. This module provides the shared implementation so neither duplicates
|
|
7
|
+
* the fetch logic.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.OpenAIEmbeddingProvider = void 0;
|
|
11
|
+
exports.createDefaultEmbeddingProvider = createDefaultEmbeddingProvider;
|
|
12
|
+
const config_1 = require("../heart/config");
|
|
13
|
+
const runtime_1 = require("../nerves/runtime");
|
|
14
|
+
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
15
|
+
class OpenAIEmbeddingProvider {
|
|
16
|
+
apiKey;
|
|
17
|
+
model;
|
|
18
|
+
constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
|
|
19
|
+
this.apiKey = apiKey;
|
|
20
|
+
this.model = model;
|
|
21
|
+
}
|
|
22
|
+
async embed(texts) {
|
|
23
|
+
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
model: this.model,
|
|
31
|
+
input: texts,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
const payload = (await response.json());
|
|
38
|
+
if (!payload.data || payload.data.length !== texts.length) {
|
|
39
|
+
throw new Error("embedding response missing expected vectors");
|
|
40
|
+
}
|
|
41
|
+
return payload.data.map((entry) => entry.embedding);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.OpenAIEmbeddingProvider = OpenAIEmbeddingProvider;
|
|
45
|
+
/**
|
|
46
|
+
* Create a default embedding provider from the configured API key.
|
|
47
|
+
* Returns null if no key is configured.
|
|
48
|
+
*/
|
|
49
|
+
function createDefaultEmbeddingProvider() {
|
|
50
|
+
const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
|
|
51
|
+
if (!apiKey)
|
|
52
|
+
return null;
|
|
53
|
+
(0, runtime_1.emitNervesEvent)({
|
|
54
|
+
component: "mind",
|
|
55
|
+
event: "mind.embedding_provider_created",
|
|
56
|
+
message: "default embedding provider created",
|
|
57
|
+
meta: { model: DEFAULT_EMBEDDING_MODEL },
|
|
58
|
+
});
|
|
59
|
+
return new OpenAIEmbeddingProvider(apiKey);
|
|
60
|
+
}
|
|
@@ -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
|
}
|
|
@@ -2,11 +2,26 @@
|
|
|
2
2
|
// Channel capabilities -- hardcoded const map keyed by channel identifier.
|
|
3
3
|
// Pure lookup, no I/O, cannot fail. Unknown channel gets minimal defaults.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.channelToFacing = channelToFacing;
|
|
5
6
|
exports.getChannelCapabilities = getChannelCapabilities;
|
|
7
|
+
exports.isRemoteChannel = isRemoteChannel;
|
|
8
|
+
exports.getAlwaysOnSenseNames = getAlwaysOnSenseNames;
|
|
6
9
|
const runtime_1 = require("../../nerves/runtime");
|
|
10
|
+
const AGENT_FACING_CHANNELS = new Set(["inner", "mcp"]);
|
|
11
|
+
function channelToFacing(channel) {
|
|
12
|
+
const facing = channel && AGENT_FACING_CHANNELS.has(channel) ? "agent" : "human";
|
|
13
|
+
(0, runtime_1.emitNervesEvent)({
|
|
14
|
+
component: "channels",
|
|
15
|
+
event: "channel.facing_lookup",
|
|
16
|
+
message: "channel facing lookup",
|
|
17
|
+
meta: { channel: channel ?? "undefined", facing },
|
|
18
|
+
});
|
|
19
|
+
return facing;
|
|
20
|
+
}
|
|
7
21
|
const CHANNEL_CAPABILITIES = {
|
|
8
22
|
cli: {
|
|
9
23
|
channel: "cli",
|
|
24
|
+
senseType: "local",
|
|
10
25
|
availableIntegrations: [],
|
|
11
26
|
supportsMarkdown: false,
|
|
12
27
|
supportsStreaming: true,
|
|
@@ -15,6 +30,7 @@ const CHANNEL_CAPABILITIES = {
|
|
|
15
30
|
},
|
|
16
31
|
teams: {
|
|
17
32
|
channel: "teams",
|
|
33
|
+
senseType: "closed",
|
|
18
34
|
availableIntegrations: ["ado", "graph", "github"],
|
|
19
35
|
supportsMarkdown: true,
|
|
20
36
|
supportsStreaming: true,
|
|
@@ -23,15 +39,35 @@ const CHANNEL_CAPABILITIES = {
|
|
|
23
39
|
},
|
|
24
40
|
bluebubbles: {
|
|
25
41
|
channel: "bluebubbles",
|
|
42
|
+
senseType: "open",
|
|
26
43
|
availableIntegrations: [],
|
|
27
44
|
supportsMarkdown: false,
|
|
28
45
|
supportsStreaming: false,
|
|
29
46
|
supportsRichCards: false,
|
|
30
47
|
maxMessageLength: Infinity,
|
|
31
48
|
},
|
|
49
|
+
inner: {
|
|
50
|
+
channel: "inner",
|
|
51
|
+
senseType: "internal",
|
|
52
|
+
availableIntegrations: [],
|
|
53
|
+
supportsMarkdown: false,
|
|
54
|
+
supportsStreaming: true,
|
|
55
|
+
supportsRichCards: false,
|
|
56
|
+
maxMessageLength: Infinity,
|
|
57
|
+
},
|
|
58
|
+
mcp: {
|
|
59
|
+
channel: "mcp",
|
|
60
|
+
senseType: "local",
|
|
61
|
+
availableIntegrations: [],
|
|
62
|
+
supportsMarkdown: true,
|
|
63
|
+
supportsStreaming: false,
|
|
64
|
+
supportsRichCards: false,
|
|
65
|
+
maxMessageLength: Infinity,
|
|
66
|
+
},
|
|
32
67
|
};
|
|
33
68
|
const DEFAULT_CAPABILITIES = {
|
|
34
69
|
channel: "cli",
|
|
70
|
+
senseType: "local",
|
|
35
71
|
availableIntegrations: [],
|
|
36
72
|
supportsMarkdown: false,
|
|
37
73
|
supportsStreaming: false,
|
|
@@ -47,3 +83,23 @@ function getChannelCapabilities(channel) {
|
|
|
47
83
|
});
|
|
48
84
|
return CHANNEL_CAPABILITIES[channel] ?? DEFAULT_CAPABILITIES;
|
|
49
85
|
}
|
|
86
|
+
/** Whether the channel is remote (open or closed) vs local/internal. */
|
|
87
|
+
function isRemoteChannel(capabilities) {
|
|
88
|
+
const senseType = capabilities?.senseType;
|
|
89
|
+
return senseType !== undefined && senseType !== "local" && senseType !== "internal";
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Returns channel names whose senseType is "open" or "closed" -- i.e. channels
|
|
93
|
+
* that are always-on (daemon-managed) rather than interactive or internal.
|
|
94
|
+
*/
|
|
95
|
+
function getAlwaysOnSenseNames() {
|
|
96
|
+
(0, runtime_1.emitNervesEvent)({
|
|
97
|
+
component: "channels",
|
|
98
|
+
event: "channel.always_on_lookup",
|
|
99
|
+
message: "always-on sense names lookup",
|
|
100
|
+
meta: {},
|
|
101
|
+
});
|
|
102
|
+
return Object.entries(CHANNEL_CAPABILITIES)
|
|
103
|
+
.filter(([, cap]) => cap.senseType === "open" || cap.senseType === "closed")
|
|
104
|
+
.map(([channel]) => channel);
|
|
105
|
+
}
|