@ouro.bot/cli 0.1.0-alpha.16 → 0.1.0-alpha.161

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.
Files changed (168) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +3 -2
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +3 -3
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/README.md +187 -191
  5. package/changelog.json +1088 -0
  6. package/dist/heart/active-work.js +633 -0
  7. package/dist/heart/bridges/manager.js +358 -0
  8. package/dist/heart/bridges/state-machine.js +135 -0
  9. package/dist/heart/bridges/store.js +123 -0
  10. package/dist/heart/commitments.js +105 -0
  11. package/dist/heart/config.js +40 -30
  12. package/dist/heart/core.js +608 -123
  13. package/dist/heart/cross-chat-delivery.js +146 -0
  14. package/dist/heart/daemon/agent-discovery.js +81 -0
  15. package/dist/heart/daemon/agent-service.js +360 -0
  16. package/dist/heart/daemon/auth-flow.js +477 -0
  17. package/dist/heart/daemon/cadence.js +70 -0
  18. package/dist/heart/daemon/daemon-cli.js +2467 -205
  19. package/dist/heart/daemon/daemon-entry.js +131 -4
  20. package/dist/heart/daemon/daemon-health.js +136 -0
  21. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  22. package/dist/heart/daemon/daemon-tombstone.js +123 -0
  23. package/dist/heart/daemon/daemon.js +445 -17
  24. package/dist/heart/daemon/habit-migration.js +181 -0
  25. package/dist/heart/daemon/habit-parser.js +119 -0
  26. package/dist/heart/daemon/habit-scheduler.js +369 -0
  27. package/dist/heart/daemon/hatch-animation.js +10 -3
  28. package/dist/heart/daemon/hatch-flow.js +30 -111
  29. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  30. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  31. package/dist/heart/daemon/inner-status.js +89 -0
  32. package/dist/heart/daemon/launchd.js +159 -0
  33. package/dist/heart/daemon/log-tailer.js +4 -3
  34. package/dist/heart/daemon/mcp-server.js +591 -0
  35. package/dist/heart/daemon/message-router.js +17 -8
  36. package/dist/heart/daemon/os-cron-deps.js +134 -0
  37. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  38. package/dist/heart/daemon/ouro-path-installer.js +78 -34
  39. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  40. package/dist/heart/daemon/outlook-http.js +369 -0
  41. package/dist/heart/daemon/outlook-read.js +1281 -0
  42. package/dist/heart/daemon/outlook-render.js +1032 -0
  43. package/dist/heart/daemon/outlook-types.js +7 -0
  44. package/dist/heart/daemon/outlook-view.js +194 -0
  45. package/dist/heart/daemon/process-manager.js +75 -0
  46. package/dist/heart/daemon/run-hooks.js +39 -0
  47. package/dist/heart/daemon/runtime-logging.js +58 -15
  48. package/dist/heart/daemon/runtime-metadata.js +219 -0
  49. package/dist/heart/daemon/runtime-mode.js +67 -0
  50. package/dist/heart/daemon/safe-mode.js +161 -0
  51. package/dist/heart/daemon/sense-manager.js +50 -2
  52. package/dist/heart/daemon/session-id-resolver.js +131 -0
  53. package/dist/heart/daemon/skill-management-installer.js +94 -0
  54. package/dist/heart/daemon/socket-client.js +202 -0
  55. package/dist/heart/daemon/specialist-orchestrator.js +2 -2
  56. package/dist/heart/daemon/specialist-prompt.js +13 -8
  57. package/dist/heart/daemon/specialist-tools.js +53 -4
  58. package/dist/heart/daemon/staged-restart.js +114 -0
  59. package/dist/heart/daemon/task-scheduler.js +3 -25
  60. package/dist/heart/daemon/thoughts.js +507 -0
  61. package/dist/heart/daemon/update-checker.js +111 -0
  62. package/dist/heart/daemon/update-hooks.js +138 -0
  63. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  64. package/dist/heart/delegation.js +62 -0
  65. package/dist/heart/identity.js +136 -55
  66. package/dist/heart/kicks.js +2 -20
  67. package/dist/heart/migrate-config.js +127 -0
  68. package/dist/heart/model-capabilities.js +48 -0
  69. package/dist/heart/obligations.js +197 -0
  70. package/dist/heart/progress-story.js +42 -0
  71. package/dist/heart/provider-failover.js +88 -0
  72. package/dist/heart/provider-ping.js +172 -0
  73. package/dist/heart/providers/anthropic-token.js +163 -0
  74. package/dist/heart/providers/anthropic.js +196 -36
  75. package/dist/heart/providers/azure.js +116 -10
  76. package/dist/heart/providers/github-copilot.js +157 -0
  77. package/dist/heart/providers/minimax.js +33 -3
  78. package/dist/heart/providers/openai-codex.js +51 -16
  79. package/dist/heart/session-activity.js +172 -0
  80. package/dist/heart/session-recall.js +216 -0
  81. package/dist/heart/streaming.js +117 -33
  82. package/dist/heart/target-resolution.js +127 -0
  83. package/dist/heart/tool-activity-callbacks.js +36 -0
  84. package/dist/heart/tool-description.js +135 -0
  85. package/dist/heart/tool-loop.js +200 -0
  86. package/dist/heart/turn-coordinator.js +28 -0
  87. package/dist/mind/associative-recall.js +122 -32
  88. package/dist/mind/bundle-manifest.js +14 -0
  89. package/dist/mind/context.js +125 -14
  90. package/dist/mind/{memory.js → diary.js} +70 -56
  91. package/dist/mind/first-impressions.js +16 -2
  92. package/dist/mind/friends/channel.js +56 -0
  93. package/dist/mind/friends/group-context.js +144 -0
  94. package/dist/mind/friends/store-file.js +19 -0
  95. package/dist/mind/friends/trust-explanation.js +74 -0
  96. package/dist/mind/friends/types.js +8 -0
  97. package/dist/mind/journal-index.js +161 -0
  98. package/dist/mind/obligation-steering.js +221 -0
  99. package/dist/mind/obligations.js +134 -0
  100. package/dist/mind/pending.js +76 -9
  101. package/dist/mind/phrases.js +1 -0
  102. package/dist/mind/prompt.js +651 -91
  103. package/dist/mind/token-estimate.js +8 -12
  104. package/dist/nerves/cli-logging.js +15 -2
  105. package/dist/nerves/coverage/run-artifacts.js +1 -1
  106. package/dist/nerves/event-buffer.js +111 -0
  107. package/dist/nerves/index.js +63 -3
  108. package/dist/nerves/observation.js +20 -0
  109. package/dist/nerves/runtime.js +5 -1
  110. package/dist/outlook-ui/assets/index-CXQG0mVx.js +61 -0
  111. package/dist/outlook-ui/assets/index-Gq7EdfkO.css +1 -0
  112. package/dist/outlook-ui/index.html +15 -0
  113. package/dist/repertoire/ado-semantic.js +6 -0
  114. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  115. package/dist/repertoire/coding/context-pack.js +254 -0
  116. package/dist/repertoire/coding/feedback.js +301 -0
  117. package/dist/repertoire/coding/index.js +4 -1
  118. package/dist/repertoire/coding/manager.js +210 -4
  119. package/dist/repertoire/coding/spawner.js +58 -12
  120. package/dist/repertoire/coding/tools.js +176 -4
  121. package/dist/repertoire/guardrails.js +291 -0
  122. package/dist/repertoire/mcp-client.js +254 -0
  123. package/dist/repertoire/mcp-manager.js +198 -0
  124. package/dist/repertoire/skills.js +14 -23
  125. package/dist/repertoire/tasks/board.js +43 -5
  126. package/dist/repertoire/tasks/fix.js +182 -0
  127. package/dist/repertoire/tasks/index.js +28 -10
  128. package/dist/repertoire/tasks/lifecycle.js +2 -2
  129. package/dist/repertoire/tasks/parser.js +3 -2
  130. package/dist/repertoire/tasks/scanner.js +194 -37
  131. package/dist/repertoire/tasks/transitions.js +11 -28
  132. package/dist/repertoire/tools-base.js +992 -273
  133. package/dist/repertoire/tools-bluebubbles.js +94 -0
  134. package/dist/repertoire/tools-github.js +1 -6
  135. package/dist/repertoire/tools-teams.js +12 -59
  136. package/dist/repertoire/tools.js +281 -101
  137. package/dist/scripts/claude-code-hook.js +41 -0
  138. package/dist/scripts/claude-code-stop-hook.js +47 -0
  139. package/dist/senses/attention-queue.js +97 -0
  140. package/dist/senses/bluebubbles-client.js +258 -5
  141. package/dist/senses/bluebubbles-entry.js +2 -0
  142. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  143. package/dist/senses/bluebubbles-media.js +339 -0
  144. package/dist/senses/bluebubbles-model.js +12 -4
  145. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  146. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  147. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  148. package/dist/senses/bluebubbles.js +1040 -45
  149. package/dist/senses/cli-entry.js +1 -1
  150. package/dist/senses/cli-layout.js +187 -0
  151. package/dist/senses/cli.js +419 -152
  152. package/dist/senses/commands.js +38 -1
  153. package/dist/senses/continuity.js +94 -0
  154. package/dist/senses/habit-turn-message.js +108 -0
  155. package/dist/senses/inner-dialog-worker.js +112 -19
  156. package/dist/senses/inner-dialog.js +592 -84
  157. package/dist/senses/pipeline.js +502 -0
  158. package/dist/senses/shared-turn.js +196 -0
  159. package/dist/senses/surface-tool.js +95 -0
  160. package/dist/senses/teams.js +667 -120
  161. package/dist/senses/trust-gate.js +112 -2
  162. package/package.json +13 -5
  163. package/skills/configure-dev-tools.md +81 -0
  164. package/subagents/README.md +4 -70
  165. package/dist/heart/daemon/subagent-installer.js +0 -134
  166. package/subagents/work-doer.md +0 -233
  167. package/subagents/work-merger.md +0 -624
  168. package/subagents/work-planner.md +0 -373
