@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.
- package/AdoptionSpecialist.ouro/agent.json +20 -0
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +22 -0
- package/AdoptionSpecialist.ouro/psyche/identities/basilisk.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/jafar.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/jormungandr.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/kaa.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/medusa.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/nagini.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/ouroboros.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/python.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/quetzalcoatl.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/sir-hiss.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/the-serpent.md +31 -0
- package/AdoptionSpecialist.ouro/psyche/identities/the-snake.md +31 -0
- package/README.md +224 -6
- package/dist/heart/agent-entry.js +17 -0
- package/dist/heart/api-error.js +34 -0
- package/dist/heart/config.js +296 -0
- package/dist/heart/core.js +485 -0
- package/dist/heart/daemon/daemon-cli.js +626 -0
- package/dist/heart/daemon/daemon-entry.js +74 -0
- package/dist/heart/daemon/daemon.js +310 -0
- package/dist/heart/daemon/hatch-flow.js +284 -0
- package/dist/heart/daemon/hatch-specialist.js +107 -0
- package/dist/heart/daemon/health-monitor.js +79 -0
- package/dist/heart/daemon/message-router.js +98 -0
- package/dist/heart/daemon/ouro-bot-entry.js +23 -0
- package/dist/heart/daemon/ouro-bot-wrapper.js +90 -0
- package/dist/heart/daemon/ouro-entry.js +23 -0
- package/dist/heart/daemon/ouro-uti.js +212 -0
- package/dist/heart/daemon/process-manager.js +220 -0
- package/dist/heart/daemon/runtime-logging.js +98 -0
- package/dist/heart/daemon/subagent-installer.js +125 -0
- package/dist/heart/daemon/task-scheduler.js +237 -0
- package/dist/heart/harness.js +26 -0
- package/dist/heart/identity.js +270 -0
- package/dist/heart/kicks.js +144 -0
- package/dist/heart/primitives.js +4 -0
- package/dist/heart/providers/anthropic.js +329 -0
- package/dist/heart/providers/azure.js +66 -0
- package/dist/heart/providers/minimax.js +53 -0
- package/dist/heart/providers/openai-codex.js +162 -0
- package/dist/heart/streaming.js +412 -0
- package/dist/heart/turn-coordinator.js +62 -0
- package/dist/inner-worker-entry.js +4 -0
- package/dist/mind/associative-recall.js +176 -0
- package/dist/mind/bundle-manifest.js +118 -0
- package/dist/mind/context.js +218 -0
- package/dist/mind/first-impressions.js +43 -0
- package/dist/mind/format.js +56 -0
- package/dist/mind/friends/channel.js +41 -0
- package/dist/mind/friends/resolver.js +84 -0
- package/dist/mind/friends/store-file.js +171 -0
- package/dist/mind/friends/store.js +4 -0
- package/dist/mind/friends/tokens.js +26 -0
- package/dist/mind/friends/types.js +21 -0
- package/dist/mind/memory.js +326 -0
- package/dist/mind/phrases.js +43 -0
- package/dist/mind/prompt.js +254 -0
- package/dist/mind/token-estimate.js +119 -0
- package/dist/nerves/cli-logging.js +31 -0
- package/dist/nerves/coverage/audit-rules.js +81 -0
- package/dist/nerves/coverage/audit.js +200 -0
- package/dist/nerves/coverage/cli-main.js +5 -0
- package/dist/nerves/coverage/cli.js +51 -0
- package/dist/nerves/coverage/contract.js +23 -0
- package/dist/nerves/coverage/file-completeness.js +46 -0
- package/dist/nerves/coverage/run-artifacts.js +77 -0
- package/dist/nerves/coverage/source-scanner.js +34 -0
- package/dist/nerves/index.js +152 -0
- package/dist/nerves/runtime.js +38 -0
- package/dist/repertoire/ado-client.js +211 -0
- package/dist/repertoire/ado-context.js +73 -0
- package/dist/repertoire/ado-semantic.js +841 -0
- package/dist/repertoire/ado-templates.js +146 -0
- package/dist/repertoire/coding/index.js +36 -0
- package/dist/repertoire/coding/manager.js +489 -0
- package/dist/repertoire/coding/monitor.js +60 -0
- package/dist/repertoire/coding/reporter.js +45 -0
- package/dist/repertoire/coding/spawner.js +102 -0
- package/dist/repertoire/coding/tools.js +167 -0
- package/dist/repertoire/coding/types.js +2 -0
- package/dist/repertoire/data/ado-endpoints.json +122 -0
- package/dist/repertoire/data/graph-endpoints.json +212 -0
- package/dist/repertoire/github-client.js +64 -0
- package/dist/repertoire/graph-client.js +118 -0
- package/dist/repertoire/skills.js +156 -0
- package/dist/repertoire/tasks/board.js +122 -0
- package/dist/repertoire/tasks/index.js +210 -0
- package/dist/repertoire/tasks/lifecycle.js +80 -0
- package/dist/repertoire/tasks/middleware.js +65 -0
- package/dist/repertoire/tasks/parser.js +173 -0
- package/dist/repertoire/tasks/scanner.js +132 -0
- package/dist/repertoire/tasks/transitions.js +145 -0
- package/dist/repertoire/tasks/types.js +2 -0
- package/dist/repertoire/tools-base.js +622 -0
- package/dist/repertoire/tools-github.js +53 -0
- package/dist/repertoire/tools-teams.js +308 -0
- package/dist/repertoire/tools.js +199 -0
- package/dist/senses/cli-entry.js +15 -0
- package/dist/senses/cli.js +523 -0
- package/dist/senses/commands.js +98 -0
- package/dist/senses/inner-dialog-worker.js +61 -0
- package/dist/senses/inner-dialog.js +216 -0
- package/dist/senses/teams-entry.js +15 -0
- package/dist/senses/teams.js +695 -0
- package/dist/senses/trust-gate.js +150 -0
- package/package.json +34 -11
- package/subagents/README.md +71 -0
- package/subagents/work-doer.md +233 -0
- package/subagents/work-merger.md +593 -0
- package/subagents/work-planner.md +373 -0
- 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
|
+
}
|