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