@@ -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
- process.stderr.write(`\r${this.frames[this.i]} ${this.msg}... `);
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
- let hadReasoning = false;
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
- currentSpinner = null;
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
- currentSpinner = new Spinner(first, pool);
380
+ setSpinner(new Spinner(first, pool));
298
381
  currentSpinner.start();
299
382
  },
300
383
  onModelStreamStart: () => {
301
- currentSpinner?.stop();
302
- currentSpinner = null;
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 (hadReasoning) {
306
- process.stdout.write("\n\n");
307
- hadReasoning = false;
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
- if (rendered)
311
- process.stdout.write(rendered);
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: (text) => {
315
- hadReasoning = true;
316
- process.stdout.write(`\x1b[2m${text}\x1b[0m`);
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
- currentSpinner = new Spinner(first, toolPhrases);
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
- currentSpinner = null;
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
- currentSpinner = null;
441
+ setSpinner(null);
347
442
  }
348
443
  else {
349
444
  currentSpinner?.stop();
350
- currentSpinner = null;
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
- currentSpinner = null;
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
- currentSpinner = null;
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
- process.stdout.write(remaining);
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
- (0, commands_1.registerDefaultCommands)(registry);
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
- // eslint-disable-next-line no-console -- terminal UX: startup banner
385
- console.log(`\n${options.agentName} (type /commands for help)\n`);
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("\r\x1b[K\x1b[36m> \x1b[0m");
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("\x1b[36m> \x1b[0m");
585
+ process.stdout.write(CLI_PROMPT);
420
586
  }
421
587
  else {
422
588
  rl.close();
423
589
  }
424
590
  });
