very-happy-cli 0.2.120 → 0.2.121

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 (35) hide show
  1. package/dist/{AcpBackend-DiIJaJWG.mjs → AcpBackend-BxvQ6O4k.mjs} +1 -1
  2. package/dist/{AcpBackend-tou8FQXI.cjs → AcpBackend-Cdj56ZfU.cjs} +1 -1
  3. package/dist/{AcpSessionManager-fF_HFB98.cjs → AcpSessionManager-DahCU78z.cjs} +51 -11
  4. package/dist/{AcpSessionManager-CZ0Az0GG.mjs → AcpSessionManager-DlWlQVhw.mjs} +51 -11
  5. package/dist/{config-CEbOao_0.cjs → config-DxOhdFEx.cjs} +2 -2
  6. package/dist/{config-CGDxsTZg.mjs → config-osO37dR1.mjs} +2 -2
  7. package/dist/{index-D4_LW69g.mjs → index-BcwAnM9k.mjs} +25 -11
  8. package/dist/{index-HKcuQ_fQ.cjs → index-Bv53Zygv.cjs} +24 -10
  9. package/dist/{index-BlkIz9BQ.cjs → index-DObePs_K.cjs} +143 -44
  10. package/dist/{index-DKj_0dqB.mjs → index-DtZFY8aS.mjs} +2 -2
  11. package/dist/{index-CUJ6faw1.mjs → index-Iz-M3qbz.mjs} +139 -41
  12. package/dist/{index-CYizH-JC.cjs → index-Vjo1jIJH.cjs} +2 -2
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-B3EGNaJr.mjs → installTerminalHooks-Bq11vOqF.mjs} +2 -2
  16. package/dist/{installTerminalHooks-MWVPJDxV.cjs → installTerminalHooks-DehsXLmV.cjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +25 -1
  19. package/dist/lib.d.mts +25 -1
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-CZ1Z6AI5.mjs → mcp-Bx1B3uny.mjs} +2 -2
  22. package/dist/{mcp-DBKk5L6_.cjs → mcp-Cpx9Qlch.cjs} +2 -2
  23. package/dist/{runGemini-C3BCKTMp.mjs → runGemini-CMgudbJs.mjs} +9 -5
  24. package/dist/{runGemini-BRUmjZgV.cjs → runGemini-DI8DipUx.cjs} +9 -5
  25. package/dist/{runOpenClaw-o1ImBlcJ.cjs → runOpenClaw-CetdjTl-.cjs} +3 -3
  26. package/dist/{runOpenClaw-DEYzcGSV.mjs → runOpenClaw-DR3fLtVf.mjs} +3 -3
  27. package/dist/{send-B9fh96vR.cjs → send-Cq0NZCn8.cjs} +2 -2
  28. package/dist/{send-DFGH_-s_.mjs → send-M4b_hD7t.mjs} +2 -2
  29. package/dist/{sessions-CKVfXHyi.mjs → sessions-CelkWhqX.mjs} +11 -4
  30. package/dist/{sessions-BNyEJ2_6.cjs → sessions-DW4lrLOp.cjs} +11 -4
  31. package/dist/{spawn-B6kP4YjA.mjs → spawn-C4fF0q3Z.mjs} +2 -2
  32. package/dist/{spawn-xPV7-s4S.cjs → spawn-D3cwBlWI.cjs} +2 -2
  33. package/dist/{types-7ZSxm6Pi.cjs → types-COOYpI8_.cjs} +135 -10
  34. package/dist/{types-Di0o0BdV.mjs → types-P2cJWD0E.mjs} +134 -10
  35. package/package.json +7 -7
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { l as logger, g as contentLogMetadata, h as errorLogMetadata, j as packageJson, k as delay } from './types-Di0o0BdV.mjs';
4
+ import { l as logger, g as contentLogMetadata, h as errorLogMetadata, j as packageJson, k as delay } from './types-P2cJWD0E.mjs';
5
5
 
