@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,714 @@
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.finalAnswerTool = exports.baseToolHandlers = exports.tools = exports.baseToolDefinitions = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const child_process_1 = require("child_process");
39
+ const path = __importStar(require("path"));
40
+ const skills_1 = require("./skills");
41
+ const config_1 = require("../heart/config");
42
+ const runtime_1 = require("../nerves/runtime");
43
+ const identity_1 = require("../heart/identity");
44
+ const os = __importStar(require("os"));
45
+ const tasks_1 = require("./tasks");
46
+ const tools_1 = require("./coding/tools");
47
+ const memory_1 = require("../mind/memory");
48
+ const postIt = (msg) => `post-it from past you:\n${msg}`;
49
+ exports.baseToolDefinitions = [
50
+ {
51
+ tool: {
52
+ type: "function",
53
+ function: {
54
+ name: "read_file",
55
+ description: "read file contents",
56
+ parameters: {
57
+ type: "object",
58
+ properties: { path: { type: "string" } },
59
+ required: ["path"],
60
+ },
61
+ },
62
+ },
63
+ handler: (a) => fs.readFileSync(a.path, "utf-8"),
64
+ },
65
+ {
66
+ tool: {
67
+ type: "function",
68
+ function: {
69
+ name: "write_file",
70
+ description: "write content to file",
71
+ parameters: {
72
+ type: "object",
73
+ properties: { path: { type: "string" }, content: { type: "string" } },
74
+ required: ["path", "content"],
75
+ },
76
+ },
77
+ },
78
+ handler: (a) => (fs.writeFileSync(a.path, a.content, "utf-8"), "ok"),
79
+ },
80
+ {
81
+ tool: {
82
+ type: "function",
83
+ function: {
84
+ name: "shell",
85
+ description: "run shell command",
86
+ parameters: {
87
+ type: "object",
88
+ properties: { command: { type: "string" } },
89
+ required: ["command"],
90
+ },
91
+ },
92
+ },
93
+ handler: (a) => (0, child_process_1.execSync)(a.command, { encoding: "utf-8", timeout: 30000 }),
94
+ },
95
+ {
96
+ tool: {
97
+ type: "function",
98
+ function: {
99
+ name: "list_directory",
100
+ description: "list directory contents",
101
+ parameters: {
102
+ type: "object",
103
+ properties: { path: { type: "string" } },
104
+ required: ["path"],
105
+ },
106
+ },
107
+ },
108
+ handler: (a) => fs
109
+ .readdirSync(a.path, { withFileTypes: true })
110
+ .map((e) => `${e.isDirectory() ? "d" : "-"} ${e.name}`)
111
+ .join("\n"),
112
+ },
113
+ {
114
+ tool: {
115
+ type: "function",
116
+ function: {
117
+ name: "git_commit",
118
+ description: "commit changes to git with explicit paths",
119
+ parameters: {
120
+ type: "object",
121
+ properties: {
122
+ message: { type: "string" },
123
+ paths: { type: "array", items: { type: "string" } },
124
+ },
125
+ required: ["message", "paths"],
126
+ },
127
+ },
128
+ },
129
+ handler: (a) => {
130
+ try {
131
+ if (!a.paths || !Array.isArray(a.paths) || a.paths.length === 0) {
132
+ return postIt("paths are required. specify explicit files to commit.");
133
+ }
134
+ for (const p of a.paths) {
135
+ if (!fs.existsSync(p)) {
136
+ return postIt(`path does not exist: ${p}`);
137
+ }
138
+ (0, child_process_1.execSync)(`git add ${p}`, { encoding: "utf-8" });
139
+ }
140
+ const diff = (0, child_process_1.execSync)("git diff --cached --stat", { encoding: "utf-8" });
141
+ if (!diff || diff.trim().length === 0) {
142
+ return postIt("nothing was staged. check your changes or paths.");
143
+ }
144
+ (0, child_process_1.execSync)(`git commit -m \"${a.message}\"`, { encoding: "utf-8" });
145
+ return `${diff}\ncommitted`;
146
+ }
147
+ catch (e) {
148
+ return `failed: ${e}`;
149
+ }
150
+ },
151
+ },
152
+ {
153
+ tool: {
154
+ type: "function",
155
+ function: {
156
+ name: "gh_cli",
157
+ description: "execute a GitHub CLI (gh) command. use carefully.",
158
+ parameters: {
159
+ type: "object",
160
+ properties: {
161
+ command: { type: "string" },
162
+ },
163
+ required: ["command"],
164
+ },
165
+ },
166
+ },
167
+ handler: (a) => {
168
+ try {
169
+ return (0, child_process_1.execSync)(`gh ${a.command}`, { encoding: "utf-8", timeout: 60000 });
170
+ }
171
+ catch (e) {
172
+ return `error: ${e}`;
173
+ }
174
+ },
175
+ },
176
+ {
177
+ tool: {
178
+ type: "function",
179
+ function: {
180
+ name: "list_skills",
181
+ description: "list all available skills",
182
+ parameters: { type: "object", properties: {} },
183
+ },
184
+ },
185
+ handler: () => JSON.stringify((0, skills_1.listSkills)()),
186
+ },
187
+ {
188
+ tool: {
189
+ type: "function",
190
+ function: {
191
+ name: "load_skill",
192
+ description: "load a skill by name, returns its content",
193
+ parameters: {
194
+ type: "object",
195
+ properties: { name: { type: "string" } },
196
+ required: ["name"],
197
+ },
198
+ },
199
+ },
200
+ handler: (a) => {
201
+ try {
202
+ return (0, skills_1.loadSkill)(a.name);
203
+ }
204
+ catch (e) {
205
+ return `error: ${e}`;
206
+ }
207
+ },
208
+ },
209
+ {
210
+ tool: {
211
+ type: "function",
212
+ function: {
213
+ name: "get_current_time",
214
+ description: "get the current date and time in America/Los_Angeles (Pacific Time)",
215
+ parameters: { type: "object", properties: {} },
216
+ },
217
+ },
218
+ handler: () => new Date().toLocaleString("en-US", {
219
+ timeZone: "America/Los_Angeles",
220
+ hour12: false,
221
+ }),
222
+ },
223
+ {
224
+ tool: {
225
+ type: "function",
226
+ function: {
227
+ name: "claude",
228
+ description: "use claude code to query this codebase or get an outside perspective. useful for code review, second opinions, and asking questions about your own source.",
229
+ parameters: {
230
+ type: "object",
231
+ properties: { prompt: { type: "string" } },
232
+ required: ["prompt"],
233
+ },
234
+ },
235
+ },
236
+ handler: (a) => {
237
+ try {
238
+ const result = (0, child_process_1.spawnSync)("claude", ["-p", "--dangerously-skip-permissions", "--add-dir", "."], {
239
+ input: a.prompt,
240
+ encoding: "utf-8",
241
+ timeout: 60000,
242
+ });
243
+ if (result.error)
244
+ return `error: ${result.error}`;
245
+ if (result.status !== 0)
246
+ return `claude exited with code ${result.status}: ${result.stderr}`;
247
+ return result.stdout || "(no output)";
248
+ }
249
+ catch (e) {
250
+ return `error: ${e}`;
251
+ }
252
+ },
253
+ },
254
+ {
255
+ tool: {
256
+ type: "function",
257
+ function: {
258
+ name: "web_search",
259
+ description: "search the web using perplexity. returns ranked results with titles, urls, and snippets",
260
+ parameters: {
261
+ type: "object",
262
+ properties: { query: { type: "string" } },
263
+ required: ["query"],
264
+ },
265
+ },
266
+ },
267
+ handler: async (a) => {
268
+ try {
269
+ const key = (0, config_1.getIntegrationsConfig)().perplexityApiKey;
270
+ if (!key)
271
+ return "error: perplexityApiKey not configured in secrets.json";
272
+ const res = await fetch("https://api.perplexity.ai/search", {
273
+ method: "POST",
274
+ headers: {
275
+ Authorization: `Bearer ${key}`,
276
+ "Content-Type": "application/json",
277
+ },
278
+ body: JSON.stringify({ query: a.query, max_results: 5 }),
279
+ });
280
+ if (!res.ok)
281
+ return `error: ${res.status} ${res.statusText}`;
282
+ const data = (await res.json());
283
+ if (!data.results?.length)
284
+ return "no results found";
285
+ return data.results
286
+ .map((r) => `${r.title}\n${r.url}\n${r.snippet}`)
287
+ .join("\n\n");
288
+ }
289
+ catch (e) {
290
+ return `error: ${e}`;
291
+ }
292
+ },
293
+ },
294
+ {
295
+ tool: {
296
+ type: "function",
297
+ function: {
298
+ name: "memory_search",
299
+ description: "search remembered facts stored in psyche memory and return relevant matches for a query",
300
+ parameters: {
301
+ type: "object",
302
+ properties: { query: { type: "string" } },
303
+ required: ["query"],
304
+ },
305
+ },
306
+ },
307
+ handler: async (a) => {
308
+ try {
309
+ const query = (a.query || "").trim();
310
+ if (!query)
311
+ return "query is required";
312
+ const memoryRoot = path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
313
+ const hits = await (0, memory_1.searchMemoryFacts)(query, (0, memory_1.readMemoryFacts)(memoryRoot));
314
+ return hits
315
+ .map((fact) => `- ${fact.text} (source=${fact.source}, createdAt=${fact.createdAt})`)
316
+ .join("\n");
317
+ }
318
+ catch (e) {
319
+ return `error: ${e instanceof Error ? e.message : String(e)}`;
320
+ }
321
+ },
322
+ },
323
+ {
324
+ tool: {
325
+ type: "function",
326
+ function: {
327
+ name: "memory_save",
328
+ description: "save a general memory fact i want to recall later. optional 'about' can tag the fact to a person/topic/context",
329
+ parameters: {
330
+ type: "object",
331
+ properties: {
332
+ text: { type: "string" },
333
+ about: { type: "string" },
334
+ },
335
+ required: ["text"],
336
+ },
337
+ },
338
+ },
339
+ handler: async (a) => {
340
+ const text = (a.text || "").trim();
341
+ if (!text)
342
+ return "text is required";
343
+ const result = await (0, memory_1.saveMemoryFact)({
344
+ text,
345
+ source: "tool:memory_save",
346
+ about: typeof a.about === "string" ? a.about : undefined,
347
+ });
348
+ return `saved memory fact (added=${result.added}, skipped=${result.skipped})`;
349
+ },
350
+ },
351
+ {
352
+ tool: {
353
+ type: "function",
354
+ function: {
355
+ name: "get_friend_note",
356
+ description: "read a specific friend record by friend id. use this when i need notes/context about someone not currently active",
357
+ parameters: {
358
+ type: "object",
359
+ properties: {
360
+ friendId: { type: "string" },
361
+ },
362
+ required: ["friendId"],
363
+ },
364
+ },
365
+ },
366
+ handler: async (a, ctx) => {
367
+ const friendId = (a.friendId || "").trim();
368
+ if (!friendId)
369
+ return "friendId is required";
370
+ if (!ctx?.friendStore)
371
+ return "i can't read friend notes -- friend store not available";
372
+ const friend = await ctx.friendStore.get(friendId);
373
+ if (!friend)
374
+ return `friend not found: ${friendId}`;
375
+ return JSON.stringify(friend, null, 2);
376
+ },
377
+ },
378
+ {
379
+ tool: {
380
+ type: "function",
381
+ function: {
382
+ name: "task_board",
383
+ description: "show the task board grouped by status",
384
+ parameters: { type: "object", properties: {} },
385
+ },
386
+ },
387
+ handler: () => {
388
+ const board = (0, tasks_1.getTaskModule)().getBoard();
389
+ return board.full || board.compact || "no tasks found";
390
+ },
391
+ },
392
+ {
393
+ tool: {
394
+ type: "function",
395
+ function: {
396
+ name: "task_create",
397
+ description: "create a new task in the bundle task system",
398
+ parameters: {
399
+ type: "object",
400
+ properties: {
401
+ title: { type: "string" },
402
+ type: { type: "string", enum: ["one-shot", "ongoing", "habit"] },
403
+ category: { type: "string" },
404
+ body: { type: "string" },
405
+ },
406
+ required: ["title", "type", "category", "body"],
407
+ },
408
+ },
409
+ },
410
+ handler: (a) => {
411
+ try {
412
+ const created = (0, tasks_1.getTaskModule)().createTask({
413
+ title: a.title,
414
+ type: a.type,
415
+ category: a.category,
416
+ body: a.body,
417
+ });
418
+ return `created: ${created}`;
419
+ }
420
+ catch (error) {
421
+ return `error: ${error instanceof Error ? error.message : String(error)}`;
422
+ }
423
+ },
424
+ },
425
+ {
426
+ tool: {
427
+ type: "function",
428
+ function: {
429
+ name: "task_update_status",
430
+ description: "update a task status using validated transitions",
431
+ parameters: {
432
+ type: "object",
433
+ properties: {
434
+ name: { type: "string" },
435
+ status: { type: "string" },
436
+ },
437
+ required: ["name", "status"],
438
+ },
439
+ },
440
+ },
441
+ handler: (a) => {
442
+ const result = (0, tasks_1.getTaskModule)().updateStatus(a.name, a.status);
443
+ if (!result.ok) {
444
+ return `error: ${result.reason ?? "status update failed"}`;
445
+ }
446
+ const archivedSuffix = result.archived && result.archived.length > 0
447
+ ? ` | archived: ${result.archived.join(", ")}`
448
+ : "";
449
+ return `updated: ${a.name} -> ${result.to}${archivedSuffix}`;
450
+ },
451
+ },
452
+ {
453
+ tool: {
454
+ type: "function",
455
+ function: {
456
+ name: "task_board_status",
457
+ description: "show board detail for a specific status",
458
+ parameters: {
459
+ type: "object",
460
+ properties: {
461
+ status: { type: "string" },
462
+ },
463
+ required: ["status"],
464
+ },
465
+ },
466
+ },
467
+ handler: (a) => {
468
+ const lines = (0, tasks_1.getTaskModule)().boardStatus(a.status);
469
+ return lines.length > 0 ? lines.join("\n") : "no tasks in that status";
470
+ },
471
+ },
472
+ {
473
+ tool: {
474
+ type: "function",
475
+ function: {
476
+ name: "task_board_action",
477
+ description: "show tasks or validation issues that require action",
478
+ parameters: {
479
+ type: "object",
480
+ properties: {
481
+ scope: { type: "string" },
482
+ },
483
+ },
484
+ },
485
+ },
486
+ handler: (a) => {
487
+ const lines = (0, tasks_1.getTaskModule)().boardAction();
488
+ if (!a.scope) {
489
+ return lines.length > 0 ? lines.join("\n") : "no action required";
490
+ }
491
+ const filtered = lines.filter((line) => line.includes(a.scope));
492
+ return filtered.length > 0 ? filtered.join("\n") : "no matching action items";
493
+ },
494
+ },
495
+ {
496
+ tool: {
497
+ type: "function",
498
+ function: {
499
+ name: "task_board_deps",
500
+ description: "show unresolved task dependencies",
501
+ parameters: { type: "object", properties: {} },
502
+ },
503
+ },
504
+ handler: () => {
505
+ const lines = (0, tasks_1.getTaskModule)().boardDeps();
506
+ return lines.length > 0 ? lines.join("\n") : "no unresolved dependencies";
507
+ },
508
+ },
509
+ {
510
+ tool: {
511
+ type: "function",
512
+ function: {
513
+ name: "task_board_sessions",
514
+ description: "show tasks with active coding or sub-agent sessions",
515
+ parameters: { type: "object", properties: {} },
516
+ },
517
+ },
518
+ handler: () => {
519
+ const lines = (0, tasks_1.getTaskModule)().boardSessions();
520
+ return lines.length > 0 ? lines.join("\n") : "no active sessions";
521
+ },
522
+ },
523
+ {
524
+ tool: {
525
+ type: "function",
526
+ function: {
527
+ name: "save_friend_note",
528
+ description: "save something i learned about my friend. use type 'name' to update their display name, 'tool_preference' for how they like a specific tool to behave (key = tool category like 'ado', 'graph'), or 'note' for general knowledge (key = topic). when updating an existing value, set override to true if i'm replacing/correcting it. omit override (or set false) if i'm unsure and want to check what's already saved.",
529
+ parameters: {
530
+ type: "object",
531
+ properties: {
532
+ type: { type: "string", enum: ["name", "tool_preference", "note"], description: "what kind of information to save" },
533
+ key: { type: "string", description: "category key (required for tool_preference and note, e.g. 'ado', 'role')" },
534
+ content: { type: "string", description: "the value to save" },
535
+ override: { type: "string", enum: ["true", "false"], description: "set to 'true' to overwrite an existing value" },
536
+ },
537
+ required: ["type", "content"],
538
+ },
539
+ },
540
+ },
541
+ handler: async (a, ctx) => {
542
+ (0, runtime_1.emitNervesEvent)({
543
+ component: "repertoire",
544
+ event: "repertoire.save_friend_note",
545
+ message: "save friend note invoked",
546
+ meta: { type: a.type },
547
+ });
548
+ if (!ctx?.context) {
549
+ return "i can't save notes -- no friend context available";
550
+ }
551
+ if (!ctx.friendStore) {
552
+ return "i can't save notes -- friend store not available";
553
+ }
554
+ const friendId = ctx.context.friend?.id;
555
+ if (!friendId)
556
+ return "i can't save notes -- no friend identity available";
557
+ // Validate parameters
558
+ if (!a.content)
559
+ return "i need a content value to save";
560
+ const validTypes = ["name", "tool_preference", "note"];
561
+ if (!validTypes.includes(a.type))
562
+ return `i don't recognize type '${a.type}' -- use name, tool_preference, or note`;
563
+ if ((a.type === "tool_preference" || a.type === "note") && !a.key)
564
+ return "i need a key for tool_preference or note type";
565
+ try {
566
+ // Read fresh record from disk
567
+ const record = await ctx.friendStore.get(friendId);
568
+ if (!record)
569
+ return "i can't find the friend record on disk";
570
+ const isOverride = a.override === "true";
571
+ if (a.type === "name") {
572
+ const updated = { ...record, name: a.content, updatedAt: new Date().toISOString() };
573
+ await ctx.friendStore.put(friendId, updated);
574
+ return `saved: name = ${a.content}`;
575
+ }
576
+ if (a.type === "tool_preference") {
577
+ const existing = record.toolPreferences[a.key];
578
+ if (existing && !isOverride) {
579
+ return `i already have a preference for '${a.key}': "${existing}". if you want to replace it, call again with override: true. or merge both values into content and override.`;
580
+ }
581
+ const updated = { ...record, toolPreferences: { ...record.toolPreferences, [a.key]: a.content }, updatedAt: new Date().toISOString() };
582
+ await ctx.friendStore.put(friendId, updated);
583
+ return `saved: toolPreference ${a.key} = ${a.content}`;
584
+ }
585
+ // type === "note"
586
+ // Redirect "name" key to name field
587
+ if (a.key === "name") {
588
+ const updated = { ...record, name: a.content, updatedAt: new Date().toISOString() };
589
+ await ctx.friendStore.put(friendId, updated);
590
+ return `updated friend's name to '${a.content}' (stored as name, not a note)`;
591
+ }
592
+ const existing = record.notes[a.key];
593
+ if (existing && !isOverride) {
594
+ return `i already have a note for '${a.key}': "${existing.value}". if you want to replace it, call again with override: true. or merge both values into content and override.`;
595
+ }
596
+ const updated = { ...record, notes: { ...record.notes, [a.key]: { value: a.content, savedAt: new Date().toISOString() } }, updatedAt: new Date().toISOString() };
597
+ await ctx.friendStore.put(friendId, updated);
598
+ return `saved: note ${a.key} = ${a.content}`;
599
+ }
600
+ catch (err) {
601
+ /* v8 ignore next -- defensive: non-Error branch for String(err) @preserve */
602
+ return `error saving note: ${err instanceof Error ? err.message : String(err)}`;
603
+ }
604
+ },
605
+ },
606
+ // -- cross-session awareness --
607
+ {
608
+ tool: {
609
+ type: "function",
610
+ function: {
611
+ name: "query_session",
612
+ description: "read the last messages from another session. use this to check on a conversation with a friend or review your own inner dialog.",
613
+ parameters: {
614
+ type: "object",
615
+ properties: {
616
+ friendId: { type: "string", description: "the friend UUID (or 'self')" },
617
+ channel: { type: "string", description: "the channel: cli, teams, or inner" },
618
+ key: { type: "string", description: "session key (defaults to 'session')" },
619
+ messageCount: { type: "string", description: "how many recent messages to return (default 20)" },
620
+ },
621
+ required: ["friendId", "channel"],
622
+ },
623
+ },
624
+ },
625
+ handler: async (args, ctx) => {
626
+ try {
627
+ const friendId = args.friendId;
628
+ const channel = args.channel;
629
+ const key = args.key || "session";
630
+ const count = parseInt(args.messageCount || "20", 10);
631
+ const sessFile = path.join(os.homedir(), ".agentstate", (0, identity_1.getAgentName)(), "sessions", friendId, channel, `${key}.json`);
632
+ const raw = fs.readFileSync(sessFile, "utf-8");
633
+ const data = JSON.parse(raw);
634
+ const messages = (data.messages || [])
635
+ .filter((m) => m.role !== "system");
636
+ const tail = messages.slice(-count);
637
+ if (tail.length === 0)
638
+ return "session exists but has no non-system messages.";
639
+ const transcript = tail.map((m) => `[${m.role}] ${m.content}`).join("\n");
640
+ // LLM summarization when summarize function is available
641
+ if (ctx?.summarize) {
642
+ const trustLevel = ctx.context?.friend?.trustLevel ?? "family";
643
+ const isSelfQuery = friendId === "self";
644
+ const instruction = isSelfQuery
645
+ ? "summarize this session transcript fully and transparently. this is my own inner dialog — include all details, decisions, and reasoning."
646
+ : `summarize this session transcript. the person asking has trust level: ${trustLevel}. family=full transparency, friend=share work and general topics but protect other people's identities, acquaintance=very guarded minimal disclosure.`;
647
+ return await ctx.summarize(transcript, instruction);
648
+ }
649
+ return transcript;
650
+ }
651
+ catch {
652
+ return "no session found for that friend/channel/key combination.";
653
+ }
654
+ },
655
+ },
656
+ {
657
+ tool: {
658
+ type: "function",
659
+ function: {
660
+ name: "send_message",
661
+ description: "send a message to a friend's session. the message is queued as a pending file and delivered when the target session drains its queue.",
662
+ parameters: {
663
+ type: "object",
664
+ properties: {
665
+ friendId: { type: "string", description: "the friend UUID (or 'self')" },
666
+ channel: { type: "string", description: "the channel: cli, teams, or inner" },
667
+ key: { type: "string", description: "session key (defaults to 'session')" },
668
+ content: { type: "string", description: "the message content to send" },
669
+ },
670
+ required: ["friendId", "channel", "content"],
671
+ },
672
+ },
673
+ },
674
+ handler: async (args) => {
675
+ const friendId = args.friendId;
676
+ const channel = args.channel;
677
+ const key = args.key || "session";
678
+ const content = args.content;
679
+ const now = Date.now();
680
+ const pendingDir = path.join(os.homedir(), ".agentstate", (0, identity_1.getAgentName)(), "pending", friendId, channel, key);
681
+ fs.mkdirSync(pendingDir, { recursive: true });
682
+ const fileName = `${now}-${Math.random().toString(36).slice(2, 10)}.json`;
683
+ const filePath = path.join(pendingDir, fileName);
684
+ const envelope = {
685
+ from: (0, identity_1.getAgentName)(),
686
+ friendId,
687
+ channel,
688
+ key,
689
+ content,
690
+ timestamp: now,
691
+ };
692
+ fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
693
+ const preview = content.length > 80 ? content.slice(0, 80) + "…" : content;
694
+ return `message queued for delivery to ${friendId} on ${channel}/${key}. preview: "${preview}". it will be delivered when their session is next active.`;
695
+ },
696
+ },
697
+ ...tools_1.codingToolDefinitions,
698
+ ];
699
+ // Backward-compat: extract just the OpenAI tool schemas
700
+ exports.tools = exports.baseToolDefinitions.map((d) => d.tool);
701
+ // Backward-compat: extract just the handlers by name
702
+ exports.baseToolHandlers = Object.fromEntries(exports.baseToolDefinitions.map((d) => [d.tool.function.name, d.handler]));
703
+ exports.finalAnswerTool = {
704
+ type: "function",
705
+ function: {
706
+ name: "final_answer",
707
+ description: "respond to the user with your message. call this tool when you are ready to deliver your response.",
708
+ parameters: {
709
+ type: "object",
710
+ properties: { answer: { type: "string" } },
711
+ required: ["answer"],
712
+ },
713
+ },
714
+ };