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