@ouro.bot/cli 0.1.0-alpha.1 → 0.1.0-alpha.100
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 +596 -0
- package/dist/heart/active-work.js +251 -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 +109 -0
- package/dist/heart/config.js +102 -23
- package/dist/heart/core.js +512 -94
- 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 +1935 -185
- 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 +218 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +10 -83
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- 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 +147 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron.js +260 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- 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 +171 -0
- package/dist/heart/daemon/process-manager.js +32 -2
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +61 -14
- 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 +129 -0
- package/dist/heart/daemon/specialist-prompt.js +99 -0
- package/dist/heart/daemon/specialist-tools.js +283 -0
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/task-scheduler.js +4 -1
- 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 +153 -23
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +191 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +77 -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 +381 -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 +103 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +37 -4
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +141 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +43 -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 +9 -1
- package/dist/mind/memory.js +89 -26
- package/dist/mind/obligation-steering.js +31 -0
- package/dist/mind/pending.js +160 -0
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt-refresh.js +20 -0
- package/dist/mind/prompt.js +499 -8
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/file-completeness.js +14 -4
- 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 +210 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +69 -4
- package/dist/repertoire/coding/spawner.js +21 -3
- package/dist/repertoire/coding/tools.js +105 -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 +770 -213
- 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 +484 -0
- package/dist/senses/bluebubbles-entry.js +13 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +261 -0
- package/dist/senses/bluebubbles-mutation-log.js +116 -0
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +1181 -0
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +452 -99
- 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 +387 -70
- package/dist/senses/pipeline.js +307 -0
- package/dist/senses/session-lock.js +119 -0
- package/dist/senses/teams.js +574 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +16 -4
- package/subagents/README.md +4 -68
- 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 -593
- package/subagents/work-planner.md +0 -373
package/dist/senses/cli.js
CHANGED
|
@@ -33,11 +33,19 @@ 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;
|
|
48
|
+
exports.runCliSession = runCliSession;
|
|
41
49
|
exports.main = main;
|
|
42
50
|
const readline = __importStar(require("readline"));
|
|
43
51
|
const os = __importStar(require("os"));
|
|
@@ -48,8 +56,10 @@ const phrases_1 = require("../mind/phrases");
|
|
|
48
56
|
const format_1 = require("../mind/format");
|
|
49
57
|
const config_1 = require("../heart/config");
|
|
50
58
|
const context_1 = require("../mind/context");
|
|
59
|
+
const pending_1 = require("../mind/pending");
|
|
51
60
|
const commands_1 = require("./commands");
|
|
52
61
|
const identity_1 = require("../heart/identity");
|
|
62
|
+
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
53
63
|
const nerves_1 = require("../nerves");
|
|
54
64
|
const store_file_1 = require("../mind/friends/store-file");
|
|
55
65
|
const resolver_1 = require("../mind/friends/resolver");
|
|
@@ -57,6 +67,57 @@ const tokens_1 = require("../mind/friends/tokens");
|
|
|
57
67
|
const cli_logging_1 = require("../nerves/cli-logging");
|
|
58
68
|
const runtime_1 = require("../nerves/runtime");
|
|
59
69
|
const trust_gate_1 = require("./trust-gate");
|
|
70
|
+
const pipeline_1 = require("./pipeline");
|
|
71
|
+
const channel_1 = require("../mind/friends/channel");
|
|
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; }
|
|
60
121
|
// spinner that only touches stderr, cleans up after itself
|
|
61
122
|
// exported for direct testability (stop-without-start branch)
|
|
62
123
|
class Spinner {
|
|
@@ -67,12 +128,14 @@ class Spinner {
|
|
|
67
128
|
msg = "";
|
|
68
129
|
phrases = null;
|
|
69
130
|
lastPhrase = "";
|
|
131
|
+
stopped = false;
|
|
70
132
|
constructor(m = "working", phrases) {
|
|
71
133
|
this.msg = m;
|
|
72
134
|
if (phrases && phrases.length > 0)
|
|
73
135
|
this.phrases = phrases;
|
|
74
136
|
}
|
|
75
137
|
start() {
|
|
138
|
+
this.stopped = false;
|
|
76
139
|
process.stderr.write("\r\x1b[K");
|
|
77
140
|
this.spin();
|
|
78
141
|
this.iv = setInterval(() => this.spin(), 80);
|
|
@@ -81,15 +144,37 @@ class Spinner {
|
|
|
81
144
|
}
|
|
82
145
|
}
|
|
83
146
|
spin() {
|
|
84
|
-
|
|
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}... `);
|
|
85
152
|
this.i = (this.i + 1) % this.frames.length;
|
|
86
153
|
}
|
|
87
154
|
rotatePhrase() {
|
|
155
|
+
/* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
|
|
156
|
+
if (this.stopped)
|
|
157
|
+
return;
|
|
88
158
|
const next = (0, phrases_1.pickPhrase)(this.phrases, this.lastPhrase);
|
|
89
159
|
this.lastPhrase = next;
|
|
90
160
|
this.msg = next;
|
|
91
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 */
|
|
92
176
|
stop(ok) {
|
|
177
|
+
this.stopped = true;
|
|
93
178
|
if (this.iv) {
|
|
94
179
|
clearInterval(this.iv);
|
|
95
180
|
this.iv = null;
|
|
@@ -276,41 +361,54 @@ function createCliCallbacks() {
|
|
|
276
361
|
meta: {},
|
|
277
362
|
});
|
|
278
363
|
let currentSpinner = null;
|
|
279
|
-
|
|
364
|
+
function setSpinner(s) { currentSpinner = s; setActiveSpinner(s); }
|
|
280
365
|
let hadToolRun = false;
|
|
281
366
|
let textDirty = false; // true when text/reasoning was written without a trailing newline
|
|
282
367
|
const streamer = new MarkdownStreamer();
|
|
368
|
+
const wrapper = new cli_layout_1.StreamingWordWrapper();
|
|
283
369
|
return {
|
|
284
370
|
onModelStart: () => {
|
|
285
371
|
currentSpinner?.stop();
|
|
286
|
-
|
|
287
|
-
hadReasoning = false;
|
|
372
|
+
setSpinner(null);
|
|
288
373
|
textDirty = false;
|
|
289
374
|
streamer.reset();
|
|
375
|
+
wrapper.reset();
|
|
290
376
|
const phrases = (0, phrases_1.getPhrases)();
|
|
291
377
|
const pool = hadToolRun ? phrases.followup : phrases.thinking;
|
|
292
378
|
const first = (0, phrases_1.pickPhrase)(pool);
|
|
293
|
-
|
|
379
|
+
setSpinner(new Spinner(first, pool));
|
|
294
380
|
currentSpinner.start();
|
|
295
381
|
},
|
|
296
382
|
onModelStreamStart: () => {
|
|
297
|
-
|
|
298
|
-
|
|
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();
|
|
299
390
|
},
|
|
300
391
|
onTextChunk: (text) => {
|
|
301
|
-
if
|
|
302
|
-
|
|
303
|
-
|
|
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);
|
|
304
398
|
}
|
|
305
399
|
const rendered = streamer.push(text);
|
|
306
|
-
|
|
307
|
-
|
|
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 */
|
|
308
407
|
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
309
408
|
},
|
|
310
|
-
onReasoningChunk: (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
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.
|
|
314
412
|
},
|
|
315
413
|
onToolStart: (_name, _args) => {
|
|
316
414
|
// Stop the model-start spinner: when the model returns only tool calls
|
|
@@ -325,13 +423,13 @@ function createCliCallbacks() {
|
|
|
325
423
|
}
|
|
326
424
|
const toolPhrases = (0, phrases_1.getPhrases)().tool;
|
|
327
425
|
const first = (0, phrases_1.pickPhrase)(toolPhrases);
|
|
328
|
-
|
|
426
|
+
setSpinner(new Spinner(first, toolPhrases));
|
|
329
427
|
currentSpinner.start();
|
|
330
428
|
hadToolRun = true;
|
|
331
429
|
},
|
|
332
430
|
onToolEnd: (name, argSummary, success) => {
|
|
333
431
|
currentSpinner?.stop();
|
|
334
|
-
|
|
432
|
+
setSpinner(null);
|
|
335
433
|
const msg = (0, format_1.formatToolResult)(name, argSummary, success);
|
|
336
434
|
const color = success ? "\x1b[32m" : "\x1b[31m";
|
|
337
435
|
process.stderr.write(`${color}${msg}\x1b[0m\n`);
|
|
@@ -339,17 +437,17 @@ function createCliCallbacks() {
|
|
|
339
437
|
onError: (error, severity) => {
|
|
340
438
|
if (severity === "transient") {
|
|
341
439
|
currentSpinner?.fail(error.message);
|
|
342
|
-
|
|
440
|
+
setSpinner(null);
|
|
343
441
|
}
|
|
344
442
|
else {
|
|
345
443
|
currentSpinner?.stop();
|
|
346
|
-
|
|
444
|
+
setSpinner(null);
|
|
347
445
|
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(error)}\x1b[0m\n`);
|
|
348
446
|
}
|
|
349
447
|
},
|
|
350
448
|
onKick: () => {
|
|
351
449
|
currentSpinner?.stop();
|
|
352
|
-
|
|
450
|
+
setSpinner(null);
|
|
353
451
|
if (textDirty) {
|
|
354
452
|
process.stdout.write("\n");
|
|
355
453
|
textDirty = false;
|
|
@@ -358,57 +456,117 @@ function createCliCallbacks() {
|
|
|
358
456
|
},
|
|
359
457
|
flushMarkdown: () => {
|
|
360
458
|
currentSpinner?.stop();
|
|
361
|
-
|
|
459
|
+
setSpinner(null);
|
|
460
|
+
/* v8 ignore start -- wrapper flush: tested via cli.test.ts flushMarkdown tests @preserve */
|
|
362
461
|
const remaining = streamer.flush();
|
|
363
|
-
if (remaining)
|
|
364
|
-
|
|
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 */
|
|
365
471
|
},
|
|
366
472
|
};
|
|
367
473
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
+
}
|
|
511
|
+
async function runCliSession(options) {
|
|
512
|
+
/* v8 ignore start -- integration: runCliSession is interactive, tested via E2E @preserve */
|
|
513
|
+
const pasteDebounceMs = options.pasteDebounceMs ?? 50;
|
|
371
514
|
const registry = (0, commands_1.createCommandRegistry)();
|
|
372
|
-
(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
const hostname = os.hostname();
|
|
378
|
-
const localExternalId = `${username}@${hostname}`;
|
|
379
|
-
const resolver = new resolver_1.FriendResolver(friendStore, {
|
|
380
|
-
provider: "local",
|
|
381
|
-
externalId: localExternalId,
|
|
382
|
-
displayName: username,
|
|
383
|
-
channel: "cli",
|
|
384
|
-
});
|
|
385
|
-
const resolvedContext = await resolver.resolve();
|
|
386
|
-
const cliToolContext = {
|
|
387
|
-
/* v8 ignore next -- CLI has no OAuth sign-in; this no-op satisfies the interface @preserve */
|
|
388
|
-
signin: async () => undefined,
|
|
389
|
-
context: resolvedContext,
|
|
390
|
-
friendStore,
|
|
391
|
-
};
|
|
392
|
-
const friendId = resolvedContext.friend.id;
|
|
393
|
-
(0, cli_logging_1.configureCliRuntimeLogger)(friendId);
|
|
394
|
-
const sessPath = (0, config_1.sessionPath)(friendId, "cli", "session");
|
|
395
|
-
// Load existing session or start fresh
|
|
396
|
-
const existing = (0, context_1.loadSession)(sessPath);
|
|
397
|
-
const messages = existing?.messages && existing.messages.length > 0
|
|
398
|
-
? existing.messages
|
|
399
|
-
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli", undefined, resolvedContext) }];
|
|
515
|
+
if (!options.disableCommands) {
|
|
516
|
+
(0, commands_1.registerDefaultCommands)(registry);
|
|
517
|
+
}
|
|
518
|
+
const messages = options.messages
|
|
519
|
+
?? [{ role: "system", content: await (0, prompt_1.buildSystem)("cli") }];
|
|
400
520
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
401
521
|
const ctrl = new InputController(rl);
|
|
402
522
|
let currentAbort = null;
|
|
403
523
|
const history = [];
|
|
404
524
|
let closed = false;
|
|
405
525
|
rl.on("close", () => { closed = true; });
|
|
406
|
-
|
|
407
|
-
|
|
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
|
+
}
|
|
408
533
|
const cliCallbacks = createCliCallbacks();
|
|
409
|
-
|
|
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
|
+
};
|
|
550
|
+
// exitOnToolCall machinery: wrap execTool to detect target tool
|
|
551
|
+
let exitToolResult;
|
|
552
|
+
let exitToolFired = false;
|
|
553
|
+
const resolvedExecTool = options.execTool;
|
|
554
|
+
const wrappedExecTool = options.exitOnToolCall && resolvedExecTool
|
|
555
|
+
? async (name, args, ctx) => {
|
|
556
|
+
const result = await resolvedExecTool(name, args, ctx);
|
|
557
|
+
if (name === options.exitOnToolCall) {
|
|
558
|
+
exitToolResult = result;
|
|
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();
|
|
563
|
+
}
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
566
|
+
: resolvedExecTool;
|
|
567
|
+
// Resolve toolChoiceRequired: use explicit option if set, else fall back to toggle
|
|
568
|
+
const getEffectiveToolChoiceRequired = () => options.toolChoiceRequired !== undefined ? options.toolChoiceRequired : (0, commands_1.getToolChoiceRequired)();
|
|
410
569
|
// Ctrl-C at the input prompt: clear line or warn/exit
|
|
411
|
-
// readline with terminal:true catches Ctrl-C in raw mode (no ^C echo)
|
|
412
570
|
rl.on("SIGINT", () => {
|
|
413
571
|
const rlInt = rl;
|
|
414
572
|
const currentLine = rlInt.line || "";
|
|
@@ -416,41 +574,91 @@ async function main() {
|
|
|
416
574
|
if (result === "clear") {
|
|
417
575
|
rlInt.line = "";
|
|
418
576
|
rlInt.cursor = 0;
|
|
419
|
-
process.stdout.write(
|
|
577
|
+
process.stdout.write(`\r\x1b[K${CLI_PROMPT}`);
|
|
420
578
|
}
|
|
421
579
|
else if (result === "warn") {
|
|
422
580
|
rlInt.line = "";
|
|
423
581
|
rlInt.cursor = 0;
|
|
424
582
|
process.stdout.write("\r\x1b[K");
|
|
425
583
|
process.stderr.write("press Ctrl-C again to exit\n");
|
|
426
|
-
process.stdout.write(
|
|
584
|
+
process.stdout.write(CLI_PROMPT);
|
|
427
585
|
}
|
|
428
586
|
else {
|
|
429
587
|
rl.close();
|
|
430
588
|
}
|
|
431
589
|
});
|
|
590
|
+
const debouncedLines = (source) => createDebouncedLines(source, pasteDebounceMs);
|
|
591
|
+
(0, runtime_1.emitNervesEvent)({
|
|
592
|
+
component: "senses",
|
|
593
|
+
event: "senses.cli_session_start",
|
|
594
|
+
message: "runCliSession started",
|
|
595
|
+
meta: { agentName: options.agentName, hasExitOnToolCall: !!options.exitOnToolCall },
|
|
596
|
+
});
|
|
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
|
+
}
|
|
432
642
|
try {
|
|
433
|
-
for await (const input of rl) {
|
|
643
|
+
for await (const input of debouncedLines(rl)) {
|
|
434
644
|
if (closed)
|
|
435
645
|
break;
|
|
436
646
|
if (!input.trim()) {
|
|
437
|
-
process.stdout.write(
|
|
647
|
+
process.stdout.write(CLI_PROMPT);
|
|
438
648
|
continue;
|
|
439
649
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
650
|
+
// Optional input gate (e.g. trust gate in main)
|
|
651
|
+
if (options.onInput) {
|
|
652
|
+
const gate = options.onInput(input);
|
|
653
|
+
if (!gate.allowed) {
|
|
654
|
+
if (gate.reply) {
|
|
655
|
+
process.stdout.write(`${gate.reply}\n`);
|
|
656
|
+
}
|
|
657
|
+
if (closed)
|
|
658
|
+
break;
|
|
659
|
+
process.stdout.write(CLI_PROMPT);
|
|
660
|
+
continue;
|
|
449
661
|
}
|
|
450
|
-
if (closed)
|
|
451
|
-
break;
|
|
452
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
453
|
-
continue;
|
|
454
662
|
}
|
|
455
663
|
// Check for slash commands
|
|
456
664
|
const parsed = (0, commands_1.parseSlashCommand)(input);
|
|
@@ -463,61 +671,206 @@ async function main() {
|
|
|
463
671
|
else if (dispatchResult.result.action === "new") {
|
|
464
672
|
messages.length = 0;
|
|
465
673
|
messages.push({ role: "system", content: await (0, prompt_1.buildSystem)("cli") });
|
|
466
|
-
|
|
674
|
+
await options.onNewSession?.();
|
|
467
675
|
// eslint-disable-next-line no-console -- terminal UX: session cleared
|
|
468
676
|
console.log("session cleared");
|
|
469
|
-
process.stdout.write(
|
|
677
|
+
process.stdout.write(CLI_PROMPT);
|
|
470
678
|
continue;
|
|
471
679
|
}
|
|
472
680
|
else if (dispatchResult.result.action === "response") {
|
|
473
681
|
// eslint-disable-next-line no-console -- terminal UX: command dispatch result
|
|
474
682
|
console.log(dispatchResult.result.message || "");
|
|
475
|
-
process.stdout.write(
|
|
683
|
+
process.stdout.write(CLI_PROMPT);
|
|
476
684
|
continue;
|
|
477
685
|
}
|
|
478
686
|
}
|
|
479
687
|
}
|
|
480
|
-
// Re-style the echoed input
|
|
481
|
-
// Calculate terminal rows the echo occupied (prompt "> " + input, wrapped)
|
|
688
|
+
// Re-style the echoed input lines without leaving wrapped paste remnants behind.
|
|
482
689
|
const cols = process.stdout.columns || 80;
|
|
483
|
-
|
|
484
|
-
const rows = Math.ceil(echoLen / cols);
|
|
485
|
-
process.stdout.write(`\x1b[${rows}A\x1b[K` + `\x1b[1m> ${input}\x1b[0m\n\n`);
|
|
486
|
-
messages.push({ role: "user", content: input });
|
|
690
|
+
process.stdout.write((0, cli_layout_1.formatEchoedInputSummary)(input, cols));
|
|
487
691
|
addHistory(history, input);
|
|
488
692
|
currentAbort = new AbortController();
|
|
489
|
-
const traceId = (0, nerves_1.createTraceId)();
|
|
490
693
|
ctrl.suppress(() => currentAbort.abort());
|
|
491
694
|
let result;
|
|
492
695
|
try {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
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
|
+
}
|
|
498
714
|
}
|
|
499
|
-
catch {
|
|
500
|
-
// AbortError
|
|
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
|
+
}
|
|
501
721
|
}
|
|
502
722
|
cliCallbacks.flushMarkdown();
|
|
503
723
|
ctrl.restore();
|
|
504
724
|
currentAbort = null;
|
|
725
|
+
// Check if exit tool was fired during this turn
|
|
726
|
+
if (exitToolFired) {
|
|
727
|
+
exitReason = "tool_exit";
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
505
730
|
// Safety net: never silently swallow an empty response
|
|
506
731
|
const lastMsg = messages[messages.length - 1];
|
|
507
732
|
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
508
733
|
process.stderr.write("\x1b[33m(empty response)\x1b[0m\n");
|
|
509
734
|
}
|
|
510
735
|
process.stdout.write("\n\n");
|
|
511
|
-
(
|
|
512
|
-
|
|
736
|
+
// Post-turn hook (session persistence, pending drain, prompt refresh, etc.)
|
|
737
|
+
if (options.onTurnEnd) {
|
|
738
|
+
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
739
|
+
}
|
|
513
740
|
if (closed)
|
|
514
741
|
break;
|
|
515
|
-
process.stdout.write(
|
|
742
|
+
process.stdout.write(CLI_PROMPT);
|
|
516
743
|
}
|
|
517
744
|
}
|
|
518
745
|
finally {
|
|
519
746
|
rl.close();
|
|
520
|
-
|
|
521
|
-
|
|
747
|
+
if (options.banner !== false) {
|
|
748
|
+
// eslint-disable-next-line no-console -- terminal UX: goodbye
|
|
749
|
+
console.log("bye");
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/* v8 ignore stop */
|
|
753
|
+
return { exitReason, toolResult: exitToolResult };
|
|
754
|
+
}
|
|
755
|
+
async function main(agentName, options) {
|
|
756
|
+
if (agentName)
|
|
757
|
+
(0, identity_1.setAgentName)(agentName);
|
|
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)());
|
|
764
|
+
// Fail fast if provider is misconfigured (triggers human-readable error + exit)
|
|
765
|
+
(0, core_1.getProvider)();
|
|
766
|
+
// Resolve context kernel (identity + channel) for CLI
|
|
767
|
+
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
768
|
+
const friendStore = new store_file_1.FileFriendStore(friendsPath);
|
|
769
|
+
const username = os.userInfo().username;
|
|
770
|
+
const hostname = os.hostname();
|
|
771
|
+
const localExternalId = `${username}@${hostname}`;
|
|
772
|
+
const resolver = new resolver_1.FriendResolver(friendStore, {
|
|
773
|
+
provider: "local",
|
|
774
|
+
externalId: localExternalId,
|
|
775
|
+
displayName: username,
|
|
776
|
+
channel: "cli",
|
|
777
|
+
});
|
|
778
|
+
const resolvedContext = await resolver.resolve();
|
|
779
|
+
const friendId = resolvedContext.friend.id;
|
|
780
|
+
const agentConfig = (0, identity_1.loadAgentConfig)();
|
|
781
|
+
(0, cli_logging_1.configureCliRuntimeLogger)(friendId, {
|
|
782
|
+
level: agentConfig.logging?.level,
|
|
783
|
+
sinks: agentConfig.logging?.sinks,
|
|
784
|
+
});
|
|
785
|
+
const sessPath = (0, config_1.sessionPath)(friendId, "cli", "session");
|
|
786
|
+
let sessionLock = null;
|
|
787
|
+
try {
|
|
788
|
+
sessionLock = (0, session_lock_1.acquireSessionLock)(`${sessPath}.lock`, (0, identity_1.getAgentName)());
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
/* v8 ignore start -- integration: main() is interactive, lock tested in session-lock.test.ts @preserve */
|
|
792
|
+
if (error instanceof session_lock_1.SessionLockError) {
|
|
793
|
+
process.stderr.write(`${error.message}\n`);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
throw error;
|
|
797
|
+
/* v8 ignore stop */
|
|
798
|
+
}
|
|
799
|
+
// Load existing session or start fresh
|
|
800
|
+
const existing = (0, context_1.loadSession)(sessPath);
|
|
801
|
+
let sessionState = existing?.state;
|
|
802
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
803
|
+
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
804
|
+
? existing.messages
|
|
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
|
+
try {
|
|
812
|
+
await runCliSession({
|
|
813
|
+
agentName: currentAgentName,
|
|
814
|
+
pasteDebounceMs,
|
|
815
|
+
messages: sessionMessages,
|
|
816
|
+
onAsyncAssistantMessage: async (messages, _assistantMessage) => {
|
|
817
|
+
(0, context_1.postTurn)(messages, sessPath, undefined, undefined, sessionState);
|
|
818
|
+
},
|
|
819
|
+
runTurn: async (messages, userInput, callbacks, signal, toolContext) => {
|
|
820
|
+
// Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
|
|
821
|
+
// User message passed via input.messages so the pipeline can prepend pending messages to it.
|
|
822
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
823
|
+
channel: "cli",
|
|
824
|
+
sessionKey: "session",
|
|
825
|
+
capabilities: cliCapabilities,
|
|
826
|
+
messages: [{ role: "user", content: userInput }],
|
|
827
|
+
continuityIngressTexts: getCliContinuityIngressTexts(userInput),
|
|
828
|
+
callbacks,
|
|
829
|
+
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
830
|
+
sessionLoader: { loadOrCreate: () => Promise.resolve({ messages, sessionPath: sessPath, state: sessionState }) },
|
|
831
|
+
pendingDir,
|
|
832
|
+
friendStore,
|
|
833
|
+
provider: "local",
|
|
834
|
+
externalId: localExternalId,
|
|
835
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
836
|
+
drainPending: pending_1.drainPending,
|
|
837
|
+
drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)(currentAgentName, deferredFriendId),
|
|
838
|
+
runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
|
|
839
|
+
...opts,
|
|
840
|
+
toolContext: {
|
|
841
|
+
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
842
|
+
signin: async () => undefined,
|
|
843
|
+
...opts?.toolContext,
|
|
844
|
+
summarize,
|
|
845
|
+
},
|
|
846
|
+
}),
|
|
847
|
+
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
848
|
+
(0, context_1.postTurn)(turnMessages, sessionPathArg, usage, hooks, state);
|
|
849
|
+
sessionState = state;
|
|
850
|
+
},
|
|
851
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
852
|
+
signal,
|
|
853
|
+
runAgentOptions: {
|
|
854
|
+
toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
|
|
855
|
+
traceId: (0, nerves_1.createTraceId)(),
|
|
856
|
+
mcpManager,
|
|
857
|
+
toolContext,
|
|
858
|
+
},
|
|
859
|
+
});
|
|
860
|
+
// Handle gate rejection: display auto-reply if present
|
|
861
|
+
if (!result.gateResult.allowed) {
|
|
862
|
+
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
863
|
+
process.stdout.write(`${result.gateResult.autoReply}\n`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return { usage: result.usage };
|
|
867
|
+
},
|
|
868
|
+
onNewSession: () => {
|
|
869
|
+
(0, context_1.deleteSession)(sessPath);
|
|
870
|
+
},
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
finally {
|
|
874
|
+
sessionLock?.release();
|
|
522
875
|
}
|
|
523
876
|
}
|