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

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 (119) 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 +515 -0
  21. package/dist/heart/daemon/daemon-cli.js +675 -0
  22. package/dist/heart/daemon/daemon-entry.js +74 -0
  23. package/dist/heart/daemon/daemon.js +313 -0
  24. package/dist/heart/daemon/hatch-flow.js +285 -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/log-tailer.js +146 -0
  28. package/dist/heart/daemon/message-router.js +98 -0
  29. package/dist/heart/daemon/os-cron.js +260 -0
  30. package/dist/heart/daemon/ouro-bot-entry.js +23 -0
  31. package/dist/heart/daemon/ouro-bot-wrapper.js +90 -0
  32. package/dist/heart/daemon/ouro-entry.js +23 -0
  33. package/dist/heart/daemon/ouro-uti.js +212 -0
  34. package/dist/heart/daemon/process-manager.js +237 -0
  35. package/dist/heart/daemon/runtime-logging.js +98 -0
  36. package/dist/heart/daemon/subagent-installer.js +125 -0
  37. package/dist/heart/daemon/task-scheduler.js +240 -0
  38. package/dist/heart/harness.js +26 -0
  39. package/dist/heart/identity.js +281 -0
  40. package/dist/heart/kicks.js +144 -0
  41. package/dist/heart/primitives.js +4 -0
  42. package/dist/heart/providers/anthropic.js +329 -0
  43. package/dist/heart/providers/azure.js +66 -0
  44. package/dist/heart/providers/minimax.js +53 -0
  45. package/dist/heart/providers/openai-codex.js +162 -0
  46. package/dist/heart/streaming.js +412 -0
  47. package/dist/heart/turn-coordinator.js +62 -0
  48. package/dist/inner-worker-entry.js +4 -0
  49. package/dist/mind/associative-recall.js +197 -0
  50. package/dist/mind/bundle-manifest.js +118 -0
  51. package/dist/mind/context.js +302 -0
  52. package/dist/mind/first-impressions.js +43 -0
  53. package/dist/mind/format.js +56 -0
  54. package/dist/mind/friends/channel.js +41 -0
  55. package/dist/mind/friends/resolver.js +84 -0
  56. package/dist/mind/friends/store-file.js +171 -0
  57. package/dist/mind/friends/store.js +4 -0
  58. package/dist/mind/friends/tokens.js +26 -0
  59. package/dist/mind/friends/types.js +21 -0
  60. package/dist/mind/memory.js +388 -0
  61. package/dist/mind/pending.js +93 -0
  62. package/dist/mind/phrases.js +43 -0
  63. package/dist/mind/prompt-refresh.js +20 -0
  64. package/dist/mind/prompt.js +352 -0
  65. package/dist/mind/token-estimate.js +119 -0
  66. package/dist/nerves/cli-logging.js +31 -0
  67. package/dist/nerves/coverage/audit-rules.js +81 -0
  68. package/dist/nerves/coverage/audit.js +200 -0
  69. package/dist/nerves/coverage/cli-main.js +5 -0
  70. package/dist/nerves/coverage/cli.js +51 -0
  71. package/dist/nerves/coverage/contract.js +23 -0
  72. package/dist/nerves/coverage/file-completeness.js +56 -0
  73. package/dist/nerves/coverage/run-artifacts.js +77 -0
  74. package/dist/nerves/coverage/source-scanner.js +34 -0
  75. package/dist/nerves/index.js +152 -0
  76. package/dist/nerves/runtime.js +38 -0
  77. package/dist/repertoire/ado-client.js +211 -0
  78. package/dist/repertoire/ado-context.js +73 -0
  79. package/dist/repertoire/ado-semantic.js +841 -0
  80. package/dist/repertoire/ado-templates.js +146 -0
  81. package/dist/repertoire/coding/index.js +36 -0
  82. package/dist/repertoire/coding/manager.js +489 -0
  83. package/dist/repertoire/coding/monitor.js +60 -0
  84. package/dist/repertoire/coding/reporter.js +45 -0
  85. package/dist/repertoire/coding/spawner.js +102 -0
  86. package/dist/repertoire/coding/tools.js +167 -0
  87. package/dist/repertoire/coding/types.js +2 -0
  88. package/dist/repertoire/data/ado-endpoints.json +122 -0
  89. package/dist/repertoire/data/graph-endpoints.json +212 -0
  90. package/dist/repertoire/github-client.js +64 -0
  91. package/dist/repertoire/graph-client.js +118 -0
  92. package/dist/repertoire/skills.js +156 -0
  93. package/dist/repertoire/tasks/board.js +122 -0
  94. package/dist/repertoire/tasks/index.js +210 -0
  95. package/dist/repertoire/tasks/lifecycle.js +80 -0
  96. package/dist/repertoire/tasks/middleware.js +65 -0
  97. package/dist/repertoire/tasks/parser.js +173 -0
  98. package/dist/repertoire/tasks/scanner.js +132 -0
  99. package/dist/repertoire/tasks/transitions.js +145 -0
  100. package/dist/repertoire/tasks/types.js +2 -0
  101. package/dist/repertoire/tools-base.js +714 -0
  102. package/dist/repertoire/tools-github.js +53 -0
  103. package/dist/repertoire/tools-teams.js +308 -0
  104. package/dist/repertoire/tools.js +199 -0
  105. package/dist/senses/cli-entry.js +15 -0
  106. package/dist/senses/cli.js +604 -0
  107. package/dist/senses/commands.js +98 -0
  108. package/dist/senses/inner-dialog-worker.js +61 -0
  109. package/dist/senses/inner-dialog.js +231 -0
  110. package/dist/senses/session-lock.js +119 -0
  111. package/dist/senses/teams-entry.js +15 -0
  112. package/dist/senses/teams.js +696 -0
  113. package/dist/senses/trust-gate.js +150 -0
  114. package/package.json +34 -11
  115. package/subagents/README.md +73 -0
  116. package/subagents/work-doer.md +233 -0
  117. package/subagents/work-merger.md +624 -0
  118. package/subagents/work-planner.md +373 -0
  119. package/bin/ouro.js +0 -6
