@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,523 @@
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.MarkdownStreamer = exports.InputController = exports.Spinner = void 0;
37
+ exports.handleSigint = handleSigint;
38
+ exports.addHistory = addHistory;
39
+ exports.renderMarkdown = renderMarkdown;
40
+ exports.createCliCallbacks = createCliCallbacks;
41
+ exports.main = main;
42
+ const readline = __importStar(require("readline"));
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ const core_1 = require("../heart/core");
46
+ const prompt_1 = require("../mind/prompt");
47
+ const phrases_1 = require("../mind/phrases");
48
+ const format_1 = require("../mind/format");
49
+ const config_1 = require("../heart/config");
50
+ const context_1 = require("../mind/context");
51
+ const commands_1 = require("./commands");
52
+ const identity_1 = require("../heart/identity");
53
+ const nerves_1 = require("../nerves");
54
+ const store_file_1 = require("../mind/friends/store-file");
55
+ const resolver_1 = require("../mind/friends/resolver");
56
+ const tokens_1 = require("../mind/friends/tokens");
57
+ const cli_logging_1 = require("../nerves/cli-logging");
58
+ const runtime_1 = require("../nerves/runtime");
59
+ const trust_gate_1 = require("./trust-gate");
60
+ // spinner that only touches stderr, cleans up after itself
61
+ // exported for direct testability (stop-without-start branch)
62
+ class Spinner {
63
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
64
+ i = 0;
65
+ iv = null;
66
+ piv = null;
67
+ msg = "";
68
+ phrases = null;
69
+ lastPhrase = "";
70
+ constructor(m = "working", phrases) {
71
+ this.msg = m;
72
+ if (phrases && phrases.length > 0)
73
+ this.phrases = phrases;
74
+ }
75
+ start() {
76
+ process.stderr.write("\r\x1b[K");
77
+ this.spin();
78
+ this.iv = setInterval(() => this.spin(), 80);
79
+ if (this.phrases) {
80
+ this.piv = setInterval(() => this.rotatePhrase(), 1500);
81
+ }
82
+ }
83
+ spin() {
84
+ process.stderr.write(`\r${this.frames[this.i]} ${this.msg}... `);
85
+ this.i = (this.i + 1) % this.frames.length;
86
+ }
87
+ rotatePhrase() {
88
+ const next = (0, phrases_1.pickPhrase)(this.phrases, this.lastPhrase);
89
+ this.lastPhrase = next;
90
+ this.msg = next;
91
+ }
92
+ stop(ok) {
93
+ if (this.iv) {
94
+ clearInterval(this.iv);
95
+ this.iv = null;
96
+ }
97
+ if (this.piv) {
98
+ clearInterval(this.piv);
99
+ this.piv = null;
100
+ }
101
+ process.stderr.write("\r\x1b[K");
102
+ /* v8 ignore next -- ok parameter currently unused by callers @preserve */
103
+ if (ok)
104
+ process.stderr.write(`\x1b[32m\u2713\x1b[0m ${ok}\n`);
105
+ }
106
+ fail(msg) {
107
+ this.stop();
108
+ process.stderr.write(`\x1b[31m\u2717\x1b[0m ${msg}\n`);
109
+ }
110
+ }
111
+ exports.Spinner = Spinner;
112
+ // Input controller: pauses readline during model/tool execution.
113
+ // Does NOT touch raw mode — readline with terminal:true manages raw mode
114
+ // internally. Touching it causes ^C to be echoed by the terminal driver.
115
+ // During suppress, we consume stdin data ourselves to swallow stray
116
+ // keystrokes and catch Ctrl-C (0x03) for interrupt.
117
+ class InputController {
118
+ rl;
119
+ suppressed = false;
120
+ dataHandler = null;
121
+ onInterrupt = null;
122
+ constructor(rl) {
123
+ this.rl = rl;
124
+ }
125
+ suppress(onInterrupt) {
126
+ if (this.suppressed)
127
+ return;
128
+ this.suppressed = true;
129
+ this.onInterrupt = onInterrupt || null;
130
+ this.rl.pause();
131
+ // Consume stdin to swallow keystrokes; catch Ctrl-C (0x03)
132
+ this.dataHandler = (data) => {
133
+ if (data[0] === 0x03 && this.onInterrupt) {
134
+ this.onInterrupt();
135
+ }
136
+ // All other input is swallowed
137
+ };
138
+ process.stdin.on("data", this.dataHandler);
139
+ // rl.pause() paused stdin — resume it so our data handler receives keypresses
140
+ process.stdin.resume();
141
+ }
142
+ restore() {
143
+ if (!this.suppressed)
144
+ return;
145
+ this.suppressed = false;
146
+ if (this.dataHandler) {
147
+ process.stdin.removeListener("data", this.dataHandler);
148
+ this.dataHandler = null;
149
+ }
150
+ this.onInterrupt = null;
151
+ this.rl.resume();
152
+ }
153
+ }
154
+ exports.InputController = InputController;
155
+ // Ctrl-C handling: returns "clear" if input was non-empty, "warn" on first empty press, "exit" on second
156
+ let _ctrlCWarned = false;
157
+ function handleSigint(_rl, currentInput) {
158
+ if (currentInput.length > 0) {
159
+ _ctrlCWarned = false;
160
+ return "clear";
161
+ }
162
+ if (_ctrlCWarned) {
163
+ _ctrlCWarned = false;
164
+ return "exit";
165
+ }
166
+ _ctrlCWarned = true;
167
+ return "warn";
168
+ }
169
+ // History management
170
+ function addHistory(history, entry) {
171
+ if (!entry.trim())
172
+ return;
173
+ if (history.length > 0 && history[history.length - 1] === entry)
174
+ return;
175
+ history.push(entry);
176
+ }
177
+ function renderMarkdown(text) {
178
+ const placeholders = [];
179
+ // Protect fenced code blocks
180
+ let result = text.replace(/```(?:\w*\n)?([\s\S]*?)```/g, (_m, code) => {
181
+ const idx = placeholders.length;
182
+ placeholders.push(`\x1b[2m${code.replace(/\n$/, "")}\x1b[22m`);
183
+ return `\x00${idx}\x00`;
184
+ });
185
+ // Protect inline code
186
+ result = result.replace(/`([^`\n]+)`/g, (_m, code) => {
187
+ const idx = placeholders.length;
188
+ placeholders.push(`\x1b[36m${code}\x1b[39m`);
189
+ return `\x00${idx}\x00`;
190
+ });
191
+ // Bold
192
+ result = result.replace(/\*\*(.+?)\*\*/g, "\x1b[1m$1\x1b[22m");
193
+ // Italic (avoid matching inside bold remnants)
194
+ result = result.replace(/(?<!\*)\*(.+?)\*(?!\*)/g, "\x1b[3m$1\x1b[23m");
195
+ // Restore placeholders
196
+ result = result.replace(/\x00(\d+)\x00/g, (_m, idx) => placeholders[parseInt(idx)]);
197
+ return result;
198
+ }
199
+ // Ordered longest-first so we match ``` before ` and ** before *
200
+ const MARKERS = ["```", "**", "*", "`"];
201
+ class MarkdownStreamer {
202
+ buf = "";
203
+ openMarker = null;
204
+ push(text) {
205
+ this.buf += text;
206
+ return this.drain(false);
207
+ }
208
+ flush() {
209
+ return this.drain(true);
210
+ }
211
+ reset() {
212
+ this.buf = "";
213
+ this.openMarker = null;
214
+ }
215
+ drain(final) {
216
+ let out = "";
217
+ while (this.buf.length > 0) {
218
+ if (this.openMarker) {
219
+ const closeIdx = this.buf.indexOf(this.openMarker);
220
+ if (closeIdx !== -1) {
221
+ const segment = this.openMarker + this.buf.slice(0, closeIdx + this.openMarker.length);
222
+ out += renderMarkdown(segment);
223
+ this.buf = this.buf.slice(closeIdx + this.openMarker.length);
224
+ this.openMarker = null;
225
+ continue;
226
+ }
227
+ if (final) {
228
+ out += renderMarkdown(this.openMarker + this.buf);
229
+ this.buf = "";
230
+ this.openMarker = null;
231
+ }
232
+ break;
233
+ }
234
+ // Normal mode — look for the next opening marker
235
+ let earliest = -1;
236
+ let matched = null;
237
+ for (const m of MARKERS) {
238
+ const idx = this.buf.indexOf(m);
239
+ if (idx !== -1 && (earliest === -1 || idx < earliest)) {
240
+ earliest = idx;
241
+ matched = m;
242
+ }
243
+ }
244
+ if (matched !== null && earliest !== -1) {
245
+ // If the tail from the match to end-of-buffer is a proper prefix of a
246
+ // longer marker, hold it back rather than consuming it prematurely.
247
+ // E.g. a trailing `*` could be the start of `**`, trailing `` ` `` could be `` ``` ``.
248
+ const tail = this.buf.slice(earliest);
249
+ if (!final && MARKERS.some(m => m.length > tail.length && m.startsWith(tail))) {
250
+ if (earliest > 0) {
251
+ out += renderMarkdown(this.buf.slice(0, earliest));
252
+ this.buf = this.buf.slice(earliest);
253
+ }
254
+ break;
255
+ }
256
+ if (earliest > 0) {
257
+ out += renderMarkdown(this.buf.slice(0, earliest));
258
+ }
259
+ this.buf = this.buf.slice(earliest + matched.length);
260
+ this.openMarker = matched;
261
+ continue;
262
+ }
263
+ out += renderMarkdown(this.buf);
264
+ this.buf = "";
265
+ break;
266
+ }
267
+ return out;
268
+ }
269
+ }
270
+ exports.MarkdownStreamer = MarkdownStreamer;
271
+ function createCliCallbacks() {
272
+ (0, runtime_1.emitNervesEvent)({
273
+ component: "senses",
274
+ event: "senses.cli_callbacks_created",
275
+ message: "cli callbacks created",
276
+ meta: {},
277
+ });
278
+ let currentSpinner = null;
279
+ let hadReasoning = false;
280
+ let hadToolRun = false;
281
+ let textDirty = false; // true when text/reasoning was written without a trailing newline
282
+ const streamer = new MarkdownStreamer();
283
+ return {
284
+ onModelStart: () => {
285
+ currentSpinner?.stop();
286
+ currentSpinner = null;
287
+ hadReasoning = false;
288
+ textDirty = false;
289
+ streamer.reset();
290
+ const phrases = (0, phrases_1.getPhrases)();
291
+ const pool = hadToolRun ? phrases.followup : phrases.thinking;
292
+ const first = (0, phrases_1.pickPhrase)(pool);
293
+ currentSpinner = new Spinner(first, pool);
294
+ currentSpinner.start();
295
+ },
296
+ onModelStreamStart: () => {
297
+ currentSpinner?.stop();
298
+ currentSpinner = null;
299
+ },
300
+ onTextChunk: (text) => {
301
+ if (hadReasoning) {
302
+ process.stdout.write("\n\n");
303
+ hadReasoning = false;
304
+ }
305
+ const rendered = streamer.push(text);
306
+ if (rendered)
307
+ process.stdout.write(rendered);
308
+ textDirty = text.length > 0 && !text.endsWith("\n");
309
+ },
310
+ onReasoningChunk: (text) => {
311
+ hadReasoning = true;
312
+ process.stdout.write(`\x1b[2m${text}\x1b[0m`);
313
+ textDirty = text.length > 0 && !text.endsWith("\n");
314
+ },
315
+ onToolStart: (_name, _args) => {
316
+ // Stop the model-start spinner: when the model returns only tool calls
317
+ // (no content/reasoning), onModelStreamStart never fires, so the old
318
+ // spinner's intervals would leak.
319
+ currentSpinner?.stop();
320
+ // Ensure the spinner starts on a fresh line so it doesn't overwrite
321
+ // the last line of text/reasoning output via \r\x1b[K
322
+ if (textDirty) {
323
+ process.stdout.write("\n");
324
+ textDirty = false;
325
+ }
326
+ const toolPhrases = (0, phrases_1.getPhrases)().tool;
327
+ const first = (0, phrases_1.pickPhrase)(toolPhrases);
328
+ currentSpinner = new Spinner(first, toolPhrases);
329
+ currentSpinner.start();
330
+ hadToolRun = true;
331
+ },
332
+ onToolEnd: (name, argSummary, success) => {
333
+ currentSpinner?.stop();
334
+ currentSpinner = null;
335
+ const msg = (0, format_1.formatToolResult)(name, argSummary, success);
336
+ const color = success ? "\x1b[32m" : "\x1b[31m";
337
+ process.stderr.write(`${color}${msg}\x1b[0m\n`);
338
+ },
339
+ onError: (error, severity) => {
340
+ if (severity === "transient") {
341
+ currentSpinner?.fail(error.message);
342
+ currentSpinner = null;
343
+ }
344
+ else {
345
+ currentSpinner?.stop();
346
+ currentSpinner = null;
347
+ process.stderr.write(`\x1b[31m${(0, format_1.formatError)(error)}\x1b[0m\n`);
348
+ }
349
+ },
350
+ onKick: () => {
351
+ currentSpinner?.stop();
352
+ currentSpinner = null;
353
+ if (textDirty) {
354
+ process.stdout.write("\n");
355
+ textDirty = false;
356
+ }
357
+ process.stderr.write(`\x1b[33m${(0, format_1.formatKick)()}\x1b[0m\n`);
358
+ },
359
+ flushMarkdown: () => {
360
+ currentSpinner?.stop();
361
+ currentSpinner = null;
362
+ const remaining = streamer.flush();
363
+ if (remaining)
364
+ process.stdout.write(remaining);
365
+ },
366
+ };
367
+ }
368
+ async function main() {
369
+ // Fail fast if provider is misconfigured (triggers human-readable error + exit)
370
+ (0, core_1.getProvider)();
371
+ const registry = (0, commands_1.createCommandRegistry)();
372
+ (0, commands_1.registerDefaultCommands)(registry);
373
+ // Resolve context kernel (identity + channel) for CLI
374
+ const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
375
+ const friendStore = new store_file_1.FileFriendStore(friendsPath);
376
+ const username = os.userInfo().username;
377
+ const hostname = os.hostname();
378
+ const localExternalId = `${username}@${hostname}`;
379
+ const resolver = new resolver_1.FriendResolver(friendStore, {
380
+ provider: "local",
381
+ externalId: localExternalId,
382
+ displayName: username,
383
+ channel: "cli",
384
+ });
385
+ const resolvedContext = await resolver.resolve();
386
+ const cliToolContext = {
387
+ /* v8 ignore next -- CLI has no OAuth sign-in; this no-op satisfies the interface @preserve */
388
+ signin: async () => undefined,
389
+ context: resolvedContext,
390
+ friendStore,
391
+ };
392
+ const friendId = resolvedContext.friend.id;
393
+ (0, cli_logging_1.configureCliRuntimeLogger)(friendId);
394
+ const sessPath = (0, config_1.sessionPath)(friendId, "cli", "session");
395
+ // Load existing session or start fresh
396
+ const existing = (0, context_1.loadSession)(sessPath);
397
+ const messages = existing?.messages && existing.messages.length > 0
398
+ ? existing.messages
399
+ : [{ role: "system", content: await (0, prompt_1.buildSystem)("cli", undefined, resolvedContext) }];
400
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
401
+ const ctrl = new InputController(rl);
402
+ let currentAbort = null;
403
+ const history = [];
404
+ let closed = false;
405
+ rl.on("close", () => { closed = true; });
406
+ // eslint-disable-next-line no-console -- terminal UX: startup banner
407
+ console.log(`\n${(0, identity_1.getAgentName)()} (type /commands for help)\n`);
408
+ const cliCallbacks = createCliCallbacks();
409
+ process.stdout.write("\x1b[36m> \x1b[0m");
410
+ // Ctrl-C at the input prompt: clear line or warn/exit
411
+ // readline with terminal:true catches Ctrl-C in raw mode (no ^C echo)
412
+ rl.on("SIGINT", () => {
413
+ const rlInt = rl;
414
+ const currentLine = rlInt.line || "";
415
+ const result = handleSigint(rl, currentLine);
416
+ if (result === "clear") {
417
+ rlInt.line = "";
418
+ rlInt.cursor = 0;
419
+ process.stdout.write("\r\x1b[K\x1b[36m> \x1b[0m");
420
+ }
421
+ else if (result === "warn") {
422
+ rlInt.line = "";
423
+ rlInt.cursor = 0;
424
+ process.stdout.write("\r\x1b[K");
425
+ process.stderr.write("press Ctrl-C again to exit\n");
426
+ process.stdout.write("\x1b[36m> \x1b[0m");
427
+ }
428
+ else {
429
+ rl.close();
430
+ }
431
+ });
432
+ try {
433
+ for await (const input of rl) {
434
+ if (closed)
435
+ break;
436
+ if (!input.trim()) {
437
+ process.stdout.write("\x1b[36m> \x1b[0m");
438
+ continue;
439
+ }
440
+ const trustGate = (0, trust_gate_1.enforceTrustGate)({
441
+ friend: resolvedContext.friend,
442
+ provider: "local",
443
+ externalId: localExternalId,
444
+ channel: "cli",
445
+ });
446
+ if (!trustGate.allowed) {
447
+ if (trustGate.reason === "stranger_first_reply") {
448
+ process.stdout.write(`${trustGate.autoReply}\n`);
449
+ }
450
+ if (closed)
451
+ break;
452
+ process.stdout.write("\x1b[36m> \x1b[0m");
453
+ continue;
454
+ }
455
+ // Check for slash commands
456
+ const parsed = (0, commands_1.parseSlashCommand)(input);
457
+ if (parsed) {
458
+ const dispatchResult = registry.dispatch(parsed.command, { channel: "cli" });
459
+ if (dispatchResult.handled && dispatchResult.result) {
460
+ if (dispatchResult.result.action === "exit") {
461
+ break;
462
+ }
463
+ else if (dispatchResult.result.action === "new") {
464
+ messages.length = 0;
465
+ messages.push({ role: "system", content: await (0, prompt_1.buildSystem)("cli") });
466
+ (0, context_1.deleteSession)(sessPath);
467
+ // eslint-disable-next-line no-console -- terminal UX: session cleared
468
+ console.log("session cleared");
469
+ process.stdout.write("\x1b[36m> \x1b[0m");
470
+ continue;
471
+ }
472
+ else if (dispatchResult.result.action === "response") {
473
+ // eslint-disable-next-line no-console -- terminal UX: command dispatch result
474
+ console.log(dispatchResult.result.message || "");
475
+ process.stdout.write("\x1b[36m> \x1b[0m");
476
+ continue;
477
+ }
478
+ }
479
+ }
480
+ // Re-style the echoed input line (readline terminal:true echoes it as "> input")
481
+ // Calculate terminal rows the echo occupied (prompt "> " + input, wrapped)
482
+ const cols = process.stdout.columns || 80;
483
+ const echoLen = 2 + input.length; // "> " prefix + input
484
+ const rows = Math.ceil(echoLen / cols);
485
+ process.stdout.write(`\x1b[${rows}A\x1b[K` + `\x1b[1m> ${input}\x1b[0m\n\n`);
486
+ messages.push({ role: "user", content: input });
487
+ addHistory(history, input);
488
+ currentAbort = new AbortController();
489
+ const traceId = (0, nerves_1.createTraceId)();
490
+ ctrl.suppress(() => currentAbort.abort());
491
+ let result;
492
+ try {
493
+ result = await (0, core_1.runAgent)(messages, cliCallbacks, "cli", currentAbort.signal, {
494
+ toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
495
+ toolContext: cliToolContext,
496
+ traceId,
497
+ });
498
+ }
499
+ catch {
500
+ // AbortError — silently return to prompt
501
+ }
502
+ cliCallbacks.flushMarkdown();
503
+ ctrl.restore();
504
+ currentAbort = null;
505
+ // Safety net: never silently swallow an empty response
506
+ const lastMsg = messages[messages.length - 1];
507
+ if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
508
+ process.stderr.write("\x1b[33m(empty response)\x1b[0m\n");
509
+ }
510
+ process.stdout.write("\n\n");
511
+ (0, context_1.postTurn)(messages, sessPath, result?.usage);
512
+ await (0, tokens_1.accumulateFriendTokens)(friendStore, resolvedContext.friend.id, result?.usage);
513
+ if (closed)
514
+ break;
515
+ process.stdout.write("\x1b[36m> \x1b[0m");
516
+ }
517
+ }
518
+ finally {
519
+ rl.close();
520
+ // eslint-disable-next-line no-console -- terminal UX: goodbye
521
+ console.log("bye");
522
+ }
523
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCommandRegistry = createCommandRegistry;
4
+ exports.getToolChoiceRequired = getToolChoiceRequired;
5
+ exports.resetToolChoiceRequired = resetToolChoiceRequired;
6
+ exports.registerDefaultCommands = registerDefaultCommands;
7
+ exports.parseSlashCommand = parseSlashCommand;
8
+ const identity_1 = require("../heart/identity");
9
+ const runtime_1 = require("../nerves/runtime");
10
+ function createCommandRegistry() {
11
+ const commands = new Map();
12
+ return {
13
+ register(cmd) {
14
+ commands.set(cmd.name, cmd);
15
+ },
16
+ get(name) {
17
+ return commands.get(name);
18
+ },
19
+ list(channel) {
20
+ return [...commands.values()].filter((c) => c.channels.includes(channel));
21
+ },
22
+ dispatch(name, ctx) {
23
+ const cmd = commands.get(name);
24
+ if (!cmd)
25
+ return { handled: false };
26
+ return { handled: true, result: cmd.handler(ctx) };
27
+ },
28
+ };
29
+ }
30
+ // Module-level toggle for tool-required mode
31
+ let _toolChoiceRequired = false;
32
+ function getToolChoiceRequired() {
33
+ return _toolChoiceRequired;
34
+ }
35
+ function resetToolChoiceRequired() {
36
+ _toolChoiceRequired = false;
37
+ }
38
+ function registerDefaultCommands(registry) {
39
+ (0, runtime_1.emitNervesEvent)({
40
+ event: "repertoire.load_start",
41
+ component: "repertoire",
42
+ message: "registering default commands",
43
+ meta: {},
44
+ });
45
+ registry.register({
46
+ name: "exit",
47
+ description: `quit ${(0, identity_1.getAgentName)()}`,
48
+ channels: ["cli"],
49
+ handler: () => ({ action: "exit" }),
50
+ });
51
+ registry.register({
52
+ name: "new",
53
+ description: "start a new conversation",
54
+ channels: ["cli", "teams"],
55
+ handler: () => ({ action: "new" }),
56
+ });
57
+ registry.register({
58
+ name: "commands",
59
+ description: "list available commands",
60
+ channels: ["cli", "teams"],
61
+ handler: (ctx) => {
62
+ const cmds = registry.list(ctx.channel);
63
+ const lines = cmds.map((c) => `/${c.name} - ${c.description}`);
64
+ return { action: "response", message: lines.join("\n") };
65
+ },
66
+ });
67
+ registry.register({
68
+ name: "tool-required",
69
+ description: "toggle tool_choice required mode (forces tool calls)",
70
+ channels: ["cli"],
71
+ handler: () => {
72
+ _toolChoiceRequired = !_toolChoiceRequired;
73
+ return { action: "response", message: `tool-required mode: ${_toolChoiceRequired ? "ON" : "OFF"}` };
74
+ },
75
+ });
76
+ (0, runtime_1.emitNervesEvent)({
77
+ event: "repertoire.load_end",
78
+ component: "repertoire",
79
+ message: "registered default commands",
80
+ meta: {},
81
+ });
82
+ }
83
+ function parseSlashCommand(input) {
84
+ const trimmed = input.trim();
85
+ if (!trimmed.startsWith("/"))
86
+ return null;
87
+ // Reject // (double slash)
88
+ if (trimmed.startsWith("//"))
89
+ return null;
90
+ const rest = trimmed.slice(1);
91
+ if (!rest)
92
+ return null;
93
+ const spaceIdx = rest.indexOf(" ");
94
+ if (spaceIdx === -1) {
95
+ return { command: rest.toLowerCase(), args: "" };
96
+ }
97
+ return { command: rest.slice(0, spaceIdx).toLowerCase(), args: rest.slice(spaceIdx + 1) };
98
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createInnerDialogWorker = createInnerDialogWorker;
4
+ exports.startInnerDialogWorker = startInnerDialogWorker;
5
+ const inner_dialog_1 = require("./inner-dialog");
6
+ const runtime_1 = require("../nerves/runtime");
7
+ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runInnerDialogTurn)(options)) {
8
+ let running = false;
9
+ async function run(reason) {
10
+ if (running)
11
+ return;
12
+ running = true;
13
+ try {
14
+ await runTurn({ reason });
15
+ }
16
+ catch (error) {
17
+ (0, runtime_1.emitNervesEvent)({
18
+ level: "error",
19
+ component: "senses",
20
+ event: "senses.inner_dialog_worker_error",
21
+ message: "inner dialog worker turn failed",
22
+ meta: {
23
+ reason,
24
+ error: error instanceof Error ? error.message : String(error),
25
+ },
26
+ });
27
+ }
28
+ finally {
29
+ running = false;
30
+ }
31
+ }
32
+ async function handleMessage(message) {
33
+ if (!message || typeof message !== "object")
34
+ return;
35
+ const maybeMessage = message;
36
+ if (maybeMessage.type === "heartbeat") {
37
+ await run("heartbeat");
38
+ return;
39
+ }
40
+ if (maybeMessage.type === "poke" ||
41
+ maybeMessage.type === "chat" ||
42
+ maybeMessage.type === "message") {
43
+ await run("instinct");
44
+ return;
45
+ }
46
+ if (maybeMessage.type === "shutdown") {
47
+ process.exit(0);
48
+ }
49
+ }
50
+ return { run, handleMessage };
51
+ }
52
+ async function startInnerDialogWorker() {
53
+ const worker = createInnerDialogWorker();
54
+ process.on("message", (message) => {
55
+ void worker.handleMessage(message);
56
+ });
57
+ process.on("disconnect", () => {
58
+ process.exit(0);
59
+ });
60
+ await worker.run("boot");
61
+ }