@ouro.bot/cli 0.0.1-alpha.0 → 0.1.0-alpha.1

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.
Files changed (114) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +20 -0
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +22 -0
  3. package/AdoptionSpecialist.ouro/psyche/identities/basilisk.md +31 -0
  4. package/AdoptionSpecialist.ouro/psyche/identities/jafar.md +31 -0
  5. package/AdoptionSpecialist.ouro/psyche/identities/jormungandr.md +31 -0
  6. package/AdoptionSpecialist.ouro/psyche/identities/kaa.md +31 -0
  7. package/AdoptionSpecialist.ouro/psyche/identities/medusa.md +31 -0
  8. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +31 -0
  9. package/AdoptionSpecialist.ouro/psyche/identities/nagini.md +31 -0
  10. package/AdoptionSpecialist.ouro/psyche/identities/ouroboros.md +31 -0
  11. package/AdoptionSpecialist.ouro/psyche/identities/python.md +31 -0
  12. package/AdoptionSpecialist.ouro/psyche/identities/quetzalcoatl.md +31 -0
  13. package/AdoptionSpecialist.ouro/psyche/identities/sir-hiss.md +31 -0
  14. package/AdoptionSpecialist.ouro/psyche/identities/the-serpent.md +31 -0
  15. package/AdoptionSpecialist.ouro/psyche/identities/the-snake.md +31 -0
  16. package/README.md +224 -6
  17. package/dist/heart/agent-entry.js +17 -0
  18. package/dist/heart/api-error.js +34 -0
  19. package/dist/heart/config.js +296 -0
  20. package/dist/heart/core.js +485 -0
  21. package/dist/heart/daemon/daemon-cli.js +626 -0
  22. package/dist/heart/daemon/daemon-entry.js +74 -0
  23. package/dist/heart/daemon/daemon.js +310 -0
  24. package/dist/heart/daemon/hatch-flow.js +284 -0
  25. package/dist/heart/daemon/hatch-specialist.js +107 -0
  26. package/dist/heart/daemon/health-monitor.js +79 -0
  27. package/dist/heart/daemon/message-router.js +98 -0
  28. package/dist/heart/daemon/ouro-bot-entry.js +23 -0
  29. package/dist/heart/daemon/ouro-bot-wrapper.js +90 -0
  30. package/dist/heart/daemon/ouro-entry.js +23 -0
  31. package/dist/heart/daemon/ouro-uti.js +212 -0
  32. package/dist/heart/daemon/process-manager.js +220 -0
  33. package/dist/heart/daemon/runtime-logging.js +98 -0
  34. package/dist/heart/daemon/subagent-installer.js +125 -0
  35. package/dist/heart/daemon/task-scheduler.js +237 -0
  36. package/dist/heart/harness.js +26 -0
  37. package/dist/heart/identity.js +270 -0
  38. package/dist/heart/kicks.js +144 -0
  39. package/dist/heart/primitives.js +4 -0
  40. package/dist/heart/providers/anthropic.js +329 -0
  41. package/dist/heart/providers/azure.js +66 -0
  42. package/dist/heart/providers/minimax.js +53 -0
  43. package/dist/heart/providers/openai-codex.js +162 -0
  44. package/dist/heart/streaming.js +412 -0
  45. package/dist/heart/turn-coordinator.js +62 -0
  46. package/dist/inner-worker-entry.js +4 -0
  47. package/dist/mind/associative-recall.js +176 -0
  48. package/dist/mind/bundle-manifest.js +118 -0
  49. package/dist/mind/context.js +218 -0
  50. package/dist/mind/first-impressions.js +43 -0
  51. package/dist/mind/format.js +56 -0
  52. package/dist/mind/friends/channel.js +41 -0
  53. package/dist/mind/friends/resolver.js +84 -0
  54. package/dist/mind/friends/store-file.js +171 -0
  55. package/dist/mind/friends/store.js +4 -0
  56. package/dist/mind/friends/tokens.js +26 -0
  57. package/dist/mind/friends/types.js +21 -0
  58. package/dist/mind/memory.js +326 -0
  59. package/dist/mind/phrases.js +43 -0
  60. package/dist/mind/prompt.js +254 -0
  61. package/dist/mind/token-estimate.js +119 -0
  62. package/dist/nerves/cli-logging.js +31 -0
  63. package/dist/nerves/coverage/audit-rules.js +81 -0
  64. package/dist/nerves/coverage/audit.js +200 -0
  65. package/dist/nerves/coverage/cli-main.js +5 -0
  66. package/dist/nerves/coverage/cli.js +51 -0
  67. package/dist/nerves/coverage/contract.js +23 -0
  68. package/dist/nerves/coverage/file-completeness.js +46 -0
  69. package/dist/nerves/coverage/run-artifacts.js +77 -0
  70. package/dist/nerves/coverage/source-scanner.js +34 -0
  71. package/dist/nerves/index.js +152 -0
  72. package/dist/nerves/runtime.js +38 -0
  73. package/dist/repertoire/ado-client.js +211 -0
  74. package/dist/repertoire/ado-context.js +73 -0
  75. package/dist/repertoire/ado-semantic.js +841 -0
  76. package/dist/repertoire/ado-templates.js +146 -0
  77. package/dist/repertoire/coding/index.js +36 -0
  78. package/dist/repertoire/coding/manager.js +489 -0
  79. package/dist/repertoire/coding/monitor.js +60 -0
  80. package/dist/repertoire/coding/reporter.js +45 -0
  81. package/dist/repertoire/coding/spawner.js +102 -0
  82. package/dist/repertoire/coding/tools.js +167 -0
  83. package/dist/repertoire/coding/types.js +2 -0
  84. package/dist/repertoire/data/ado-endpoints.json +122 -0
  85. package/dist/repertoire/data/graph-endpoints.json +212 -0
  86. package/dist/repertoire/github-client.js +64 -0
  87. package/dist/repertoire/graph-client.js +118 -0
  88. package/dist/repertoire/skills.js +156 -0
  89. package/dist/repertoire/tasks/board.js +122 -0
  90. package/dist/repertoire/tasks/index.js +210 -0
  91. package/dist/repertoire/tasks/lifecycle.js +80 -0
  92. package/dist/repertoire/tasks/middleware.js +65 -0
  93. package/dist/repertoire/tasks/parser.js +173 -0
  94. package/dist/repertoire/tasks/scanner.js +132 -0
  95. package/dist/repertoire/tasks/transitions.js +145 -0
  96. package/dist/repertoire/tasks/types.js +2 -0
  97. package/dist/repertoire/tools-base.js +622 -0
  98. package/dist/repertoire/tools-github.js +53 -0
  99. package/dist/repertoire/tools-teams.js +308 -0
  100. package/dist/repertoire/tools.js +199 -0
  101. package/dist/senses/cli-entry.js +15 -0
  102. package/dist/senses/cli.js +523 -0
  103. package/dist/senses/commands.js +98 -0
  104. package/dist/senses/inner-dialog-worker.js +61 -0
  105. package/dist/senses/inner-dialog.js +216 -0
  106. package/dist/senses/teams-entry.js +15 -0
  107. package/dist/senses/teams.js +695 -0
  108. package/dist/senses/trust-gate.js +150 -0
  109. package/package.json +34 -11
  110. package/subagents/README.md +71 -0
  111. package/subagents/work-doer.md +233 -0
  112. package/subagents/work-merger.md +593 -0
  113. package/subagents/work-planner.md +373 -0
  114. package/bin/ouro.js +0 -6
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ // FileFriendStore -- filesystem adapter for FriendStore.
3
+ // Stores each friend as one unified JSON file in bundle `friends/`.
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.FileFriendStore = void 0;
39
+ const fs = __importStar(require("fs"));
40
+ const fsPromises = __importStar(require("fs/promises"));
41
+ const path = __importStar(require("path"));
42
+ const runtime_1 = require("../../nerves/runtime");
43
+ const DEFAULT_ROLE = "friend";
44
+ const DEFAULT_TRUST_LEVEL = "friend";
45
+ class FileFriendStore {
46
+ friendsPath;
47
+ constructor(friendsPath) {
48
+ this.friendsPath = friendsPath;
49
+ fs.mkdirSync(friendsPath, { recursive: true });
50
+ (0, runtime_1.emitNervesEvent)({
51
+ component: "friends",
52
+ event: "friends.store_init",
53
+ message: "file friend store initialized",
54
+ meta: {},
55
+ });
56
+ }
57
+ async get(id) {
58
+ const record = await this.readJson(path.join(this.friendsPath, `${id}.json`));
59
+ if (!record)
60
+ return null;
61
+ return this.normalize(record);
62
+ }
63
+ async put(id, record) {
64
+ await this.writeJson(path.join(this.friendsPath, `${id}.json`), this.normalize(record));
65
+ }
66
+ async delete(id) {
67
+ await this.removeFile(path.join(this.friendsPath, `${id}.json`));
68
+ }
69
+ async findByExternalId(provider, externalId, tenantId) {
70
+ let entries;
71
+ try {
72
+ entries = await fsPromises.readdir(this.friendsPath);
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ for (const entry of entries) {
78
+ if (!entry.endsWith(".json"))
79
+ continue;
80
+ const raw = await this.readJson(path.join(this.friendsPath, entry));
81
+ if (!raw)
82
+ continue;
83
+ const record = this.normalize(raw);
84
+ const match = record.externalIds.some((ext) => ext.provider === provider &&
85
+ ext.externalId === externalId &&
86
+ (tenantId === undefined || ext.tenantId === tenantId));
87
+ if (match) {
88
+ return record;
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+ async hasAnyFriends() {
94
+ let entries;
95
+ try {
96
+ entries = await fsPromises.readdir(this.friendsPath);
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ return entries.some((entry) => entry.endsWith(".json"));
102
+ }
103
+ normalize(raw) {
104
+ const trustLevel = raw.trustLevel;
105
+ const normalizedTrustLevel = trustLevel === "family" ||
106
+ trustLevel === "friend" ||
107
+ trustLevel === "acquaintance" ||
108
+ trustLevel === "stranger"
109
+ ? trustLevel
110
+ : DEFAULT_TRUST_LEVEL;
111
+ return {
112
+ id: raw.id,
113
+ name: raw.name,
114
+ role: typeof raw.role === "string" && raw.role.trim() ? raw.role : DEFAULT_ROLE,
115
+ trustLevel: normalizedTrustLevel,
116
+ connections: Array.isArray(raw.connections)
117
+ ? raw.connections
118
+ .filter((connection) => (typeof connection === "object" &&
119
+ connection !== null &&
120
+ typeof connection.name === "string" &&
121
+ typeof connection.relationship === "string"))
122
+ .map((connection) => ({
123
+ name: connection.name,
124
+ relationship: connection.relationship,
125
+ }))
126
+ : [],
127
+ externalIds: Array.isArray(raw.externalIds) ? raw.externalIds : [],
128
+ tenantMemberships: Array.isArray(raw.tenantMemberships) ? raw.tenantMemberships : [],
129
+ toolPreferences: raw.toolPreferences && typeof raw.toolPreferences === "object"
130
+ ? raw.toolPreferences
131
+ : {},
132
+ notes: raw.notes && typeof raw.notes === "object" ? raw.notes : {},
133
+ totalTokens: typeof raw.totalTokens === "number" ? raw.totalTokens : 0,
134
+ createdAt: typeof raw.createdAt === "string" ? raw.createdAt : new Date().toISOString(),
135
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : new Date().toISOString(),
136
+ schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : 1,
137
+ };
138
+ }
139
+ async readJson(filePath) {
140
+ try {
141
+ const raw = await fsPromises.readFile(filePath, "utf-8");
142
+ try {
143
+ const parsed = JSON.parse(raw);
144
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
145
+ return null;
146
+ }
147
+ return parsed;
148
+ }
149
+ catch {
150
+ return null;
151
+ }
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ async writeJson(filePath, data) {
158
+ await fsPromises.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
159
+ }
160
+ async removeFile(filePath) {
161
+ try {
162
+ await fsPromises.unlink(filePath);
163
+ }
164
+ catch (err) {
165
+ if (err?.code === "ENOENT")
166
+ return;
167
+ throw err;
168
+ }
169
+ }
170
+ }
171
+ exports.FileFriendStore = FileFriendStore;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // Friend store abstraction.
3
+ // All friend persistence goes through FriendStore -- no friend module imports `fs` directly.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ // Token accumulation helper.
3
+ // Tracks cumulative token usage per friend across turns.
4
+ // Called from both CLI and Teams adapters after each agent turn.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.accumulateFriendTokens = accumulateFriendTokens;
7
+ const runtime_1 = require("../../nerves/runtime");
8
+ async function accumulateFriendTokens(store, friendId, usage) {
9
+ if (!usage?.output_tokens)
10
+ return;
11
+ const record = await store.get(friendId);
12
+ if (!record)
13
+ return;
14
+ // Only count output tokens (what the model generated for this friend).
15
+ // Input tokens are mostly system prompt re-sent every turn -- counting them
16
+ // would inflate the total and make the onboarding threshold meaningless.
17
+ record.totalTokens = (record.totalTokens ?? 0) + usage.output_tokens;
18
+ record.updatedAt = new Date().toISOString();
19
+ await store.put(record.id, record);
20
+ (0, runtime_1.emitNervesEvent)({
21
+ component: "friends",
22
+ event: "friends.tokens_accumulated",
23
+ message: "tokens accumulated for friend",
24
+ meta: { friendId, outputTokens: usage.output_tokens },
25
+ });
26
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // Context kernel type definitions.
3
+ // FriendRecord (merged identity + memory), channel capabilities, and resolved context.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.isIdentityProvider = isIdentityProvider;
6
+ exports.isIntegration = isIntegration;
7
+ const runtime_1 = require("../../nerves/runtime");
8
+ const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation"]);
9
+ function isIdentityProvider(value) {
10
+ (0, runtime_1.emitNervesEvent)({
11
+ component: "friends",
12
+ event: "friends.identity_provider_check",
13
+ message: "identity provider validation",
14
+ meta: {},
15
+ });
16
+ return typeof value === "string" && IDENTITY_PROVIDERS.has(value);
17
+ }
18
+ const INTEGRATIONS = new Set(["ado", "github", "graph"]);
19
+ function isIntegration(value) {
20
+ return typeof value === "string" && INTEGRATIONS.has(value);
21
+ }
@@ -0,0 +1,326 @@
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.__memoryTestUtils = void 0;
37
+ exports.ensureMemoryStorePaths = ensureMemoryStorePaths;
38
+ exports.appendFactsWithDedup = appendFactsWithDedup;
39
+ exports.readMemoryFacts = readMemoryFacts;
40
+ exports.saveMemoryFact = saveMemoryFact;
41
+ exports.searchMemoryFacts = searchMemoryFacts;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const crypto_1 = require("crypto");
45
+ const config_1 = require("../heart/config");
46
+ const identity_1 = require("../heart/identity");
47
+ const runtime_1 = require("../nerves/runtime");
48
+ const DEDUP_THRESHOLD = 0.6;
49
+ const ENTITY_TOKEN = /[a-z0-9]+/g;
50
+ const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
51
+ class OpenAIEmbeddingProvider {
52
+ apiKey;
53
+ model;
54
+ constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
55
+ this.apiKey = apiKey;
56
+ this.model = model;
57
+ }
58
+ async embed(texts) {
59
+ const response = await fetch("https://api.openai.com/v1/embeddings", {
60
+ method: "POST",
61
+ headers: {
62
+ Authorization: `Bearer ${this.apiKey}`,
63
+ "Content-Type": "application/json",
64
+ },
65
+ body: JSON.stringify({
66
+ model: this.model,
67
+ input: texts,
68
+ }),
69
+ });
70
+ if (!response.ok) {
71
+ throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
72
+ }
73
+ const payload = (await response.json());
74
+ if (!payload.data || payload.data.length !== texts.length) {
75
+ throw new Error("embedding response missing expected vectors");
76
+ }
77
+ return payload.data.map((entry) => entry.embedding);
78
+ }
79
+ }
80
+ function ensureMemoryStorePaths(rootDir) {
81
+ const factsPath = path.join(rootDir, "facts.jsonl");
82
+ const entitiesPath = path.join(rootDir, "entities.json");
83
+ const dailyDir = path.join(rootDir, "daily");
84
+ fs.mkdirSync(rootDir, { recursive: true });
85
+ fs.mkdirSync(dailyDir, { recursive: true });
86
+ if (!fs.existsSync(factsPath))
87
+ fs.writeFileSync(factsPath, "", "utf8");
88
+ if (!fs.existsSync(entitiesPath))
89
+ fs.writeFileSync(entitiesPath, "{}\n", "utf8");
90
+ (0, runtime_1.emitNervesEvent)({
91
+ component: "mind",
92
+ event: "mind.memory_paths_ready",
93
+ message: "memory store paths ready",
94
+ meta: { rootDir },
95
+ });
96
+ return { rootDir, factsPath, entitiesPath, dailyDir };
97
+ }
98
+ function overlapScore(left, right) {
99
+ const leftWords = new Set(left
100
+ .toLowerCase()
101
+ .split(/[^a-z0-9]+/g)
102
+ .filter(Boolean));
103
+ const rightWords = new Set(right
104
+ .toLowerCase()
105
+ .split(/[^a-z0-9]+/g)
106
+ .filter(Boolean));
107
+ if (leftWords.size === 0 || rightWords.size === 0)
108
+ return 0;
109
+ let common = 0;
110
+ for (const word of leftWords) {
111
+ if (rightWords.has(word))
112
+ common++;
113
+ }
114
+ return common / Math.min(leftWords.size, rightWords.size);
115
+ }
116
+ function readExistingFacts(factsPath) {
117
+ if (!fs.existsSync(factsPath))
118
+ return [];
119
+ const raw = fs.readFileSync(factsPath, "utf8").trim();
120
+ if (!raw)
121
+ return [];
122
+ return raw
123
+ .split("\n")
124
+ .map((line) => JSON.parse(line));
125
+ }
126
+ function readEntityIndex(entitiesPath) {
127
+ if (!fs.existsSync(entitiesPath))
128
+ return {};
129
+ try {
130
+ const raw = fs.readFileSync(entitiesPath, "utf8").trim();
131
+ if (!raw)
132
+ return {};
133
+ return JSON.parse(raw);
134
+ }
135
+ catch {
136
+ return {};
137
+ }
138
+ }
139
+ function writeEntityIndex(entitiesPath, index) {
140
+ fs.writeFileSync(entitiesPath, JSON.stringify(index, null, 2) + "\n", "utf8");
141
+ }
142
+ function extractEntityTokens(text) {
143
+ const matches = text.toLowerCase().match(ENTITY_TOKEN) ?? [];
144
+ return [...new Set(matches.filter((token) => token.length >= 3))];
145
+ }
146
+ function updateEntityIndex(entitiesPath, fact) {
147
+ const index = readEntityIndex(entitiesPath);
148
+ const tokens = extractEntityTokens(fact.text);
149
+ for (const token of tokens) {
150
+ const existing = index[token];
151
+ if (existing) {
152
+ existing.count += 1;
153
+ if (!existing.factIds.includes(fact.id))
154
+ existing.factIds.push(fact.id);
155
+ existing.lastSeenAt = fact.createdAt;
156
+ continue;
157
+ }
158
+ index[token] = {
159
+ count: 1,
160
+ factIds: [fact.id],
161
+ lastSeenAt: fact.createdAt,
162
+ };
163
+ }
164
+ writeEntityIndex(entitiesPath, index);
165
+ }
166
+ function appendDailyFact(dailyDir, fact) {
167
+ fs.mkdirSync(dailyDir, { recursive: true });
168
+ const day = fact.createdAt.slice(0, 10) || "unknown";
169
+ const dayPath = path.join(dailyDir, `${day}.jsonl`);
170
+ fs.appendFileSync(dayPath, `${JSON.stringify(fact)}\n`, "utf8");
171
+ }
172
+ function appendFactsWithDedup(stores, incoming) {
173
+ const existing = readExistingFacts(stores.factsPath);
174
+ const all = [...existing];
175
+ let added = 0;
176
+ let skipped = 0;
177
+ for (const fact of incoming) {
178
+ const duplicate = all.some((prior) => overlapScore(prior.text, fact.text) > DEDUP_THRESHOLD);
179
+ if (duplicate) {
180
+ skipped++;
181
+ continue;
182
+ }
183
+ all.push(fact);
184
+ added++;
185
+ fs.appendFileSync(stores.factsPath, `${JSON.stringify(fact)}\n`, "utf8");
186
+ updateEntityIndex(stores.entitiesPath, fact);
187
+ appendDailyFact(stores.dailyDir, fact);
188
+ }
189
+ (0, runtime_1.emitNervesEvent)({
190
+ component: "mind",
191
+ event: "mind.memory_write",
192
+ message: "memory write completed",
193
+ meta: { added, skipped },
194
+ });
195
+ return { added, skipped };
196
+ }
197
+ function cosineSimilarity(left, right) {
198
+ if (left.length === 0 || right.length === 0 || left.length !== right.length)
199
+ return 0;
200
+ let dot = 0;
201
+ let leftNorm = 0;
202
+ let rightNorm = 0;
203
+ for (let i = 0; i < left.length; i += 1) {
204
+ dot += left[i] * right[i];
205
+ leftNorm += left[i] * left[i];
206
+ rightNorm += right[i] * right[i];
207
+ }
208
+ if (leftNorm === 0 || rightNorm === 0)
209
+ return 0;
210
+ return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
211
+ }
212
+ exports.__memoryTestUtils = {
213
+ cosineSimilarity,
214
+ };
215
+ function createDefaultEmbeddingProvider() {
216
+ const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
217
+ if (!apiKey)
218
+ return null;
219
+ return new OpenAIEmbeddingProvider(apiKey);
220
+ }
221
+ async function buildEmbedding(text, embeddingProvider) {
222
+ const provider = embeddingProvider ?? createDefaultEmbeddingProvider();
223
+ if (!provider) {
224
+ (0, runtime_1.emitNervesEvent)({
225
+ level: "warn",
226
+ component: "mind",
227
+ event: "mind.memory_embedding_unavailable",
228
+ message: "embedding provider unavailable for memory write",
229
+ meta: { reason: "missing_openai_embeddings_key" },
230
+ });
231
+ return [];
232
+ }
233
+ try {
234
+ const vectors = await provider.embed([text]);
235
+ return vectors[0] ?? [];
236
+ }
237
+ catch (error) {
238
+ (0, runtime_1.emitNervesEvent)({
239
+ level: "warn",
240
+ component: "mind",
241
+ event: "mind.memory_embedding_unavailable",
242
+ message: "embedding provider unavailable for memory write",
243
+ meta: {
244
+ reason: error instanceof Error ? error.message : String(error),
245
+ },
246
+ });
247
+ return [];
248
+ }
249
+ }
250
+ function readMemoryFacts(memoryRoot = path.join((0, identity_1.getAgentRoot)(), "psyche", "memory")) {
251
+ return readExistingFacts(path.join(memoryRoot, "facts.jsonl"));
252
+ }
253
+ async function saveMemoryFact(options) {
254
+ const text = options.text.trim();
255
+ const memoryRoot = options.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
256
+ const stores = ensureMemoryStorePaths(memoryRoot);
257
+ const embedding = await buildEmbedding(text, options.embeddingProvider);
258
+ const fact = {
259
+ id: options.idFactory ? options.idFactory() : (0, crypto_1.randomUUID)(),
260
+ text,
261
+ source: options.source,
262
+ about: options.about?.trim() || undefined,
263
+ createdAt: (options.now ?? (() => new Date()))().toISOString(),
264
+ embedding,
265
+ };
266
+ return appendFactsWithDedup(stores, [fact]);
267
+ }
268
+ function substringMatches(queryLower, facts) {
269
+ return facts.filter((fact) => fact.text.toLowerCase().includes(queryLower));
270
+ }
271
+ function uniqueFacts(facts) {
272
+ const seen = new Set();
273
+ const unique = [];
274
+ for (const fact of facts) {
275
+ if (seen.has(fact.id))
276
+ continue;
277
+ seen.add(fact.id);
278
+ unique.push(fact);
279
+ }
280
+ return unique;
281
+ }
282
+ async function searchMemoryFacts(query, facts, embeddingProvider) {
283
+ const trimmed = query.trim();
284
+ if (!trimmed)
285
+ return [];
286
+ const queryLower = trimmed.toLowerCase();
287
+ const substringFallback = () => substringMatches(queryLower, facts).slice(0, 5);
288
+ const embeddedFacts = facts.filter((fact) => Array.isArray(fact.embedding) && fact.embedding.length > 0);
289
+ if (embeddedFacts.length === 0) {
290
+ return substringFallback();
291
+ }
292
+ const provider = embeddingProvider ?? createDefaultEmbeddingProvider();
293
+ if (!provider) {
294
+ return substringFallback();
295
+ }
296
+ try {
297
+ const vectors = await provider.embed([trimmed]);
298
+ const queryEmbedding = vectors[0];
299
+ if (!Array.isArray(queryEmbedding) || queryEmbedding.length === 0) {
300
+ return substringFallback();
301
+ }
302
+ const scored = embeddedFacts
303
+ .filter((fact) => fact.embedding.length === queryEmbedding.length)
304
+ .map((fact) => ({
305
+ fact,
306
+ score: cosineSimilarity(queryEmbedding, fact.embedding),
307
+ }))
308
+ .filter((entry) => entry.score > 0)
309
+ .sort((left, right) => right.score - left.score)
310
+ .map((entry) => entry.fact);
311
+ const fallback = substringFallback();
312
+ return uniqueFacts([...scored, ...fallback]).slice(0, 5);
313
+ }
314
+ catch (error) {
315
+ (0, runtime_1.emitNervesEvent)({
316
+ level: "warn",
317
+ component: "mind",
318
+ event: "mind.memory_embedding_unavailable",
319
+ message: "embedding provider unavailable for memory search",
320
+ meta: {
321
+ reason: error instanceof Error ? error.message : String(error),
322
+ },
323
+ });
324
+ return substringFallback();
325
+ }
326
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ // Shared phrase pools for fun loading messages.
3
+ // Phrases have NO trailing "..." -- adapters add that.
4
+ // Pools are loaded from agent.json via loadAgentConfig().
5
+ // If agent.json has no phrases, loadAgentConfig() auto-fills placeholders.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getPhrases = getPhrases;
8
+ exports.pickPhrase = pickPhrase;
9
+ const identity_1 = require("../heart/identity");
10
+ const runtime_1 = require("../nerves/runtime");
11
+ // Returns phrase pools from agent.json (always present — loadAgentConfig auto-fills).
12
+ function getPhrases() {
13
+ (0, runtime_1.emitNervesEvent)({
14
+ event: "repertoire.load_start",
15
+ component: "repertoire",
16
+ message: "loading phrase pools",
17
+ meta: {},
18
+ });
19
+ const phrases = (0, identity_1.loadAgentConfig)().phrases;
20
+ (0, runtime_1.emitNervesEvent)({
21
+ event: "repertoire.load_end",
22
+ component: "repertoire",
23
+ message: "loaded phrase pools",
24
+ meta: {
25
+ thinking_count: phrases.thinking.length,
26
+ tool_count: phrases.tool.length,
27
+ followup_count: phrases.followup.length,
28
+ },
29
+ });
30
+ return phrases;
31
+ }
32
+ // Pick a random phrase from a pool, avoiding immediate repeats.
33
+ function pickPhrase(pool, lastUsed) {
34
+ if (pool.length === 0)
35
+ return "";
36
+ if (pool.length === 1)
37
+ return pool[0];
38
+ let pick;
39
+ do {
40
+ pick = pool[Math.floor(Math.random() * pool.length)];
41
+ } while (pick === lastUsed);
42
+ return pick;
43
+ }