@ouro.bot/cli 0.1.0-alpha.13 → 0.1.0-alpha.131
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/psyche/SOUL.md +2 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/changelog.json +814 -0
- package/dist/heart/active-work.js +622 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +105 -0
- package/dist/heart/config.js +66 -21
- package/dist/heart/core.js +518 -100
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +457 -0
- package/dist/heart/daemon/daemon-cli.js +1516 -195
- package/dist/heart/daemon/daemon-entry.js +43 -2
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +261 -1
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -72
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-path-installer.js +57 -29
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +50 -2
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +2 -2
- package/dist/heart/daemon/specialist-prompt.js +7 -4
- package/dist/heart/daemon/specialist-tools.js +52 -3
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +64 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +197 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +159 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +195 -34
- package/dist/heart/providers/azure.js +115 -9
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +49 -14
- package/dist/heart/safe-workspace.js +381 -0
- package/dist/heart/session-activity.js +173 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/streaming.js +108 -24
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/tool-loop.js +194 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +12 -0
- package/dist/mind/context.js +60 -14
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +456 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/index.js +12 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +301 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +210 -4
- package/dist/repertoire/coding/spawner.js +39 -9
- package/dist/repertoire/coding/tools.js +171 -4
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +198 -0
- package/dist/repertoire/skills.js +3 -26
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +925 -250
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +106 -53
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +915 -45
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +374 -131
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +388 -83
- package/dist/senses/pipeline.js +444 -0
- package/dist/senses/teams.js +607 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +9 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
package/dist/senses/cli.js
CHANGED
|
@@ -33,11 +33,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.MarkdownStreamer = exports.InputController = exports.Spinner = void 0;
|
|
36
|
+
exports.MarkdownStreamer = exports.InputController = exports.Spinner = exports.StreamingWordWrapper = exports.wrapCliText = exports.formatEchoedInputSummary = void 0;
|
|
37
|
+
exports.formatPendingPrefix = formatPendingPrefix;
|
|
38
|
+
exports.getCliContinuityIngressTexts = getCliContinuityIngressTexts;
|
|
39
|
+
exports.writeCliAsyncAssistantMessage = writeCliAsyncAssistantMessage;
|
|
40
|
+
exports.pauseActiveSpinner = pauseActiveSpinner;
|
|
41
|
+
exports.resumeActiveSpinner = resumeActiveSpinner;
|
|
42
|
+
exports.setActiveSpinner = setActiveSpinner;
|
|
37
43
|
exports.handleSigint = handleSigint;
|
|
38
44
|
exports.addHistory = addHistory;
|
|
39
45
|
exports.renderMarkdown = renderMarkdown;
|
|
40
46
|
exports.createCliCallbacks = createCliCallbacks;
|
|
47
|
+
exports.createDebouncedLines = createDebouncedLines;
|
|
41
48
|
exports.runCliSession = runCliSession;
|
|
42
49
|
exports.main = main;
|
|
43
50
|
const readline = __importStar(require("readline"));
|
|
@@ -50,9 +57,9 @@ const format_1 = require("../mind/format");
|
|
|
50
57
|
const config_1 = require("../heart/config");
|
|
51
58
|
const context_1 = require("../mind/context");
|
|
52
59
|
const pending_1 = require("../mind/pending");
|
|
53
|
-
const prompt_refresh_1 = require("../mind/prompt-refresh");
|
|
54
60
|
const commands_1 = require("./commands");
|
|
55
61
|
const identity_1 = require("../heart/identity");
|
|
62
|
+
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
56
63
|
const nerves_1 = require("../nerves");
|
|
57
64
|
const store_file_1 = require("../mind/friends/store-file");
|
|
58
65
|
const resolver_1 = require("../mind/friends/resolver");
|
|
@@ -60,7 +67,57 @@ const tokens_1 = require("../mind/friends/tokens");
|
|
|
60
67
|
const cli_logging_1 = require("../nerves/cli-logging");
|
|
61
68
|
const runtime_1 = require("../nerves/runtime");
|
|
62
69
|
const trust_gate_1 = require("./trust-gate");
|
|
70
|
+
const pipeline_1 = require("./pipeline");
|
|
71
|
+
const channel_1 = require("../mind/friends/channel");
|
|
63
72
|
const session_lock_1 = require("./session-lock");
|
|
73
|
+
const update_hooks_1 = require("../heart/daemon/update-hooks");
|
|
74
|
+
const bundle_meta_1 = require("../heart/daemon/hooks/bundle-meta");
|
|
75
|
+
const bundle_manifest_1 = require("../mind/bundle-manifest");
|
|
76
|
+
const cli_layout_1 = require("./cli-layout");
|
|
77
|
+
var cli_layout_2 = require("./cli-layout");
|
|
78
|
+
Object.defineProperty(exports, "formatEchoedInputSummary", { enumerable: true, get: function () { return cli_layout_2.formatEchoedInputSummary; } });
|
|
79
|
+
Object.defineProperty(exports, "wrapCliText", { enumerable: true, get: function () { return cli_layout_2.wrapCliText; } });
|
|
80
|
+
Object.defineProperty(exports, "StreamingWordWrapper", { enumerable: true, get: function () { return cli_layout_2.StreamingWordWrapper; } });
|
|
81
|
+
/**
|
|
82
|
+
* Format pending messages as content-prefix strings for injection into
|
|
83
|
+
* the next user message. Self-messages (from === agentName) become
|
|
84
|
+
* `[inner thought: {content}]`, inter-agent messages become
|
|
85
|
+
* `[message from {name}: {content}]`.
|
|
86
|
+
*/
|
|
87
|
+
function formatPendingPrefix(messages, agentName) {
|
|
88
|
+
return messages
|
|
89
|
+
.map((msg) => msg.from === agentName
|
|
90
|
+
? `[inner thought: ${msg.content}]`
|
|
91
|
+
: `[message from ${msg.from}: ${msg.content}]`)
|
|
92
|
+
.join("\n");
|
|
93
|
+
}
|
|
94
|
+
function getCliContinuityIngressTexts(input) {
|
|
95
|
+
const trimmed = input.trim();
|
|
96
|
+
return trimmed ? [trimmed] : [];
|
|
97
|
+
}
|
|
98
|
+
const CLI_PROMPT = "\x1b[36m> \x1b[0m";
|
|
99
|
+
function writeCliAsyncAssistantMessage(rl, message, stdout = process.stdout) {
|
|
100
|
+
const rlInt = rl;
|
|
101
|
+
const currentLine = rlInt.line ?? "";
|
|
102
|
+
const currentCursor = rlInt.cursor ?? currentLine.length;
|
|
103
|
+
stdout.write("\r\x1b[K");
|
|
104
|
+
stdout.write(`${renderMarkdown(message)}\n`);
|
|
105
|
+
stdout.write(CLI_PROMPT);
|
|
106
|
+
if (!currentLine)
|
|
107
|
+
return;
|
|
108
|
+
stdout.write(currentLine);
|
|
109
|
+
if (currentCursor < currentLine.length) {
|
|
110
|
+
readline.cursorTo(process.stdout, 2 + currentCursor);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Module-level active spinner for log coordination.
|
|
114
|
+
// The terminal log sink calls these to avoid interleaving with spinner output.
|
|
115
|
+
let _activeSpinner = null;
|
|
116
|
+
/* v8 ignore start -- spinner coordination: exercised at runtime, not unit-testable without real terminal @preserve */
|
|
117
|
+
function pauseActiveSpinner() { _activeSpinner?.pause(); }
|
|
118
|
+
function resumeActiveSpinner() { _activeSpinner?.resume(); }
|
|
119
|
+
/* v8 ignore stop */
|
|
120
|
+
function setActiveSpinner(s) { _activeSpinner = s; }
|
|
64
121
|
// spinner that only touches stderr, cleans up after itself
|
|
65
122
|
// exported for direct testability (stop-without-start branch)
|
|
66
123
|
class Spinner {
|
|
@@ -71,12 +128,14 @@ class Spinner {
|
|
|
71
128
|
msg = "";
|
|
72
129
|
phrases = null;
|
|
73
130
|
lastPhrase = "";
|
|
131
|
+
stopped = false;
|
|
74
132
|
constructor(m = "working", phrases) {
|
|
75
133
|
this.msg = m;
|
|
76
134
|
if (phrases && phrases.length > 0)
|
|
77
135
|
this.phrases = phrases;
|
|
78
136
|
}
|
|
79
137
|
start() {
|
|
138
|
+
this.stopped = false;
|
|
80
139
|
process.stderr.write("\r\x1b[K");
|
|
81
140
|
this.spin();
|
|
82
141
|
this.iv = setInterval(() => this.spin(), 80);
|
|
@@ -85,15 +144,37 @@ class Spinner {
|
|
|
85
144
|
}
|
|
86
145
|
}
|
|
87
146
|
spin() {
|
|
88
|
-
|
|
147
|
+
// Guard: clearInterval can't prevent already-dequeued callbacks
|
|
148
|
+
/* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
|
|
149
|
+
if (this.stopped)
|
|
150
|
+
return;
|
|
151
|
+
process.stderr.write(`\r\x1b[K${this.frames[this.i]} ${this.msg}... `);
|
|
89
152
|
this.i = (this.i + 1) % this.frames.length;
|
|
90
153
|
}
|
|
91
154
|
rotatePhrase() {
|
|
155
|
+
/* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
|
|
156
|
+
if (this.stopped)
|
|
157
|
+
return;
|
|
92
158
|
const next = (0, phrases_1.pickPhrase)(this.phrases, this.lastPhrase);
|
|
93
159
|
this.lastPhrase = next;
|
|
94
160
|
this.msg = next;
|
|
95
161
|
}
|
|
162
|
+
/* v8 ignore start -- pause/resume: exercised at runtime via log sink coordination @preserve */
|
|
163
|
+
/** Clear the spinner line temporarily so other output can print cleanly. */
|
|
164
|
+
pause() {
|
|
165
|
+
if (this.stopped)
|
|
166
|
+
return;
|
|
167
|
+
process.stderr.write("\r\x1b[K");
|
|
168
|
+
}
|
|
169
|
+
/** Restore the spinner line after a pause. */
|
|
170
|
+
resume() {
|
|
171
|
+
if (this.stopped)
|
|
172
|
+
return;
|
|
173
|
+
this.spin();
|
|
174
|
+
}
|
|
175
|
+
/* v8 ignore stop */
|
|
96
176
|
stop(ok) {
|
|
177
|
+
this.stopped = true;
|
|
97
178
|
if (this.iv) {
|
|
98
179
|
clearInterval(this.iv);
|
|
99
180
|
this.iv = null;
|
|
@@ -280,41 +361,54 @@ function createCliCallbacks() {
|
|
|
280
361
|
meta: {},
|
|
281
362
|
});
|
|
282
363
|
let currentSpinner = null;
|
|
283
|
-
|
|
364
|
+
function setSpinner(s) { currentSpinner = s; setActiveSpinner(s); }
|
|
284
365
|
let hadToolRun = false;
|
|
285
366
|
let textDirty = false; // true when text/reasoning was written without a trailing newline
|
|
286
367
|
const streamer = new MarkdownStreamer();
|
|
368
|
+
const wrapper = new cli_layout_1.StreamingWordWrapper();
|
|
287
369
|
return {
|
|
288
370
|
onModelStart: () => {
|
|
289
371
|
currentSpinner?.stop();
|
|
290
|
-
|
|
291
|
-
hadReasoning = false;
|
|
372
|
+
setSpinner(null);
|
|
292
373
|
textDirty = false;
|
|
293
374
|
streamer.reset();
|
|
375
|
+
wrapper.reset();
|
|
294
376
|
const phrases = (0, phrases_1.getPhrases)();
|
|
295
377
|
const pool = hadToolRun ? phrases.followup : phrases.thinking;
|
|
296
378
|
const first = (0, phrases_1.pickPhrase)(pool);
|
|
297
|
-
|
|
379
|
+
setSpinner(new Spinner(first, pool));
|
|
298
380
|
currentSpinner.start();
|
|
299
381
|
},
|
|
300
382
|
onModelStreamStart: () => {
|
|
301
|
-
|
|
302
|
-
|
|
383
|
+
// No-op: content callbacks (onTextChunk, onReasoningChunk) handle
|
|
384
|
+
// stopping the spinner. onModelStreamStart fires too early and
|
|
385
|
+
// doesn't fire at all for final_answer tool streaming.
|
|
386
|
+
},
|
|
387
|
+
onClearText: () => {
|
|
388
|
+
streamer.reset();
|
|
389
|
+
wrapper.reset();
|
|
303
390
|
},
|
|
304
391
|
onTextChunk: (text) => {
|
|
305
|
-
if
|
|
306
|
-
|
|
307
|
-
|
|
392
|
+
// Stop spinner if still running — final_answer streaming and Anthropic
|
|
393
|
+
// tool-only responses bypass onModelStreamStart, so the spinner would
|
|
394
|
+
// otherwise keep running (and its \r writes overwrite response text).
|
|
395
|
+
if (currentSpinner) {
|
|
396
|
+
currentSpinner.stop();
|
|
397
|
+
setSpinner(null);
|
|
308
398
|
}
|
|
309
399
|
const rendered = streamer.push(text);
|
|
310
|
-
|
|
311
|
-
|
|
400
|
+
/* v8 ignore start -- wrapper integration: tested via cli.test.ts onTextChunk tests @preserve */
|
|
401
|
+
if (rendered) {
|
|
402
|
+
const wrapped = wrapper.push(rendered);
|
|
403
|
+
if (wrapped)
|
|
404
|
+
process.stdout.write(wrapped);
|
|
405
|
+
}
|
|
406
|
+
/* v8 ignore stop */
|
|
312
407
|
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
313
408
|
},
|
|
314
|
-
onReasoningChunk: (
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
409
|
+
onReasoningChunk: (_text) => {
|
|
410
|
+
// Keep reasoning private in the CLI surface. The spinner continues to
|
|
411
|
+
// represent active thinking until actual tool or answer output arrives.
|
|
318
412
|
},
|
|
319
413
|
onToolStart: (_name, _args) => {
|
|
320
414
|
// Stop the model-start spinner: when the model returns only tool calls
|
|
@@ -329,13 +423,13 @@ function createCliCallbacks() {
|
|
|
329
423
|
}
|
|
330
424
|
const toolPhrases = (0, phrases_1.getPhrases)().tool;
|
|
331
425
|
const first = (0, phrases_1.pickPhrase)(toolPhrases);
|
|
332
|
-
|
|
426
|
+
setSpinner(new Spinner(first, toolPhrases));
|
|
333
427
|
currentSpinner.start();
|
|
334
428
|
hadToolRun = true;
|
|
335
429
|
},
|
|
336
430
|
onToolEnd: (name, argSummary, success) => {
|
|
337
431
|
currentSpinner?.stop();
|
|
338
|
-
|
|
432
|
+
setSpinner(null);
|
|
339
433
|
const msg = (0, format_1.formatToolResult)(name, argSummary, success);
|
|
340
434
|
const color = success ? "\x1b[32m" : "\x1b[31m";
|
|
341
435
|
process.stderr.write(`${color}${msg}\x1b[0m\n`);
|
|
@@ -343,17 +437,17 @@ function createCliCallbacks() {
|
|
|
343
437
|
onError: (error, severity) => {
|
|
344
438
|
if (severity === "transient") {
|
|
345
439
|
currentSpinner?.fail(error.message);
|
|
346
|
-
|
|
440
|
+
setSpinner(null);
|
|
347
441
|
}
|
|
348
442
|
else {
|
|
349
443
|
currentSpinner?.stop();
|
|
350
|
-
|
|
444
|
+
setSpinner(null);
|
|
351
445
|
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(error)}\x1b[0m\n`);
|
|
352
446
|
}
|
|
353
447
|
},
|
|
354
448
|
onKick: () => {
|
|
355
449
|
currentSpinner?.stop();
|
|
356
|
-
|
|
450
|
+
setSpinner(null);
|
|
357
451
|
if (textDirty) {
|
|
358
452
|
process.stdout.write("\n");
|
|
359
453
|
textDirty = false;
|
|
@@ -362,17 +456,65 @@ function createCliCallbacks() {
|
|
|
362
456
|
},
|
|
363
457
|
flushMarkdown: () => {
|
|
364
458
|
currentSpinner?.stop();
|
|
365
|
-
|
|
459
|
+
setSpinner(null);
|
|
460
|
+
/* v8 ignore start -- wrapper flush: tested via cli.test.ts flushMarkdown tests @preserve */
|
|
366
461
|
const remaining = streamer.flush();
|
|
367
|
-
if (remaining)
|
|
368
|
-
|
|
462
|
+
if (remaining) {
|
|
463
|
+
const wrapped = wrapper.push(remaining);
|
|
464
|
+
if (wrapped)
|
|
465
|
+
process.stdout.write(wrapped);
|
|
466
|
+
}
|
|
467
|
+
const tail = wrapper.flush();
|
|
468
|
+
if (tail)
|
|
469
|
+
process.stdout.write(tail);
|
|
470
|
+
/* v8 ignore stop */
|
|
369
471
|
},
|
|
370
472
|
};
|
|
371
473
|
}
|
|
474
|
+
// Debounced line iterator: collects rapid-fire lines (paste) into a single input.
|
|
475
|
+
// When the debounce timeout wins the race, the pending iter.next() is saved
|
|
476
|
+
// and reused in the next iteration to prevent it from silently consuming input.
|
|
477
|
+
async function* createDebouncedLines(source, debounceMs) {
|
|
478
|
+
if (debounceMs <= 0) {
|
|
479
|
+
yield* source;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const iter = source[Symbol.asyncIterator]();
|
|
483
|
+
let pending = null;
|
|
484
|
+
while (true) {
|
|
485
|
+
const first = pending ? await pending : await iter.next();
|
|
486
|
+
pending = null;
|
|
487
|
+
if (first.done)
|
|
488
|
+
break;
|
|
489
|
+
const lines = [first.value];
|
|
490
|
+
let more = true;
|
|
491
|
+
while (more) {
|
|
492
|
+
const nextPromise = iter.next();
|
|
493
|
+
const raced = await Promise.race([
|
|
494
|
+
nextPromise.then((r) => ({ kind: "line", result: r })),
|
|
495
|
+
new Promise((r) => setTimeout(() => r({ kind: "timeout" }), debounceMs)),
|
|
496
|
+
]);
|
|
497
|
+
if (raced.kind === "timeout") {
|
|
498
|
+
pending = nextPromise;
|
|
499
|
+
more = false;
|
|
500
|
+
}
|
|
501
|
+
else if (raced.result.done) {
|
|
502
|
+
more = false;
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
lines.push(raced.result.value);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
yield lines.join("\n");
|
|
509
|
+
}
|
|
510
|
+
}
|
|
372
511
|
async function runCliSession(options) {
|
|
512
|
+
/* v8 ignore start -- integration: runCliSession is interactive, tested via E2E @preserve */
|
|
373
513
|
const pasteDebounceMs = options.pasteDebounceMs ?? 50;
|
|
374
514
|
const registry = (0, commands_1.createCommandRegistry)();
|
|
375
|
-
(
|
|
515
|
+
if (!options.disableCommands) {
|
|
516
|
+
(0, commands_1.registerDefaultCommands)(registry);
|
|
517
|
+
}
|
|
376
518
|
const messages = options.messages
|
|
377
519
|
?? [{ role: "system", content: await (0, prompt_1.buildSystem)("cli") }];
|
|
378
520
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
@@ -381,9 +523,30 @@ async function runCliSession(options) {
|
|
|
381
523
|
const history = [];
|
|
382
524
|
let closed = false;
|
|
383
525
|
rl.on("close", () => { closed = true; });
|
|
384
|
-
|
|
385
|
-
|
|
526
|
+
if (options.banner !== false) {
|
|
527
|
+
const bannerText = typeof options.banner === "string"
|
|
528
|
+
? options.banner
|
|
529
|
+
: `${options.agentName} (type /commands for help)`;
|
|
530
|
+
// eslint-disable-next-line no-console -- terminal UX: startup banner
|
|
531
|
+
console.log(`\n${bannerText}\n`);
|
|
532
|
+
}
|
|
386
533
|
const cliCallbacks = createCliCallbacks();
|
|
534
|
+
const effectiveToolContext = {
|
|
535
|
+
signin: options.toolContext?.signin ?? (async () => undefined),
|
|
536
|
+
...options.toolContext,
|
|
537
|
+
codingFeedback: {
|
|
538
|
+
send: async (message) => {
|
|
539
|
+
const assistantMessage = {
|
|
540
|
+
role: "assistant",
|
|
541
|
+
content: message,
|
|
542
|
+
};
|
|
543
|
+
messages.push(assistantMessage);
|
|
544
|
+
await options.onAsyncAssistantMessage?.(messages, assistantMessage);
|
|
545
|
+
writeCliAsyncAssistantMessage(rl, message);
|
|
546
|
+
await options.toolContext?.codingFeedback?.send(message);
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
};
|
|
387
550
|
// exitOnToolCall machinery: wrap execTool to detect target tool
|
|
388
551
|
let exitToolResult;
|
|
389
552
|
let exitToolFired = false;
|
|
@@ -394,13 +557,15 @@ async function runCliSession(options) {
|
|
|
394
557
|
if (name === options.exitOnToolCall) {
|
|
395
558
|
exitToolResult = result;
|
|
396
559
|
exitToolFired = true;
|
|
560
|
+
// Abort immediately so the model doesn't generate more output
|
|
561
|
+
// (e.g. reasoning about calling final_answer after complete_adoption)
|
|
562
|
+
currentAbort?.abort();
|
|
397
563
|
}
|
|
398
564
|
return result;
|
|
399
565
|
}
|
|
400
566
|
: resolvedExecTool;
|
|
401
567
|
// Resolve toolChoiceRequired: use explicit option if set, else fall back to toggle
|
|
402
568
|
const getEffectiveToolChoiceRequired = () => options.toolChoiceRequired !== undefined ? options.toolChoiceRequired : (0, commands_1.getToolChoiceRequired)();
|
|
403
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
404
569
|
// Ctrl-C at the input prompt: clear line or warn/exit
|
|
405
570
|
rl.on("SIGINT", () => {
|
|
406
571
|
const rlInt = rl;
|
|
@@ -409,50 +574,20 @@ async function runCliSession(options) {
|
|
|
409
574
|
if (result === "clear") {
|
|
410
575
|
rlInt.line = "";
|
|
411
576
|
rlInt.cursor = 0;
|
|
412
|
-
process.stdout.write(
|
|
577
|
+
process.stdout.write(`\r\x1b[K${CLI_PROMPT}`);
|
|
413
578
|
}
|
|
414
579
|
else if (result === "warn") {
|
|
415
580
|
rlInt.line = "";
|
|
416
581
|
rlInt.cursor = 0;
|
|
417
582
|
process.stdout.write("\r\x1b[K");
|
|
418
583
|
process.stderr.write("press Ctrl-C again to exit\n");
|
|
419
|
-
process.stdout.write(
|
|
584
|
+
process.stdout.write(CLI_PROMPT);
|
|
420
585
|
}
|
|
421
586
|
else {
|
|
422
587
|
rl.close();
|
|
423
588
|
}
|
|
424
589
|
});
|
|
425
|
-
|
|
426
|
-
async function* debouncedLines(source) {
|
|
427
|
-
if (pasteDebounceMs <= 0) {
|
|
428
|
-
yield* source;
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
const iter = source[Symbol.asyncIterator]();
|
|
432
|
-
while (true) {
|
|
433
|
-
const first = await iter.next();
|
|
434
|
-
if (first.done)
|
|
435
|
-
break;
|
|
436
|
-
const lines = [first.value];
|
|
437
|
-
let more = true;
|
|
438
|
-
while (more) {
|
|
439
|
-
const raced = await Promise.race([
|
|
440
|
-
iter.next().then((r) => ({ kind: "line", result: r })),
|
|
441
|
-
new Promise((r) => setTimeout(() => r({ kind: "timeout" }), pasteDebounceMs)),
|
|
442
|
-
]);
|
|
443
|
-
if (raced.kind === "timeout") {
|
|
444
|
-
more = false;
|
|
445
|
-
}
|
|
446
|
-
else if (raced.result.done) {
|
|
447
|
-
more = false;
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
lines.push(raced.result.value);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
yield lines.join("\n");
|
|
454
|
-
}
|
|
455
|
-
}
|
|
590
|
+
const debouncedLines = (source) => createDebouncedLines(source, pasteDebounceMs);
|
|
456
591
|
(0, runtime_1.emitNervesEvent)({
|
|
457
592
|
component: "senses",
|
|
458
593
|
event: "senses.cli_session_start",
|
|
@@ -460,12 +595,56 @@ async function runCliSession(options) {
|
|
|
460
595
|
meta: { agentName: options.agentName, hasExitOnToolCall: !!options.exitOnToolCall },
|
|
461
596
|
});
|
|
462
597
|
let exitReason = "user_quit";
|
|
598
|
+
// Auto-first-turn: process the last user message immediately so the agent
|
|
599
|
+
// speaks first (e.g. specialist greeting). Only triggers when explicitly opted in.
|
|
600
|
+
if (options.autoFirstTurn && messages.length > 0 && messages[messages.length - 1]?.role === "user") {
|
|
601
|
+
currentAbort = new AbortController();
|
|
602
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
603
|
+
ctrl.suppress(() => currentAbort.abort());
|
|
604
|
+
let result;
|
|
605
|
+
try {
|
|
606
|
+
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
607
|
+
toolChoiceRequired: getEffectiveToolChoiceRequired(),
|
|
608
|
+
traceId,
|
|
609
|
+
tools: options.tools,
|
|
610
|
+
execTool: wrappedExecTool,
|
|
611
|
+
toolContext: effectiveToolContext,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
catch (err) {
|
|
615
|
+
// AbortError (Ctrl-C) -- silently continue to prompt
|
|
616
|
+
// All other errors: show the user what happened
|
|
617
|
+
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
618
|
+
process.stderr.write(`\x1b[31m${err instanceof Error ? err.message : String(err)}\x1b[0m\n`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
cliCallbacks.flushMarkdown();
|
|
622
|
+
ctrl.restore();
|
|
623
|
+
currentAbort = null;
|
|
624
|
+
if (exitToolFired) {
|
|
625
|
+
exitReason = "tool_exit";
|
|
626
|
+
rl.close();
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
const lastMsg = messages[messages.length - 1];
|
|
630
|
+
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
631
|
+
process.stderr.write("\x1b[33m(empty response)\x1b[0m\n");
|
|
632
|
+
}
|
|
633
|
+
process.stdout.write("\n\n");
|
|
634
|
+
if (options.onTurnEnd) {
|
|
635
|
+
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (!exitToolFired) {
|
|
640
|
+
process.stdout.write(CLI_PROMPT);
|
|
641
|
+
}
|
|
463
642
|
try {
|
|
464
643
|
for await (const input of debouncedLines(rl)) {
|
|
465
644
|
if (closed)
|
|
466
645
|
break;
|
|
467
646
|
if (!input.trim()) {
|
|
468
|
-
process.stdout.write(
|
|
647
|
+
process.stdout.write(CLI_PROMPT);
|
|
469
648
|
continue;
|
|
470
649
|
}
|
|
471
650
|
// Optional input gate (e.g. trust gate in main)
|
|
@@ -477,7 +656,7 @@ async function runCliSession(options) {
|
|
|
477
656
|
}
|
|
478
657
|
if (closed)
|
|
479
658
|
break;
|
|
480
|
-
process.stdout.write(
|
|
659
|
+
process.stdout.write(CLI_PROMPT);
|
|
481
660
|
continue;
|
|
482
661
|
}
|
|
483
662
|
}
|
|
@@ -495,42 +674,50 @@ async function runCliSession(options) {
|
|
|
495
674
|
await options.onNewSession?.();
|
|
496
675
|
// eslint-disable-next-line no-console -- terminal UX: session cleared
|
|
497
676
|
console.log("session cleared");
|
|
498
|
-
process.stdout.write(
|
|
677
|
+
process.stdout.write(CLI_PROMPT);
|
|
499
678
|
continue;
|
|
500
679
|
}
|
|
501
680
|
else if (dispatchResult.result.action === "response") {
|
|
502
681
|
// eslint-disable-next-line no-console -- terminal UX: command dispatch result
|
|
503
682
|
console.log(dispatchResult.result.message || "");
|
|
504
|
-
process.stdout.write(
|
|
683
|
+
process.stdout.write(CLI_PROMPT);
|
|
505
684
|
continue;
|
|
506
685
|
}
|
|
507
686
|
}
|
|
508
687
|
}
|
|
509
|
-
// Re-style the echoed input lines
|
|
688
|
+
// Re-style the echoed input lines without leaving wrapped paste remnants behind.
|
|
510
689
|
const cols = process.stdout.columns || 80;
|
|
511
|
-
|
|
512
|
-
let echoRows = 0;
|
|
513
|
-
for (const line of inputLines) {
|
|
514
|
-
echoRows += Math.ceil((2 + line.length) / cols);
|
|
515
|
-
}
|
|
516
|
-
process.stdout.write(`\x1b[${echoRows}A\x1b[K` + `\x1b[1m> ${inputLines[0]}${inputLines.length > 1 ? ` (+${inputLines.length - 1} lines)` : ""}\x1b[0m\n\n`);
|
|
517
|
-
messages.push({ role: "user", content: input });
|
|
690
|
+
process.stdout.write((0, cli_layout_1.formatEchoedInputSummary)(input, cols));
|
|
518
691
|
addHistory(history, input);
|
|
519
692
|
currentAbort = new AbortController();
|
|
520
|
-
const traceId = (0, nerves_1.createTraceId)();
|
|
521
693
|
ctrl.suppress(() => currentAbort.abort());
|
|
522
694
|
let result;
|
|
523
695
|
try {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
696
|
+
if (options.runTurn) {
|
|
697
|
+
// Pipeline-based turn: the runTurn callback handles user message assembly,
|
|
698
|
+
// pending drain, trust gate, runAgent, postTurn, and token accumulation.
|
|
699
|
+
result = await options.runTurn(messages, input, cliCallbacks, currentAbort.signal, effectiveToolContext);
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
// Legacy path: inline runAgent (used by adoption specialist and tests)
|
|
703
|
+
const prefix = options.getContentPrefix?.();
|
|
704
|
+
messages.push({ role: "user", content: prefix ? `${prefix}\n\n${input}` : input });
|
|
705
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
706
|
+
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
707
|
+
toolChoiceRequired: getEffectiveToolChoiceRequired(),
|
|
708
|
+
traceId,
|
|
709
|
+
tools: options.tools,
|
|
710
|
+
execTool: wrappedExecTool,
|
|
711
|
+
toolContext: effectiveToolContext,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
531
714
|
}
|
|
532
|
-
catch {
|
|
533
|
-
// AbortError -- silently return to prompt
|
|
715
|
+
catch (err) {
|
|
716
|
+
// AbortError (Ctrl-C) -- silently return to prompt
|
|
717
|
+
// All other errors: show the user what happened
|
|
718
|
+
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
719
|
+
process.stderr.write(`\x1b[31m${err instanceof Error ? err.message : String(err)}\x1b[0m\n`);
|
|
720
|
+
}
|
|
534
721
|
}
|
|
535
722
|
cliCallbacks.flushMarkdown();
|
|
536
723
|
ctrl.restore();
|
|
@@ -552,20 +739,28 @@ async function runCliSession(options) {
|
|
|
552
739
|
}
|
|
553
740
|
if (closed)
|
|
554
741
|
break;
|
|
555
|
-
process.stdout.write(
|
|
742
|
+
process.stdout.write(CLI_PROMPT);
|
|
556
743
|
}
|
|
557
744
|
}
|
|
558
745
|
finally {
|
|
559
746
|
rl.close();
|
|
560
|
-
|
|
561
|
-
|
|
747
|
+
if (options.banner !== false) {
|
|
748
|
+
// eslint-disable-next-line no-console -- terminal UX: goodbye
|
|
749
|
+
console.log("bye");
|
|
750
|
+
}
|
|
562
751
|
}
|
|
752
|
+
/* v8 ignore stop */
|
|
563
753
|
return { exitReason, toolResult: exitToolResult };
|
|
564
754
|
}
|
|
565
755
|
async function main(agentName, options) {
|
|
566
756
|
if (agentName)
|
|
567
757
|
(0, identity_1.setAgentName)(agentName);
|
|
568
758
|
const pasteDebounceMs = options?.pasteDebounceMs ?? 50;
|
|
759
|
+
// Register spinner hooks so log output clears the spinner before printing
|
|
760
|
+
(0, nerves_1.registerSpinnerHooks)(pauseActiveSpinner, resumeActiveSpinner);
|
|
761
|
+
// Fallback: apply pending updates for daemon-less direct CLI usage
|
|
762
|
+
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
763
|
+
await (0, update_hooks_1.applyPendingUpdates)((0, identity_1.getAgentBundlesRoot)(), (0, bundle_manifest_1.getPackageVersion)());
|
|
569
764
|
// Fail fast if provider is misconfigured (triggers human-readable error + exit)
|
|
570
765
|
(0, core_1.getProvider)();
|
|
571
766
|
// Resolve context kernel (identity + channel) for CLI
|
|
@@ -581,13 +776,6 @@ async function main(agentName, options) {
|
|
|
581
776
|
channel: "cli",
|
|
582
777
|
});
|
|
583
778
|
const resolvedContext = await resolver.resolve();
|
|
584
|
-
const cliToolContext = {
|
|
585
|
-
/* v8 ignore next -- CLI has no OAuth sign-in; this no-op satisfies the interface @preserve */
|
|
586
|
-
signin: async () => undefined,
|
|
587
|
-
context: resolvedContext,
|
|
588
|
-
friendStore,
|
|
589
|
-
summarize: (0, core_1.createSummarize)(),
|
|
590
|
-
};
|
|
591
779
|
const friendId = resolvedContext.friend.id;
|
|
592
780
|
const agentConfig = (0, identity_1.loadAgentConfig)();
|
|
593
781
|
(0, cli_logging_1.configureCliRuntimeLogger)(friendId, {
|
|
@@ -610,52 +798,107 @@ async function main(agentName, options) {
|
|
|
610
798
|
}
|
|
611
799
|
// Load existing session or start fresh
|
|
612
800
|
const existing = (0, context_1.loadSession)(sessPath);
|
|
801
|
+
let sessionState = existing?.state;
|
|
802
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
613
803
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
614
804
|
? existing.messages
|
|
615
|
-
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli",
|
|
616
|
-
//
|
|
617
|
-
const
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
for (const msg of pending) {
|
|
623
|
-
sessionMessages.push({ role: "user", name: "harness", content: `[proactive message from ${msg.from}]` });
|
|
624
|
-
sessionMessages.push({ role: "assistant", content: msg.content });
|
|
625
|
-
}
|
|
626
|
-
return pending.length;
|
|
627
|
-
};
|
|
628
|
-
// Startup drain: deliver offline messages
|
|
629
|
-
const startupCount = drainToMessages();
|
|
630
|
-
if (startupCount > 0) {
|
|
631
|
-
(0, context_1.saveSession)(sessPath, sessionMessages);
|
|
632
|
-
}
|
|
805
|
+
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli", { mcpManager }, resolvedContext) }];
|
|
806
|
+
// Per-turn pipeline input: CLI capabilities and pending dir
|
|
807
|
+
const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
|
|
808
|
+
const currentAgentName = (0, identity_1.getAgentName)();
|
|
809
|
+
const pendingDir = (0, pending_1.getPendingDir)(currentAgentName, friendId, "cli", "session");
|
|
810
|
+
const summarize = (0, core_1.createSummarize)();
|
|
811
|
+
const cliFailoverState = { pending: null };
|
|
633
812
|
try {
|
|
634
813
|
await runCliSession({
|
|
635
|
-
agentName:
|
|
814
|
+
agentName: currentAgentName,
|
|
636
815
|
pasteDebounceMs,
|
|
637
816
|
messages: sessionMessages,
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
817
|
+
onAsyncAssistantMessage: async (messages, _assistantMessage) => {
|
|
818
|
+
(0, context_1.postTurn)(messages, sessPath, undefined, undefined, sessionState);
|
|
819
|
+
},
|
|
820
|
+
runTurn: async (messages, userInput, callbacks, signal, toolContext) => {
|
|
821
|
+
// Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
|
|
822
|
+
// User message passed via input.messages so the pipeline can prepend pending messages to it.
|
|
823
|
+
const failoverState = cliFailoverState;
|
|
824
|
+
// Capture terminal errors instead of displaying immediately — the failover
|
|
825
|
+
// message replaces the raw error if failover triggers successfully.
|
|
826
|
+
let capturedTerminalError = null;
|
|
827
|
+
/* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
|
|
828
|
+
const failoverAwareCallbacks = {
|
|
829
|
+
...callbacks,
|
|
830
|
+
onError: (error, severity) => {
|
|
831
|
+
if (severity === "terminal" && failoverState) {
|
|
832
|
+
capturedTerminalError = error;
|
|
833
|
+
callbacks.onError(new Error(""), "transient");
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
callbacks.onError(error, severity);
|
|
837
|
+
},
|
|
838
|
+
};
|
|
839
|
+
/* v8 ignore stop */
|
|
840
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
841
|
+
channel: "cli",
|
|
842
|
+
sessionKey: "session",
|
|
843
|
+
capabilities: cliCapabilities,
|
|
844
|
+
messages: [{ role: "user", content: userInput }],
|
|
845
|
+
continuityIngressTexts: getCliContinuityIngressTexts(userInput),
|
|
846
|
+
callbacks: failoverAwareCallbacks,
|
|
847
|
+
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
848
|
+
sessionLoader: {
|
|
849
|
+
loadOrCreate: () => Promise.resolve({
|
|
850
|
+
messages,
|
|
851
|
+
sessionPath: sessPath,
|
|
852
|
+
state: sessionState,
|
|
853
|
+
}),
|
|
854
|
+
},
|
|
855
|
+
pendingDir,
|
|
856
|
+
friendStore,
|
|
642
857
|
provider: "local",
|
|
643
858
|
externalId: localExternalId,
|
|
644
|
-
|
|
859
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
860
|
+
drainPending: pending_1.drainPending,
|
|
861
|
+
drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)(currentAgentName, deferredFriendId),
|
|
862
|
+
runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
|
|
863
|
+
...opts,
|
|
864
|
+
toolContext: {
|
|
865
|
+
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
866
|
+
signin: async () => undefined,
|
|
867
|
+
...opts?.toolContext,
|
|
868
|
+
summarize,
|
|
869
|
+
},
|
|
870
|
+
}),
|
|
871
|
+
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
872
|
+
(0, context_1.postTurn)(turnMessages, sessionPathArg, usage, hooks, state);
|
|
873
|
+
sessionState = state;
|
|
874
|
+
},
|
|
875
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
876
|
+
signal,
|
|
877
|
+
runAgentOptions: {
|
|
878
|
+
toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
|
|
879
|
+
traceId: (0, nerves_1.createTraceId)(),
|
|
880
|
+
mcpManager,
|
|
881
|
+
toolContext,
|
|
882
|
+
},
|
|
883
|
+
failoverState,
|
|
645
884
|
});
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
};
|
|
885
|
+
/* v8 ignore start -- failover display: tested via pipeline integration tests @preserve */
|
|
886
|
+
if (result.failoverMessage) {
|
|
887
|
+
// Failover handled it — show the actionable message instead of the raw error
|
|
888
|
+
process.stdout.write(`\x1b[33m${result.failoverMessage}\x1b[0m\n`);
|
|
651
889
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
890
|
+
else if (capturedTerminalError) {
|
|
891
|
+
// Failover didn't trigger (no failoverState, or sequence failed) — show the raw error
|
|
892
|
+
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(capturedTerminalError)}\x1b[0m\n`);
|
|
893
|
+
}
|
|
894
|
+
/* v8 ignore stop */
|
|
895
|
+
// Handle gate rejection: display auto-reply if present
|
|
896
|
+
if (!result.gateResult.allowed) {
|
|
897
|
+
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
898
|
+
process.stdout.write(`${result.gateResult.autoReply}\n`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return { usage: result.usage };
|
|
659
902
|
},
|
|
660
903
|
onNewSession: () => {
|
|
661
904
|
(0, context_1.deleteSession)(sessPath);
|