6
6
  const DEFAULT_TIMEOUTS = {
7
7
  /** Default initialization timeout: 60 seconds */
@@ -3,7 +3,7 @@
3
3
  var node_child_process = require('node:child_process');
4
4
  var sdk = require('@agentclientprotocol/sdk');
5
5
  var node_crypto = require('node:crypto');
6
- var persistence = require('./types-7ZSxm6Pi.cjs');
6
+ var persistence = require('./types-COOYpI8_.cjs');
7
7
 
8
8
  const DEFAULT_TIMEOUTS = {
9
9
  /** Default initialization timeout: 60 seconds */
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var cuid2 = require('@paralleldrive/cuid2');
4
- var persistence = require('./types-7ZSxm6Pi.cjs');
4
+ var persistence = require('./types-COOYpI8_.cjs');
5
5
 
6
6
  function turnOptions(turnId, time) {
7
7
  return turnId ? { turn: turnId, time } : { time };
@@ -38,11 +38,24 @@ function parseThinkingPayload(payload) {
38
38
  class AcpSessionManager {
39
39
  currentTurnId = null;
40
40
  acpCallToSessionCall = /* @__PURE__ */ new Map();
41
+ stream;
41
42
  /** Monotonic clock: max(lastTime + 1, Date.now()) */
42
43
  lastTime = 0;
43
44
  /** Pending text waiting to be flushed when the stream type changes */
44
45
  pendingText = "";
45
46
  pendingType = null;
47
+ /**
48
+ * Draft identity of the pending block: `<turn id>:<block index>`. There is
49
+ * no API message id on this path, so the turn id plays that role and the
50
+ * index counts blocks within the turn. Null while no turn is open — text
51
+ * outside a turn (pi's startup banner) is persisted as before but never
52
+ * drafted, since the web only paints drafts for a live turn anyway.
53
+ */
54
+ pendingKey = null;
55
+ blockCursor = 0;
56
+ constructor(options = {}) {
57
+ this.stream = options.stream ?? null;
58
+ }
46
59
  nextTime() {
47
60
  this.lastTime = Math.max(this.lastTime + 1, Date.now());
48
61
  return this.lastTime;
@@ -56,8 +69,34 @@ class AcpSessionManager {
56
69
  this.acpCallToSessionCall.set(acpCallId, created);
57
70
  return created;
58
71
  }
72
+ /**
73
+ * Route one streamed delta into the pending block, opening a new block (and
74
+ * flushing the previous one) when the kind changes. Returns the envelopes
75
+ * the switch flushed.
76
+ */
77
+ accumulate(type, text) {
78
+ const flushed = this.pendingType !== type ? this.flush() : [];
79
+ if (this.pendingType === null) {
80
+ this.pendingType = type;
81
+ if (this.currentTurnId && this.stream) {
82
+ this.pendingKey = { mid: this.currentTurnId, idx: this.blockCursor++ };
83
+ this.stream.openBlock(this.pendingKey.mid, this.pendingKey.idx, type === "thinking" ? "thinking" : "text");
84
+ }
85
+ }
86
+ this.pendingText += text;
87
+ if (this.pendingKey && this.stream) {
88
+ this.stream.appendDelta(this.pendingKey.mid, this.pendingKey.idx, text);
89
+ }
90
+ return flushed;
91
+ }
59
92
  flush() {
93
+ const key = this.pendingKey;
94
+ this.pendingKey = null;
95
+ if (key && this.stream) {
96
+ this.stream.closeBlock(key.mid, key.idx);
97
+ }
60
98
  if (!this.pendingText || !this.pendingType) {
99
+ this.pendingType = null;
61
100
  return [];
62
101
  }
63
102
  const text = this.pendingText.replace(/^\n+|\n+$/g, "");
@@ -67,18 +106,25 @@ class AcpSessionManager {
67
106
  if (!text) {
68
107
  return [];
69
108
  }
109
+ const options = {
110
+ ...turnOptions(this.currentTurnId, this.nextTime()),
111
+ ...key ? { streamKey: persistence.streamKeyOf(key.mid, key.idx) } : {}
112
+ };
70
113
  if (type === "thinking") {
71
- return [persistence.createEnvelope("agent", { t: "text", text, thinking: true }, turnOptions(this.currentTurnId, this.nextTime()))];
114
+ return [persistence.createEnvelope("agent", { t: "text", text, thinking: true }, options)];
72
115
  }
73
- return [persistence.createEnvelope("agent", { t: "text", text }, turnOptions(this.currentTurnId, this.nextTime()))];
116
+ return [persistence.createEnvelope("agent", { t: "text", text }, options)];
74
117
  }
75
118
  startTurn() {
76
119
  if (this.currentTurnId) {
77
120
  return [];
78
121
  }
122
+ const flushed = this.flush();
79
123
  this.currentTurnId = cuid2.createId();
80
124
  this.acpCallToSessionCall.clear();
125
+ this.blockCursor = 0;
81
126
  return [
127
+ ...flushed,
82
128
  persistence.createEnvelope("agent", { t: "turn-start" }, { turn: this.currentTurnId, time: this.nextTime() })
83
129
  ];
84
130
  }
@@ -102,10 +148,7 @@ class AcpSessionManager {
102
148
  return [];
103
149
  }
104
150
  if (streaming) {
105
- const flushed = this.pendingType !== "thinking" ? this.flush() : [];
106
- this.pendingType = "thinking";
107
- this.pendingText += text;
108
- return flushed;
151
+ return this.accumulate("thinking", text);
109
152
  }
110
153
  const trimmed = text.replace(/^\n+|\n+$/g, "");
111
154
  if (!trimmed) {
@@ -124,10 +167,7 @@ class AcpSessionManager {
124
167
  if (!text) {
125
168
  return [];
126
169
  }
127
- const flushed = this.pendingType !== "output" ? this.flush() : [];
128
- this.pendingType = "output";
129
- this.pendingText += text;
130
- return flushed;
170
+ return this.accumulate("output", text);
131
171
  }
132
172
  if (msg.type === "tool-call") {
133
173
  const flushed = this.flush();
@@ -1,5 +1,5 @@
1
1
  import { createId } from '@paralleldrive/cuid2';
2
- import { b as createEnvelope } from './types-Di0o0BdV.mjs';
2
+ import { s as streamKeyOf, b as createEnvelope } from './types-P2cJWD0E.mjs';
3
3
 
4
4
  function turnOptions(turnId, time) {
5
5
  return turnId ? { turn: turnId, time } : { time };
@@ -36,11 +36,24 @@ function parseThinkingPayload(payload) {
36
36
  class AcpSessionManager {
37
37
  currentTurnId = null;
38
38
  acpCallToSessionCall = /* @__PURE__ */ new Map();
39
+ stream;
39
40
  /** Monotonic clock: max(lastTime + 1, Date.now()) */
40
41
  lastTime = 0;
41
42
  /** Pending text waiting to be flushed when the stream type changes */
42
43
  pendingText = "";
43
44
  pendingType = null;
45
+ /**
46
+ * Draft identity of the pending block: `<turn id>:<block index>`. There is
47
+ * no API message id on this path, so the turn id plays that role and the
48
+ * index counts blocks within the turn. Null while no turn is open — text
49
+ * outside a turn (pi's startup banner) is persisted as before but never
50
+ * drafted, since the web only paints drafts for a live turn anyway.
51
+ */
52
+ pendingKey = null;
53
+ blockCursor = 0;
54
+ constructor(options = {}) {
55
+ this.stream = options.stream ?? null;
56
+ }
44
57
  nextTime() {
45
58
  this.lastTime = Math.max(this.lastTime + 1, Date.now());
46
59
  return this.lastTime;
@@ -54,8 +67,34 @@ class AcpSessionManager {
54
67
  this.acpCallToSessionCall.set(acpCallId, created);
55
68
  return created;
56
69
  }
70
+ /**
71
+ * Route one streamed delta into the pending block, opening a new block (and
72
+ * flushing the previous one) when the kind changes. Returns the envelopes
73
+ * the switch flushed.
74
+ */
75
+ accumulate(type, text) {
76
+ const flushed = this.pendingType !== type ? this.flush() : [];
77
+ if (this.pendingType === null) {
78
+ this.pendingType = type;
79
+ if (this.currentTurnId && this.stream) {
80
+ this.pendingKey = { mid: this.currentTurnId, idx: this.blockCursor++ };
81
+ this.stream.openBlock(this.pendingKey.mid, this.pendingKey.idx, type === "thinking" ? "thinking" : "text");
82
+ }
83
+ }
84
+ this.pendingText += text;
85
+ if (this.pendingKey && this.stream) {
86
+ this.stream.appendDelta(this.pendingKey.mid, this.pendingKey.idx, text);
87
+ }
88
+ return flushed;
89
+ }
57
90
  flush() {
91
+ const key = this.pendingKey;
92
+ this.pendingKey = null;
93
+ if (key && this.stream) {
94
+ this.stream.closeBlock(key.mid, key.idx);
95
+ }
58
96
  if (!this.pendingText || !this.pendingType) {
97
+ this.pendingType = null;
59
98
  return [];
60
99
  }
61
100
  const text = this.pendingText.replace(/^\n+|\n+$/g, "");
@@ -65,18 +104,25 @@ class AcpSessionManager {
65
104
  if (!text) {
66
105
  return [];
67
106
  }
107
+ const options = {
108
+ ...turnOptions(this.currentTurnId, this.nextTime()),
109
+ ...key ? { streamKey: streamKeyOf(key.mid, key.idx) } : {}
110
+ };
68
111
  if (type === "thinking") {
69
- return [createEnvelope("agent", { t: "text", text, thinking: true }, turnOptions(this.currentTurnId, this.nextTime()))];
112
+ return [createEnvelope("agent", { t: "text", text, thinking: true }, options)];
70
113
  }
71
- return [createEnvelope("agent", { t: "text", text }, turnOptions(this.currentTurnId, this.nextTime()))];
114
+ return [createEnvelope("agent", { t: "text", text }, options)];
72
115
  }
73
116
  startTurn() {
74
117
  if (this.currentTurnId) {
75
118
  return [];
76
119
  }
120
+ const flushed = this.flush();
77
121
  this.currentTurnId = createId();
78
122
  this.acpCallToSessionCall.clear();
123
+ this.blockCursor = 0;
79
124
  return [
125
+ ...flushed,
80
126
  createEnvelope("agent", { t: "turn-start" }, { turn: this.currentTurnId, time: this.nextTime() })
81
127
  ];
82
128
  }
@@ -100,10 +146,7 @@ class AcpSessionManager {
100
146
  return [];
101
147
  }
102
148
  if (streaming) {
103
- const flushed = this.pendingType !== "thinking" ? this.flush() : [];
104
- this.pendingType = "thinking";
105
- this.pendingText += text;
106
- return flushed;
149
+ return this.accumulate("thinking", text);
107
150
  }
108
151
  const trimmed = text.replace(/^\n+|\n+$/g, "");
109
152
  if (!trimmed) {
@@ -122,10 +165,7 @@ class AcpSessionManager {
122
165
  if (!text) {
123
166
  return [];
124
167
  }
125
- const flushed = this.pendingType !== "output" ? this.flush() : [];
126
- this.pendingType = "output";
127
- this.pendingText += text;
128
- return flushed;
168
+ return this.accumulate("output", text);
129
169
  }
130
170
  if (msg.type === "tool-call") {
131
171
  const flushed = this.flush();
@@ -4,8 +4,8 @@ var fs = require('fs');
4
4
  var path = require('path');
5
5
  var os = require('os');
6
6
  var child_process = require('child_process');
7
- var persistence = require('./types-7ZSxm6Pi.cjs');
8
- var index = require('./index-BlkIz9BQ.cjs');
7
+ var persistence = require('./types-COOYpI8_.cjs');
8
+ var index = require('./index-DObePs_K.cjs');
9
9
  require('axios');
10
10
  require('chalk');
11
11
  require('node:util');
@@ -2,8 +2,8 @@ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir } from 'os';
4
4
  import { execSync } from 'child_process';
5
- import { l as logger } from './types-Di0o0BdV.mjs';
6
- import { j as DEFAULT_GEMINI_MODEL, G as GEMINI_MODEL_ENV } from './index-CUJ6faw1.mjs';
5
+ import { l as logger } from './types-P2cJWD0E.mjs';
6
+ import { j as DEFAULT_GEMINI_MODEL, G as GEMINI_MODEL_ENV } from './index-Iz-M3qbz.mjs';
7
7
  import 'axios';
8
8
  import 'chalk';
9
9
  import 'node:util';
@@ -1,10 +1,10 @@
1
- import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-DiIJaJWG.mjs';
2
- export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-DiIJaJWG.mjs';
3
- import { d as connectionState, A as ApiClient, e as readSettings, f as encodeBase64, l as logger, p as projectPath } from './types-Di0o0BdV.mjs';
4
- import { A as AcpSessionManager } from './AcpSessionManager-CZ0Az0GG.mjs';
1
+ import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-BxvQ6O4k.mjs';
2
+ export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-BxvQ6O4k.mjs';
3
+ import { d as connectionState, A as ApiClient, e as readSettings, f as encodeBase64, l as logger, p as projectPath } from './types-P2cJWD0E.mjs';
4
+ import { A as AcpSessionManager } from './AcpSessionManager-DlWlQVhw.mjs';
5
5
  import { randomUUID } from 'node:crypto';
6
6
  import { join } from 'node:path';
7
- import { n as initialMachineMetadata, o as createSessionMetadata, u as setupOfflineReconnection, v as notifyDaemonSessionStarted, x as MessageQueue2, y as hashObject, z as startHappyServer, T as TitleGenerator, B as registerKillSessionHandler, C as BasePermissionHandler, E as writeSessionModeFile, F as normalizeAcpPermissionMode, H as removeSessionModeFile } from './index-CUJ6faw1.mjs';
7
+ import { n as initialMachineMetadata, o as createSessionMetadata, u as setupOfflineReconnection, v as notifyDaemonSessionStarted, x as StreamRelay, y as MessageQueue2, z as hashObject, B as startHappyServer, T as TitleGenerator, C as registerKillSessionHandler, E as BasePermissionHandler, F as writeSessionModeFile, H as normalizeAcpPermissionMode, I as removeSessionModeFile } from './index-Iz-M3qbz.mjs';
8
8
  import 'node:child_process';
9
9
  import '@agentclientprotocol/sdk';
10
10
  import 'axios';
@@ -636,7 +636,11 @@ async function runAcp(opts) {
636
636
  }
637
637
  permissionHandler = new GenericAcpPermissionHandler(session, opts.agentName);
638
638
  permissionHandler.reset("Previous CLI process exited before responding");
639
- const sessionManager = new AcpSessionManager();
639
+ const streamingEnabled = process.env.HAPPY_SESSION_STREAM_DISABLED !== "1";
640
+ const streamRelay = new StreamRelay({
641
+ send: (frame) => session.sendStreamFrame(frame)
642
+ });
643
+ const sessionManager = new AcpSessionManager(streamingEnabled ? { stream: streamRelay } : {});
640
644
  const messageQueue = new MessageQueue2((mode) => hashObject(mode));
641
645
  let currentPermissionMode;
642
646
  let currentModel;
@@ -690,6 +694,13 @@ async function runAcp(opts) {
690
694
  verbose
691
695
  });
692
696
  let thinking = false;
697
+ const setThinking = (next) => {
698
+ if (thinking === next) {
699
+ return;
700
+ }
701
+ thinking = next;
702
+ session.keepAlive(thinking, "remote");
703
+ };
693
704
  let acpSessionId = null;
694
705
  let shouldExit = false;
695
706
  let abortController = new AbortController();
@@ -934,11 +945,6 @@ async function runAcp(opts) {
934
945
  const suffix = msg.detail ? `: ${msg.detail}` : "";
935
946
  const statusLine = `Status: ${msg.status}${suffix}`;
936
947
  logAcp("muted", statusLine);
937
- const nextThinking = msg.status === "running";
938
- if (thinking !== nextThinking) {
939
- thinking = nextThinking;
940
- session.keepAlive(thinking, "remote");
941
- }
942
948
  if (msg.status === "idle") {
943
949
  clearPendingTurn();
944
950
  }
@@ -1051,6 +1057,7 @@ async function runAcp(opts) {
1051
1057
  }
1052
1058
  logAcp("incoming", `Incoming prompt: ${formatUnknownForConsole(batch.message, ACP_EVENT_PREVIEW_CHARS)}`);
1053
1059
  sendEnvelopes(sessionManager.startTurn());
1060
+ setThinking(true);
1054
1061
  const turnEnded = waitForTurnEnd();
1055
1062
  try {
1056
1063
  if (typeof batch.mode.permissionMode === "string" && batch.mode.permissionMode.length > 0) {
@@ -1062,12 +1069,16 @@ async function runAcp(opts) {
1062
1069
  await backend.sendPrompt(acpSessionId, batch.message);
1063
1070
  await turnEnded;
1064
1071
  sendEnvelopes(sessionManager.endTurn("completed"));
1072
+ streamRelay.endTurn();
1073
+ setThinking(false);
1065
1074
  session.sendSessionEvent({ type: "ready" });
1066
1075
  if (verbose) {
1067
1076
  logAcp("muted", `Outgoing prompt completion from ${opts.agentName}`);
1068
1077
  }
1069
1078
  } catch (error) {
1070
1079
  sendEnvelopes(sessionManager.endTurn("failed"));
1080
+ streamRelay.endTurn();
1081
+ setThinking(false);
1071
1082
  session.sendSessionEvent({ type: "ready" });
1072
1083
  logAcp("error", `Prompt error from ${opts.agentName}: ${error instanceof Error ? error.message : String(error)}`);
1073
1084
  clearPendingTurn(error instanceof Error ? error : new Error(String(error)));
@@ -1075,7 +1086,10 @@ async function runAcp(opts) {
1075
1086
  }
1076
1087
  }
1077
1088
  } finally {
1089
+ setThinking(false);
1078
1090
  clearInterval(keepAliveInterval);
1091
+ streamRelay.endTurn();
1092
+ streamRelay.dispose();
1079
1093
  reconnectionHandle?.cancel();
1080
1094
  clearPendingTurn(new Error("ACP runner shutting down"));
1081
1095
  try {
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var AcpBackend = require('./AcpBackend-tou8FQXI.cjs');
4
- var persistence = require('./types-7ZSxm6Pi.cjs');
5
- var AcpSessionManager = require('./AcpSessionManager-fF_HFB98.cjs');
3
+ var AcpBackend = require('./AcpBackend-Cdj56ZfU.cjs');
4
+ var persistence = require('./types-COOYpI8_.cjs');
5
+ var AcpSessionManager = require('./AcpSessionManager-DahCU78z.cjs');
6
6
  var node_crypto = require('node:crypto');
7
7
  var path = require('node:path');
8
- var index = require('./index-BlkIz9BQ.cjs');
8
+ var index = require('./index-DObePs_K.cjs');
9
9
  require('node:child_process');
10
10
  require('@agentclientprotocol/sdk');
11
11
  require('axios');
@@ -637,7 +637,11 @@ async function runAcp(opts) {
637
637
  }
638
638
  permissionHandler = new GenericAcpPermissionHandler(session, opts.agentName);
639
639
  permissionHandler.reset("Previous CLI process exited before responding");
640
- const sessionManager = new AcpSessionManager.AcpSessionManager();
640
+ const streamingEnabled = process.env.HAPPY_SESSION_STREAM_DISABLED !== "1";
641
+ const streamRelay = new index.StreamRelay({
642
+ send: (frame) => session.sendStreamFrame(frame)
643
+ });
644
+ const sessionManager = new AcpSessionManager.AcpSessionManager(streamingEnabled ? { stream: streamRelay } : {});
641
645
  const messageQueue = new index.MessageQueue2((mode) => index.hashObject(mode));
642
646
  let currentPermissionMode;
643
647
  let currentModel;
@@ -691,6 +695,13 @@ async function runAcp(opts) {
691
695
  verbose
692
696
  });
693
697
  let thinking = false;
698
+ const setThinking = (next) => {
699
+ if (thinking === next) {
700
+ return;
701
+ }
702
+ thinking = next;
703
+ session.keepAlive(thinking, "remote");
704
+ };
694
705
  let acpSessionId = null;
695
706
  let shouldExit = false;
696
707
  let abortController = new AbortController();
@@ -935,11 +946,6 @@ async function runAcp(opts) {
935
946
  const suffix = msg.detail ? `: ${msg.detail}` : "";
936
947
  const statusLine = `Status: ${msg.status}${suffix}`;
937
948
  logAcp("muted", statusLine);
938
- const nextThinking = msg.status === "running";
939
- if (thinking !== nextThinking) {
940
- thinking = nextThinking;
941
- session.keepAlive(thinking, "remote");
942
- }
943
949
  if (msg.status === "idle") {
944
950
  clearPendingTurn();
945
951
  }
@@ -1052,6 +1058,7 @@ async function runAcp(opts) {
1052
1058
  }
1053
1059
  logAcp("incoming", `Incoming prompt: ${formatUnknownForConsole(batch.message, ACP_EVENT_PREVIEW_CHARS)}`);
1054
1060
  sendEnvelopes(sessionManager.startTurn());
1061
+ setThinking(true);
1055
1062
  const turnEnded = waitForTurnEnd();
1056
1063
  try {
1057
1064
  if (typeof batch.mode.permissionMode === "string" && batch.mode.permissionMode.length > 0) {
@@ -1063,12 +1070,16 @@ async function runAcp(opts) {
1063
1070
  await backend.sendPrompt(acpSessionId, batch.message);
1064
1071
  await turnEnded;
1065
1072
  sendEnvelopes(sessionManager.endTurn("completed"));
1073
+ streamRelay.endTurn();
1074
+ setThinking(false);
1066
1075
  session.sendSessionEvent({ type: "ready" });
1067
1076
  if (verbose) {
1068
1077
  logAcp("muted", `Outgoing prompt completion from ${opts.agentName}`);
1069
1078
  }
1070
1079
  } catch (error) {
1071
1080
  sendEnvelopes(sessionManager.endTurn("failed"));
1081
+ streamRelay.endTurn();
1082
+ setThinking(false);
1072
1083
  session.sendSessionEvent({ type: "ready" });
1073
1084
  logAcp("error", `Prompt error from ${opts.agentName}: ${error instanceof Error ? error.message : String(error)}`);
1074
1085
  clearPendingTurn(error instanceof Error ? error : new Error(String(error)));
@@ -1076,7 +1087,10 @@ async function runAcp(opts) {
1076
1087
  }
1077
1088
  }
1078
1089
  } finally {
1090
+ setThinking(false);
1079
1091
  clearInterval(keepAliveInterval);
1092
+ streamRelay.endTurn();
1093
+ streamRelay.dispose();
1080
1094
  reconnectionHandle?.cancel();
1081
1095
  clearPendingTurn(new Error("ACP runner shutting down"));
1082
1096
  try {