425
- // Debounced line iterator: collects rapid-fire lines (paste) into a single input
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("\x1b[36m> \x1b[0m");
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("\x1b[36m> \x1b[0m");
660
+ process.stdout.write(CLI_PROMPT);
481
661
  continue;
482
662
  }
483
663
  }
484
- // Check for slash commands
485
- const parsed = (0, commands_1.parseSlashCommand)(input);
486
- if (parsed) {
487
- const dispatchResult = registry.dispatch(parsed.command, { channel: "cli" });
488
- if (dispatchResult.handled && dispatchResult.result) {
489
- if (dispatchResult.result.action === "exit") {
490
- break;
491
- }
492
- else if (dispatchResult.result.action === "new") {
493
- messages.length = 0;
494
- messages.push({ role: "system", content: await (0, prompt_1.buildSystem)("cli") });
495
- await options.onNewSession?.();
496
- // eslint-disable-next-line no-console -- terminal UX: session cleared
497
- console.log("session cleared");
498
- process.stdout.write("\x1b[36m> \x1b[0m");
499
- continue;
500
- }
501
- else if (dispatchResult.result.action === "response") {
502
- // eslint-disable-next-line no-console -- terminal UX: command dispatch result
503
- console.log(dispatchResult.result.message || "");
504
- process.stdout.write("\x1b[36m> \x1b[0m");
505
- continue;
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
- const inputLines = input.split("\n");
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
- result = await (0, core_1.runAgent)(messages, cliCallbacks, "cli", currentAbort.signal, {
525
- toolChoiceRequired: getEffectiveToolChoiceRequired(),
526
- traceId,
527
- tools: options.tools,
528
- execTool: wrappedExecTool,
529
- toolContext: options.toolContext,
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("\x1b[36m> \x1b[0m");
765
+ process.stdout.write(CLI_PROMPT);
556
766
  }
557
767
  }
558
768
  finally {
559
769
  rl.close();
560
- // eslint-disable-next-line no-console -- terminal UX: goodbye
561
- console.log("bye");
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", undefined, resolvedContext) }];
616
- // Pending queue drain: inject pending messages as harness-context + assistant-content pairs
617
- const pendingDir = (0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), friendId, "cli", "session");
618
- const drainToMessages = () => {
619
- const pending = (0, pending_1.drainPending)(pendingDir);
620
- if (pending.length === 0)
621
- return 0;
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: (0, identity_1.getAgentName)(),
838
+ agentName: currentAgentName,
636
839
  pasteDebounceMs,
637
840
  messages: sessionMessages,
638
- toolContext: cliToolContext,
639
- onInput: () => {
640
- const trustGate = (0, trust_gate_1.enforceTrustGate)({
641
- friend: resolvedContext.friend,
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
- channel: "cli",
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
- if (!trustGate.allowed) {
647
- return {
648
- allowed: false,
649
- reply: trustGate.reason === "stranger_first_reply" ? trustGate.autoReply : undefined,
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
- return { allowed: true };
653
- },
654
- onTurnEnd: async (msgs, result) => {
655
- (0, context_1.postTurn)(msgs, sessPath, result.usage);
656
- await (0, tokens_1.accumulateFriendTokens)(friendStore, resolvedContext.friend.id, result.usage);
657
- drainToMessages();
658
- await (0, prompt_refresh_1.refreshSystemPrompt)(msgs, "cli", undefined, resolvedContext);
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);