@@ -0,0 +1,352 @@
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.resetPsycheCache = resetPsycheCache;
37
+ exports.buildSessionSummary = buildSessionSummary;
38
+ exports.runtimeInfoSection = runtimeInfoSection;
39
+ exports.contextSection = contextSection;
40
+ exports.buildSystem = buildSystem;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const core_1 = require("../heart/core");
44
+ const tools_1 = require("../repertoire/tools");
45
+ const skills_1 = require("../repertoire/skills");
46
+ const identity_1 = require("../heart/identity");
47
+ const os = __importStar(require("os"));
48
+ const channel_1 = require("./friends/channel");
49
+ const runtime_1 = require("../nerves/runtime");
50
+ const first_impressions_1 = require("./first-impressions");
51
+ const tasks_1 = require("../repertoire/tasks");
52
+ // Lazy-loaded psyche text cache
53
+ let _psycheCache = null;
54
+ function loadPsycheFile(name) {
55
+ try {
56
+ const psycheDir = path.join((0, identity_1.getAgentRoot)(), "psyche");
57
+ return fs.readFileSync(path.join(psycheDir, name), "utf-8").trim();
58
+ }
59
+ catch {
60
+ return "";
61
+ }
62
+ }
63
+ function loadPsyche() {
64
+ if (_psycheCache)
65
+ return _psycheCache;
66
+ _psycheCache = {
67
+ soul: loadPsycheFile("SOUL.md"),
68
+ identity: loadPsycheFile("IDENTITY.md"),
69
+ lore: loadPsycheFile("LORE.md"),
70
+ tacitKnowledge: loadPsycheFile("TACIT.md"),
71
+ aspirations: loadPsycheFile("ASPIRATIONS.md"),
72
+ };
73
+ return _psycheCache;
74
+ }
75
+ function resetPsycheCache() {
76
+ _psycheCache = null;
77
+ }
78
+ const DEFAULT_ACTIVE_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours
79
+ function resolveFriendName(friendId, friendsDir, agentName) {
80
+ if (friendId === "self")
81
+ return agentName;
82
+ try {
83
+ const raw = fs.readFileSync(path.join(friendsDir, `${friendId}.json`), "utf-8");
84
+ const record = JSON.parse(raw);
85
+ return record.name ?? friendId;
86
+ }
87
+ catch {
88
+ return friendId;
89
+ }
90
+ }
91
+ function buildSessionSummary(options) {
92
+ const { sessionsDir, friendsDir, agentName, currentFriendId, currentChannel, currentKey, activeThresholdMs = DEFAULT_ACTIVE_THRESHOLD_MS, } = options;
93
+ if (!fs.existsSync(sessionsDir))
94
+ return "";
95
+ const now = Date.now();
96
+ const entries = [];
97
+ let friendDirs;
98
+ try {
99
+ friendDirs = fs.readdirSync(sessionsDir);
100
+ }
101
+ catch {
102
+ return "";
103
+ }
104
+ for (const friendId of friendDirs) {
105
+ const friendPath = path.join(sessionsDir, friendId);
106
+ let channels;
107
+ try {
108
+ channels = fs.readdirSync(friendPath);
109
+ }
110
+ catch {
111
+ continue;
112
+ }
113
+ for (const channel of channels) {
114
+ const channelPath = path.join(friendPath, channel);
115
+ let keys;
116
+ try {
117
+ keys = fs.readdirSync(channelPath);
118
+ }
119
+ catch {
120
+ continue;
121
+ }
122
+ for (const keyFile of keys) {
123
+ if (!keyFile.endsWith(".json"))
124
+ continue;
125
+ const key = keyFile.replace(/\.json$/, "");
126
+ // Exclude current session
127
+ if (friendId === currentFriendId && channel === currentChannel && key === currentKey) {
128
+ continue;
129
+ }
130
+ const filePath = path.join(channelPath, keyFile);
131
+ let mtimeMs;
132
+ try {
133
+ mtimeMs = fs.statSync(filePath).mtimeMs;
134
+ }
135
+ catch {
136
+ continue;
137
+ }
138
+ if (now - mtimeMs > activeThresholdMs)
139
+ continue;
140
+ const displayName = resolveFriendName(friendId, friendsDir, agentName);
141
+ entries.push({ friendId, displayName, channel, key, lastActivityMs: mtimeMs });
142
+ }
143
+ }
144
+ }
145
+ if (entries.length === 0)
146
+ return "";
147
+ // Sort by most recent first
148
+ entries.sort((a, b) => b.lastActivityMs - a.lastActivityMs);
149
+ const lines = ["## active sessions"];
150
+ for (const entry of entries) {
151
+ const ago = formatTimeAgo(now - entry.lastActivityMs);
152
+ lines.push(`- ${entry.displayName}/${entry.channel}/${entry.key} (last: ${ago})`);
153
+ }
154
+ return lines.join("\n");
155
+ }
156
+ function formatTimeAgo(ms) {
157
+ const minutes = Math.floor(ms / 60000);
158
+ if (minutes < 60)
159
+ return `${minutes}m ago`;
160
+ const hours = Math.floor(minutes / 60);
161
+ if (hours < 24)
162
+ return `${hours}h ago`;
163
+ const days = Math.floor(hours / 24);
164
+ return `${days}d ago`;
165
+ }
166
+ function soulSection() {
167
+ return loadPsyche().soul;
168
+ }
169
+ function identitySection() {
170
+ return loadPsyche().identity;
171
+ }
172
+ function loreSection() {
173
+ const text = loadPsyche().lore;
174
+ if (!text)
175
+ return "";
176
+ return `## my lore\n${text}`;
177
+ }
178
+ function tacitKnowledgeSection() {
179
+ const text = loadPsyche().tacitKnowledge;
180
+ if (!text)
181
+ return "";
182
+ return `## tacit knowledge\n${text}`;
183
+ }
184
+ function aspirationsSection() {
185
+ const text = loadPsyche().aspirations;
186
+ if (!text)
187
+ return "";
188
+ return `## my aspirations\n${text}`;
189
+ }
190
+ function runtimeInfoSection(channel) {
191
+ const lines = [];
192
+ const agentName = (0, identity_1.getAgentName)();
193
+ lines.push(`## runtime`);
194
+ lines.push(`agent: ${agentName}`);
195
+ lines.push(`cwd: ${process.cwd()}`);
196
+ lines.push(`channel: ${channel}`);
197
+ lines.push(`i can read and modify my own source code.`);
198
+ if (channel === "cli") {
199
+ lines.push("i introduce myself on boot with a fun random greeting.");
200
+ }
201
+ else {
202
+ lines.push("i am responding in Microsoft Teams. i keep responses concise. i use markdown formatting. i do not introduce myself on boot.");
203
+ }
204
+ return lines.join("\n");
205
+ }
206
+ function providerSection() {
207
+ return `## my provider\n${(0, core_1.getProviderDisplayLabel)()}`;
208
+ }
209
+ function dateSection() {
210
+ const today = new Date().toISOString().slice(0, 10);
211
+ return `current date: ${today}`;
212
+ }
213
+ function toolsSection(channel, options) {
214
+ const channelTools = (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)(channel));
215
+ const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.finalAnswerTool] : channelTools;
216
+ const list = activeTools
217
+ .map((t) => `- ${t.function.name}: ${t.function.description}`)
218
+ .join("\n");
219
+ return `## my tools\n${list}`;
220
+ }
221
+ function skillsSection() {
222
+ const names = (0, skills_1.listSkills)() || [];
223
+ if (!names.length)
224
+ return "";
225
+ return `## my skills (use load_skill to activate)\n${names.join(", ")}`;
226
+ }
227
+ function taskBoardSection() {
228
+ try {
229
+ const board = (0, tasks_1.getTaskModule)().getBoard().compact.trim();
230
+ if (!board)
231
+ return "";
232
+ return `## task board\n${board}`;
233
+ }
234
+ catch {
235
+ return "";
236
+ }
237
+ }
238
+ function memoryFriendToolContractSection() {
239
+ return `## memory and friend tool contracts
240
+ 1. \`save_friend_note\` — When I learn something about a person - a preference, a tool setting, a personal detail, or how they like to work - I call \`save_friend_note\` immediately. This is how I build knowledge about people.
241
+ 2. \`memory_save\` — When I learn something general - about a project, codebase, system, decision, or anything I might need later that isn't about a specific person - I call \`memory_save\`. When in doubt, I save it.
242
+ 3. \`get_friend_note\` — When I need to check what I know about someone who isn't in this conversation - cross-referencing before mentioning someone, or checking context about a person someone else brought up - I call \`get_friend_note\`.
243
+ 4. \`memory_search\` — When I need to recall something I learned before - a topic comes up and I want to check what I know - I call \`memory_search\`.
244
+
245
+ ## what's already in my context
246
+ - My active friend's notes are auto-loaded (I don't need \`get_friend_note\` for the person I'm talking to).
247
+ - Associative recall auto-injects relevant facts (but \`memory_search\` is there when I need something specific).
248
+ - My psyche files (SOUL, IDENTITY, TACIT, LORE, ASPIRATIONS) are always loaded - I already know who I am.
249
+ - My task board is always loaded - I already know my work.`;
250
+ }
251
+ function toolBehaviorSection(options) {
252
+ if (!(options?.toolChoiceRequired ?? true))
253
+ return "";
254
+ return `## tool behavior
255
+ tool_choice is set to "required" -- i must call a tool on every turn.
256
+ - need more information? i call a tool.
257
+ - ready to respond to the user? i call \`final_answer\`.
258
+ \`final_answer\` is a tool call -- it satisfies the tool_choice requirement.
259
+ \`final_answer\` must be the ONLY tool call in that turn. do not combine it with other tool calls.
260
+ do NOT call \`get_current_time\` or other no-op tools just before \`final_answer\`. if i am done, i call \`final_answer\` directly.`;
261
+ }
262
+ function contextSection(context) {
263
+ if (!context)
264
+ return "";
265
+ const lines = ["## friend context"];
266
+ const friendOrIdentity = context.friend;
267
+ if (!friendOrIdentity)
268
+ return "";
269
+ const emailId = friendOrIdentity.externalIds.find(e => e.provider === "aad");
270
+ const idDisplay = emailId
271
+ ? `${friendOrIdentity.name} (${emailId.externalId})`
272
+ : friendOrIdentity.name;
273
+ lines.push(`friend: ${idDisplay}`);
274
+ // Channel
275
+ const ch = context.channel;
276
+ const traits = [];
277
+ if (ch.supportsMarkdown)
278
+ traits.push("markdown");
279
+ if (!ch.supportsStreaming)
280
+ traits.push("no streaming");
281
+ if (ch.supportsStreaming)
282
+ traits.push("streaming");
283
+ if (ch.supportsRichCards)
284
+ traits.push("rich cards");
285
+ // maxMessageLength constraint removed -- chunked streaming handles delivery,
286
+ // error recovery splits on failure. No artificial limits in the prompt.
287
+ /* v8 ignore next -- empty-traits branch unreachable: streaming/no-streaming always adds a trait @preserve */
288
+ lines.push(`channel: ${ch.channel}${traits.length ? ` (${traits.join(", ")})` : ""}`);
289
+ // Friend record (guaranteed non-null here -- checked above)
290
+ const friend = context.friend;
291
+ // Always-on directives (permanent in contextSection, never gated by token threshold)
292
+ lines.push("");
293
+ lines.push("my conversation memory is ephemeral -- it resets between sessions. anything i learn about my friend, i save with save_friend_note so future me remembers.");
294
+ lines.push("the conversation is my source of truth. my notes are a journal for future me -- they may be stale or incomplete.");
295
+ lines.push("when i learn something that might invalidate an existing note, i check related notes and update or override any that are stale.");
296
+ lines.push("i save ANYTHING i learn about my friend immediately with save_friend_note -- names, preferences, what they do, what they care about. when in doubt, save it. saving comes BEFORE responding: i call save_friend_note first, then final_answer on the next turn.");
297
+ // Onboarding instructions (only below token threshold -- drop once exceeded)
298
+ const impressions = (0, first_impressions_1.getFirstImpressions)(friend);
299
+ if (impressions) {
300
+ lines.push(impressions);
301
+ }
302
+ // Friend notes (from FriendRecord -- rendered in system prompt, NOT toolPreferences)
303
+ if (Object.keys(friend.notes).length > 0) {
304
+ lines.push("");
305
+ lines.push("## what i know about this friend");
306
+ for (const [key, entry] of Object.entries(friend.notes)) {
307
+ lines.push(`- ${key}: [${entry.savedAt.slice(0, 10)}] ${entry.value}`);
308
+ }
309
+ }
310
+ return lines.join("\n");
311
+ }
312
+ async function buildSystem(channel = "cli", options, context) {
313
+ (0, runtime_1.emitNervesEvent)({
314
+ event: "mind.step_start",
315
+ component: "mind",
316
+ message: "buildSystem started",
317
+ meta: { channel, has_context: Boolean(context), tool_choice_required: Boolean(options?.toolChoiceRequired) },
318
+ });
319
+ const system = [
320
+ soulSection(),
321
+ identitySection(),
322
+ loreSection(),
323
+ tacitKnowledgeSection(),
324
+ aspirationsSection(),
325
+ runtimeInfoSection(channel),
326
+ providerSection(),
327
+ dateSection(),
328
+ toolsSection(channel, options),
329
+ skillsSection(),
330
+ taskBoardSection(),
331
+ buildSessionSummary({
332
+ sessionsDir: path.join(os.homedir(), ".agentstate", (0, identity_1.getAgentName)(), "sessions"),
333
+ friendsDir: path.join((0, identity_1.getAgentRoot)(), "friends"),
334
+ agentName: (0, identity_1.getAgentName)(),
335
+ currentFriendId: context?.friend?.id,
336
+ currentChannel: channel,
337
+ currentKey: "session",
338
+ }),
339
+ memoryFriendToolContractSection(),
340
+ toolBehaviorSection(options),
341
+ contextSection(context),
342
+ ]
343
+ .filter(Boolean)
344
+ .join("\n\n");
345
+ (0, runtime_1.emitNervesEvent)({
346
+ event: "mind.step_end",
347
+ component: "mind",
348
+ message: "buildSystem completed",
349
+ meta: { channel },
350
+ });
351
+ return system;
352
+ }
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__internal = void 0;
4
+ exports.estimateTokensForMessage = estimateTokensForMessage;
5
+ exports.estimateTokensForMessages = estimateTokensForMessages;
6
+ const runtime_1 = require("../nerves/runtime");
7
+ // A conservative, dependency-free token estimator.
8
+ // Goal: avoid context overflows without pulling in tokenizer deps.
9
+ const CHARS_PER_TOKEN = 4;
10
+ const PER_MESSAGE_OVERHEAD_TOKENS = 10;
11
+ function safeStringify(value) {
12
+ try {
13
+ return JSON.stringify(value);
14
+ }
15
+ catch {
16
+ return "";
17
+ }
18
+ }
19
+ function countCharsInContent(content) {
20
+ if (!content)
21
+ return 0;
22
+ if (typeof content === "string")
23
+ return content.length;
24
+ if (Array.isArray(content)) {
25
+ let total = 0;
26
+ for (const part of content) {
27
+ if (!part)
28
+ continue;
29
+ if (typeof part === "string") {
30
+ total += part.length;
31
+ continue;
32
+ }
33
+ if (typeof part === "object") {
34
+ // Common OpenAI content-part shapes: {type:"text", text:"..."}
35
+ // Be defensive and only count obvious text-like fields.
36
+ const p = part;
37
+ if (typeof p.text === "string")
38
+ total += p.text.length;
39
+ if (typeof p.content === "string")
40
+ total += p.content.length;
41
+ if (typeof p.name === "string")
42
+ total += p.name.length;
43
+ // As a fallback, stringify small-ish objects. This is conservative.
44
+ if (total === 0)
45
+ total += safeStringify(p).length;
46
+ }
47
+ }
48
+ return total;
49
+ }
50
+ if (typeof content === "object") {
51
+ const c = content;
52
+ if (typeof c.text === "string")
53
+ return c.text.length;
54
+ if (typeof c.content === "string")
55
+ return c.content.length;
56
+ return safeStringify(content).length;
57
+ }
58
+ return 0;
59
+ }
60
+ function countCharsInToolCalls(toolCalls) {
61
+ if (!Array.isArray(toolCalls))
62
+ return 0;
63
+ let total = 0;
64
+ for (const tc of toolCalls) {
65
+ if (!tc || typeof tc !== "object")
66
+ continue;
67
+ const t = tc;
68
+ if (typeof t.id === "string")
69
+ total += t.id.length;
70
+ if (typeof t.type === "string")
71
+ total += t.type.length;
72
+ if (t.function && typeof t.function === "object") {
73
+ if (typeof t.function.name === "string")
74
+ total += t.function.name.length;
75
+ if (typeof t.function.arguments === "string")
76
+ total += t.function.arguments.length;
77
+ else if (t.function.arguments != null)
78
+ total += safeStringify(t.function.arguments).length;
79
+ }
80
+ }
81
+ return total;
82
+ }
83
+ function estimateTokensForMessage(msg) {
84
+ try {
85
+ let chars = 0;
86
+ // role/name/tool_call_id count as metadata.
87
+ const m = msg;
88
+ if (typeof m.role === "string")
89
+ chars += m.role.length;
90
+ if (typeof m.name === "string")
91
+ chars += m.name.length;
92
+ if (typeof m.tool_call_id === "string")
93
+ chars += m.tool_call_id.length;
94
+ chars += countCharsInContent(m.content);
95
+ chars += countCharsInToolCalls(m.tool_calls);
96
+ return PER_MESSAGE_OVERHEAD_TOKENS + Math.ceil(chars / CHARS_PER_TOKEN);
97
+ }
98
+ catch (error) {
99
+ (0, runtime_1.emitNervesEvent)({
100
+ component: "mind",
101
+ event: "mind.token_estimate_error",
102
+ level: "warn",
103
+ message: "token estimation failed; using overhead fallback",
104
+ meta: { reason: error instanceof Error ? error.message : String(error) },
105
+ });
106
+ // estimator must never throw
107
+ return PER_MESSAGE_OVERHEAD_TOKENS;
108
+ }
109
+ }
110
+ function estimateTokensForMessages(msgs) {
111
+ let total = 0;
112
+ for (const msg of msgs)
113
+ total += estimateTokensForMessage(msg);
114
+ return total;
115
+ }
116
+ exports.__internal = {
117
+ CHARS_PER_TOKEN,
118
+ PER_MESSAGE_OVERHEAD_TOKENS,
119
+ };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.configureCliRuntimeLogger = configureCliRuntimeLogger;
4
+ const config_1 = require("../heart/config");
5
+ const nerves_1 = require("../nerves");
6
+ const runtime_1 = require("./runtime");
7
+ const runtime_2 = require("./runtime");
8
+ function resolveCliSinks(sinks) {
9
+ const requested = sinks && sinks.length > 0 ? sinks : ["terminal", "ndjson"];
10
+ return [...new Set(requested)];
11
+ }
12
+ function configureCliRuntimeLogger(_friendId, options = {}) {
13
+ const sinkKinds = resolveCliSinks(options.sinks);
14
+ const sinks = sinkKinds.map((sinkKind) => {
15
+ if (sinkKind === "terminal") {
16
+ return (0, nerves_1.createTerminalSink)();
17
+ }
18
+ return (0, nerves_1.createNdjsonFileSink)((0, config_1.logPath)("cli", "runtime"));
19
+ });
20
+ const logger = (0, nerves_1.createLogger)({
21
+ level: options.level ?? "info",
22
+ sinks,
23
+ });
24
+ (0, runtime_2.setRuntimeLogger)(logger);
25
+ (0, runtime_1.emitNervesEvent)({
26
+ component: "senses",
27
+ event: "senses.cli_logger_configured",
28
+ message: "cli runtime logger configured",
29
+ meta: { sinks: sinkKinds, level: options.level ?? "info" },
30
+ });
31
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Per-test audit rules for nerves event coverage.
4
+ *
5
+ * Rule 1: every-test-emits -- every test must emit at least one event
6
+ * Rule 2: start/end pairing -- _start events must have matching _end or _error
7
+ * Rule 3: error context -- error-level events must have non-empty meta
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.checkEveryTestEmits = checkEveryTestEmits;
11
+ exports.checkStartEndPairing = checkStartEndPairing;
12
+ exports.checkErrorContext = checkErrorContext;
13
+ /**
14
+ * Rule 1: Every test must emit at least one nerves event.
15
+ */
16
+ function checkEveryTestEmits(data) {
17
+ if (!data || typeof data !== "object") {
18
+ return { status: "fail", total_tests: 0, silent_tests: [] };
19
+ }
20
+ const entries = Object.entries(data);
21
+ const silent = entries
22
+ .filter(([, events]) => !Array.isArray(events) || events.length === 0)
23
+ .map(([name]) => name);
24
+ return {
25
+ status: silent.length === 0 ? "pass" : "fail",
26
+ total_tests: entries.length,
27
+ silent_tests: silent,
28
+ };
29
+ }
30
+ /**
31
+ * Rule 2: _start events must have matching _end or _error within the same test.
32
+ */
33
+ function checkStartEndPairing(data) {
34
+ if (!data || typeof data !== "object") {
35
+ return { status: "fail", unmatched: [] };
36
+ }
37
+ const unmatched = [];
38
+ for (const [testName, events] of Object.entries(data)) {
39
+ if (!Array.isArray(events))
40
+ continue;
41
+ const eventNames = events.map((e) => e.event);
42
+ const startEvents = eventNames.filter((name) => name.endsWith("_start"));
43
+ for (const startEvent of startEvents) {
44
+ const prefix = startEvent.slice(0, -"_start".length);
45
+ const hasEnd = eventNames.some((name) => name === `${prefix}_end`);
46
+ const hasError = eventNames.some((name) => name === `${prefix}_error`);
47
+ if (!hasEnd && !hasError) {
48
+ unmatched.push(`${testName}: ${startEvent} has no matching ${prefix}_end or ${prefix}_error`);
49
+ }
50
+ }
51
+ }
52
+ return {
53
+ status: unmatched.length === 0 ? "pass" : "fail",
54
+ unmatched,
55
+ };
56
+ }
57
+ /**
58
+ * Rule 3: Error-level events must have non-empty meta (at least one key).
59
+ */
60
+ function checkErrorContext(data) {
61
+ if (!data || typeof data !== "object") {
62
+ return { status: "fail", violations: [] };
63
+ }
64
+ const violations = [];
65
+ for (const [testName, events] of Object.entries(data)) {
66
+ if (!Array.isArray(events))
67
+ continue;
68
+ for (const event of events) {
69
+ if (event.level !== "error")
70
+ continue;
71
+ const meta = event.meta;
72
+ if (!meta || typeof meta !== "object" || Object.keys(meta).length === 0) {
73
+ violations.push(`${testName}: error event '${event.event}' has empty or missing meta`);
74
+ }
75
+ }
76
+ }
77
+ return {
78
+ status: violations.length === 0 ? "pass" : "fail",
79
+ violations,
80
+ };
81
+ }