@hydra-acp/cli 0.1.42 → 0.1.44

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/dist/cli.js CHANGED
@@ -4106,12 +4106,42 @@ function mapPromptReceived(u) {
4106
4106
  }
4107
4107
  return { kind: "user-text", text: promptText };
4108
4108
  }
4109
+ function isExitPlanModeTool(name) {
4110
+ if (!name) {
4111
+ return false;
4112
+ }
4113
+ const normalised = name.toLowerCase().replace(/[_\s-]/g, "");
4114
+ return normalised === "exitplanmode";
4115
+ }
4116
+ function readExitPlanMarkdown(u) {
4117
+ const rawInput = u.rawInput;
4118
+ if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) {
4119
+ return null;
4120
+ }
4121
+ const plan = rawInput.plan;
4122
+ if (typeof plan !== "string" || plan.length === 0) {
4123
+ return null;
4124
+ }
4125
+ return sanitizeWireText(plan);
4126
+ }
4109
4127
  function mapToolCall(u) {
4110
4128
  const toolCallId = readString(u, "toolCallId") ?? readString(u, "id");
4111
4129
  if (!toolCallId) {
4112
4130
  return null;
4113
4131
  }
4114
4132
  const rawTitle = readString(u, "title") ?? readString(u, "name") ?? readString(u, "label") ?? "tool call";
4133
+ const toolName = readString(u, "name") ?? readString(u, "title");
4134
+ if (isExitPlanModeTool(toolName)) {
4135
+ const plan = readExitPlanMarkdown(u);
4136
+ if (plan !== null) {
4137
+ const status2 = readString(u, "status");
4138
+ const event2 = { kind: "exit-plan-mode", toolCallId, plan };
4139
+ if (status2 !== void 0) {
4140
+ event2.status = status2;
4141
+ }
4142
+ return event2;
4143
+ }
4144
+ }
4115
4145
  const title = sanitizeSingleLine(rawTitle);
4116
4146
  const status = readString(u, "status");
4117
4147
  const rawKind = readString(u, "kind");
@@ -4136,6 +4166,18 @@ function mapToolCallUpdate(u) {
4136
4166
  if (!meaningful) {
4137
4167
  return null;
4138
4168
  }
4169
+ const toolName = readString(u, "name") ?? rawTitle;
4170
+ if (isExitPlanModeTool(toolName)) {
4171
+ const event2 = { kind: "exit-plan-mode", toolCallId };
4172
+ const plan = readExitPlanMarkdown(u);
4173
+ if (plan !== null) {
4174
+ event2.plan = plan;
4175
+ }
4176
+ if (status !== void 0) {
4177
+ event2.status = status;
4178
+ }
4179
+ return event2;
4180
+ }
4139
4181
  const event = { kind: "tool-call-update", toolCallId };
4140
4182
  if (title !== void 0) {
4141
4183
  event.title = title;
@@ -6164,7 +6206,7 @@ function writeStyled(term, text, style) {
6164
6206
  term(text);
6165
6207
  return;
6166
6208
  case "thought":
6167
- term.brightBlack.dim.noFormat(text);
6209
+ term.brightBlack.noFormat(text);
6168
6210
  return;
6169
6211
  case "tool":
6170
6212
  term.brightBlue.noFormat(text);
@@ -6600,6 +6642,8 @@ function emergencyTerminalReset() {
6600
6642
  // SGR mouse mode off
6601
6643
  "\x1B[?1015l",
6602
6644
  // urxvt mouse mode off
6645
+ "\x1B[=0;0w",
6646
+ // MasterBandit selective mouse reporting off
6603
6647
  "\x1B[?2004l",
6604
6648
  // bracketed paste off
6605
6649
  "\x1B[>4;0m",
@@ -6846,6 +6890,13 @@ var init_screen = __esm({
6846
6890
  onProcessExit = null;
6847
6891
  onProcessSignal = null;
6848
6892
  onProcessUncaught = null;
6893
+ // Selective Mouse Reporting (MasterBandit `CSI = w` / `CSI ? w`). Probed
6894
+ // on start() when mouseEnabled is false — terminals that support it let
6895
+ // us receive wheel-up/down without claiming the mouse for clicks (so the
6896
+ // host terminal still does native text selection on click+drag).
6897
+ selectiveMouseSupported = false;
6898
+ selectiveMouseProbing = false;
6899
+ selectiveMouseProbeTimer = null;
6849
6900
  // Last OSC 9;4 state we wrote (3 = indeterminate, 0 = remove). Used to
6850
6901
  // suppress redundant writes when setBanner runs but `status` didn't
6851
6902
  // actually change, and to re-emit on start() if a picker round-trip
@@ -6904,6 +6955,7 @@ var init_screen = __esm({
6904
6955
  }
6905
6956
  this.term.on("resize", this.resizeHandler);
6906
6957
  this.installBracketedPaste();
6958
+ this.installSelectiveMouseReporting();
6907
6959
  this.installEmergencyCleanup();
6908
6960
  this.lastProgressState = 0;
6909
6961
  this.writeProgressIndicator(this.banner.status === "busy" ? 3 : 0);
@@ -6930,6 +6982,7 @@ var init_screen = __esm({
6930
6982
  clearTimeout(this.throttledRepaintTimer);
6931
6983
  this.throttledRepaintTimer = null;
6932
6984
  }
6985
+ this.uninstallSelectiveMouseReporting();
6933
6986
  this.uninstallBracketedPaste();
6934
6987
  this.uninstallEmergencyCleanup();
6935
6988
  this.term.off("key", this.keyHandler);
@@ -6983,6 +7036,34 @@ var init_screen = __esm({
6983
7036
  this.pasteActive = false;
6984
7037
  this.pasteBuffer = "";
6985
7038
  }
7039
+ // Probe for MasterBandit's Selective Mouse Reporting protocol. Sent
7040
+ // unconditionally on terminals that don't recognise it (silently
7041
+ // ignored). A supporting terminal replies with `\x1b[?<b>;<e> w` —
7042
+ // matched in handleRawStdin, which then enables wheel-only reporting.
7043
+ // Skipped when mouseEnabled is true: full mouse capture is already on
7044
+ // via terminal-kit and selective would just be dormant per spec.
7045
+ installSelectiveMouseReporting() {
7046
+ if (this.mouseEnabled || this.selectiveMouseProbing || this.selectiveMouseSupported) {
7047
+ return;
7048
+ }
7049
+ this.selectiveMouseProbing = true;
7050
+ process.stdout.write("\x1B[?w");
7051
+ this.selectiveMouseProbeTimer = setTimeout(() => {
7052
+ this.selectiveMouseProbing = false;
7053
+ this.selectiveMouseProbeTimer = null;
7054
+ }, 250);
7055
+ }
7056
+ uninstallSelectiveMouseReporting() {
7057
+ if (this.selectiveMouseProbeTimer) {
7058
+ clearTimeout(this.selectiveMouseProbeTimer);
7059
+ this.selectiveMouseProbeTimer = null;
7060
+ }
7061
+ this.selectiveMouseProbing = false;
7062
+ if (this.selectiveMouseSupported) {
7063
+ process.stdout.write("\x1B[=0;0w");
7064
+ this.selectiveMouseSupported = false;
7065
+ }
7066
+ }
6986
7067
  installEmergencyCleanup() {
6987
7068
  if (this.emergencyCleanupInstalled) {
6988
7069
  return;
@@ -7025,8 +7106,57 @@ uncaught: ${err.stack ?? err.message}
7025
7106
  this.onProcessUncaught = null;
7026
7107
  }
7027
7108
  }
7109
+ // Strip Selective Mouse Reporting sequences from a stdin chunk and
7110
+ // dispatch them. Returns the chunk with recognised sequences removed
7111
+ // so the remaining text can flow through the existing key/paste
7112
+ // pipeline. Matches:
7113
+ // - `\x1b[?<b>;<e> w` probe reply (any digits, mandatory ' w' suffix)
7114
+ // - `\x1b[<64;<col>;<row>M` wheel-up press
7115
+ // - `\x1b[<65;<col>;<row>M` wheel-down press
7116
+ consumeSelectiveMouseSequences(text) {
7117
+ if (!text.includes("\x1B[")) {
7118
+ return text;
7119
+ }
7120
+ if (this.selectiveMouseProbing) {
7121
+ const probeRe = /\x1b\[\?(\d+);(\d+) w/;
7122
+ const m2 = probeRe.exec(text);
7123
+ if (m2) {
7124
+ this.selectiveMouseProbing = false;
7125
+ if (this.selectiveMouseProbeTimer) {
7126
+ clearTimeout(this.selectiveMouseProbeTimer);
7127
+ this.selectiveMouseProbeTimer = null;
7128
+ }
7129
+ this.selectiveMouseSupported = true;
7130
+ process.stdout.write("\x1B[=24;1w");
7131
+ text = text.slice(0, m2.index) + text.slice(m2.index + m2[0].length);
7132
+ }
7133
+ }
7134
+ if (!this.selectiveMouseSupported) {
7135
+ return text;
7136
+ }
7137
+ const wheelRe = /\x1b\[<(64|65);\d+;\d+M/g;
7138
+ let out = "";
7139
+ let lastEnd = 0;
7140
+ let m;
7141
+ while ((m = wheelRe.exec(text)) !== null) {
7142
+ out += text.slice(lastEnd, m.index);
7143
+ const code = m[1];
7144
+ if (code === "64") {
7145
+ this.scrollBy(3);
7146
+ } else {
7147
+ this.scrollBy(-3);
7148
+ }
7149
+ lastEnd = m.index + m[0].length;
7150
+ }
7151
+ out += text.slice(lastEnd);
7152
+ return out;
7153
+ }
7028
7154
  handleRawStdin(chunk) {
7029
- const text = chunk.toString("binary");
7155
+ let text = chunk.toString("binary");
7156
+ text = this.consumeSelectiveMouseSequences(text);
7157
+ if (text.length === 0) {
7158
+ return;
7159
+ }
7030
7160
  if (this.pasteActive) {
7031
7161
  this.handleRawStdinSegment(text);
7032
7162
  return;
@@ -9155,6 +9285,27 @@ async function pickSession(term, opts) {
9155
9285
  paintIndicator();
9156
9286
  });
9157
9287
  };
9288
+ const repaintDataZone = () => {
9289
+ withSync(() => {
9290
+ term.moveTo(1, headerRow());
9291
+ term.dim.noFormat(` ${headerLine}`);
9292
+ for (let v = 0; v < viewportSize; v++) {
9293
+ const row = headerRow() + 1 + v;
9294
+ const sessionIdx = scrollOffset + v;
9295
+ if (sessionIdx < visible.length) {
9296
+ term.moveTo(1, row);
9297
+ paintSessionRow(sessionIdx);
9298
+ } else {
9299
+ term.moveTo(1, row).eraseLineAfter();
9300
+ }
9301
+ }
9302
+ paintIndicator();
9303
+ if (selectedIdx === 0) {
9304
+ placeComposerCursor();
9305
+ term.hideCursor(false);
9306
+ }
9307
+ });
9308
+ };
9158
9309
  let pasteActive = false;
9159
9310
  let pasteBuffer = "";
9160
9311
  let tkStdinHandler = null;
@@ -9253,6 +9404,7 @@ ${cells}`;
9253
9404
  const refresh = async (preferredId, refreshOpts = {}) => {
9254
9405
  try {
9255
9406
  const beforeKey = refreshOpts.silent ? renderFingerprint() : "";
9407
+ const beforeTotal = total;
9256
9408
  const next = await listSessions(opts.target);
9257
9409
  allSessions = sortSessions(next);
9258
9410
  applyFilter();
@@ -9272,7 +9424,11 @@ ${cells}`;
9272
9424
  if (refreshOpts.silent && renderFingerprint() === beforeKey) {
9273
9425
  return;
9274
9426
  }
9275
- renderFromScratch();
9427
+ if (total === beforeTotal) {
9428
+ repaintDataZone();
9429
+ } else {
9430
+ renderFromScratch();
9431
+ }
9276
9432
  } catch (err) {
9277
9433
  if (refreshOpts.silent) {
9278
9434
  return;
@@ -10757,6 +10913,8 @@ function formatEvent(event) {
10757
10913
  case "tool-call":
10758
10914
  case "tool-call-update":
10759
10915
  return [];
10916
+ case "exit-plan-mode":
10917
+ return [];
10760
10918
  case "plan":
10761
10919
  return formatPlan(event);
10762
10920
  case "mode-changed":
@@ -11079,6 +11237,62 @@ function toolIconStyle(status) {
11079
11237
  return "tool-status-running";
11080
11238
  }
11081
11239
  }
11240
+ function formatExitPlanMode(state) {
11241
+ const lines = [
11242
+ {
11243
+ prefix: "\u25A3 ",
11244
+ prefixStyle: "plan",
11245
+ body: "Plan",
11246
+ bodyStyle: "plan"
11247
+ }
11248
+ ];
11249
+ lines.push(...parseAgentMarkdown(state.plan));
11250
+ const status = state.status;
11251
+ if (status !== void 0) {
11252
+ const footer = exitPlanFooter(status);
11253
+ if (footer !== null) {
11254
+ lines.push(footer);
11255
+ }
11256
+ }
11257
+ return lines;
11258
+ }
11259
+ function exitPlanFooter(status) {
11260
+ switch (status) {
11261
+ case "completed":
11262
+ case "succeeded":
11263
+ case "ok":
11264
+ return {
11265
+ prefix: " ",
11266
+ body: "\u2713 Approved",
11267
+ bodyStyle: "tool-status-ok"
11268
+ };
11269
+ case "failed":
11270
+ case "error":
11271
+ case "rejected":
11272
+ return {
11273
+ prefix: " ",
11274
+ body: "\u2717 Rejected",
11275
+ bodyStyle: "tool-status-fail"
11276
+ };
11277
+ case "cancelled":
11278
+ return {
11279
+ prefix: " ",
11280
+ body: "\u229D Cancelled",
11281
+ bodyStyle: "tool-status-cancelled"
11282
+ };
11283
+ case "pending":
11284
+ case "in_progress":
11285
+ case "running":
11286
+ case "updated":
11287
+ return {
11288
+ prefix: " ",
11289
+ body: "awaiting approval\u2026",
11290
+ bodyStyle: "dim"
11291
+ };
11292
+ default:
11293
+ return null;
11294
+ }
11295
+ }
11082
11296
  function formatPlan(event) {
11083
11297
  const stopped = event.stopped === true;
11084
11298
  const amended = event.amended === true;
@@ -12638,6 +12852,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
12638
12852
  return true;
12639
12853
  case "/clear":
12640
12854
  toolStates.clear();
12855
+ exitPlanStates.clear();
12641
12856
  toolCallOrder.length = 0;
12642
12857
  toolsBlockStartedAt = null;
12643
12858
  toolsBlockEndedAt = null;
@@ -12852,6 +13067,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
12852
13067
  }
12853
13068
  };
12854
13069
  const toolStates = /* @__PURE__ */ new Map();
13070
+ const exitPlanStates = /* @__PURE__ */ new Map();
12855
13071
  const toolCallOrder = [];
12856
13072
  let toolsExpanded = false;
12857
13073
  let toolsBlockStartedAt = null;
@@ -13041,6 +13257,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13041
13257
  screen.clearKey("plan");
13042
13258
  lastPlanEvent = null;
13043
13259
  toolStates.clear();
13260
+ exitPlanStates.clear();
13044
13261
  toolCallOrder.length = 0;
13045
13262
  toolsExpanded = false;
13046
13263
  toolsBlockEndedAt = null;
@@ -13057,6 +13274,23 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13057
13274
  screen.appendStreaming(event.text, "\xB7 ", "thought", "thought");
13058
13275
  return;
13059
13276
  }
13277
+ if (event.kind === "exit-plan-mode") {
13278
+ closeAgentText();
13279
+ const existing = exitPlanStates.get(event.toolCallId);
13280
+ const merged = {
13281
+ plan: event.plan ?? existing?.plan ?? "",
13282
+ status: event.status ?? existing?.status
13283
+ };
13284
+ exitPlanStates.set(event.toolCallId, merged);
13285
+ if (merged.plan.length === 0) {
13286
+ return;
13287
+ }
13288
+ const lines = formatExitPlanMode(merged);
13289
+ if (lines.length > 0) {
13290
+ screen.upsertLines(event.toolCallId, lines);
13291
+ }
13292
+ return;
13293
+ }
13060
13294
  if (event.kind === "tool-call") {
13061
13295
  closeAgentText();
13062
13296
  recordToolCall(event.toolCallId, event.title, event.status, void 0);
@@ -13128,6 +13362,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13128
13362
  ]);
13129
13363
  }
13130
13364
  toolStates.clear();
13365
+ exitPlanStates.clear();
13131
13366
  toolCallOrder.length = 0;
13132
13367
  toolsBlockStartedAt = null;
13133
13368
  toolsBlockEndedAt = null;
@@ -13184,6 +13419,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13184
13419
  renderToolsBlock();
13185
13420
  screen.clearKey("tools");
13186
13421
  toolStates.clear();
13422
+ exitPlanStates.clear();
13187
13423
  toolCallOrder.length = 0;
13188
13424
  toolsBlockStartedAt = null;
13189
13425
  toolsBlockEndedAt = null;
package/dist/index.js CHANGED
@@ -6965,12 +6965,42 @@ function mapPromptReceived(u) {
6965
6965
  }
6966
6966
  return { kind: "user-text", text: promptText };
6967
6967
  }
6968
+ function isExitPlanModeTool(name) {
6969
+ if (!name) {
6970
+ return false;
6971
+ }
6972
+ const normalised = name.toLowerCase().replace(/[_\s-]/g, "");
6973
+ return normalised === "exitplanmode";
6974
+ }
6975
+ function readExitPlanMarkdown(u) {
6976
+ const rawInput = u.rawInput;
6977
+ if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) {
6978
+ return null;
6979
+ }
6980
+ const plan = rawInput.plan;
6981
+ if (typeof plan !== "string" || plan.length === 0) {
6982
+ return null;
6983
+ }
6984
+ return sanitizeWireText(plan);
6985
+ }
6968
6986
  function mapToolCall(u) {
6969
6987
  const toolCallId = readString(u, "toolCallId") ?? readString(u, "id");
6970
6988
  if (!toolCallId) {
6971
6989
  return null;
6972
6990
  }
6973
6991
  const rawTitle = readString(u, "title") ?? readString(u, "name") ?? readString(u, "label") ?? "tool call";
6992
+ const toolName = readString(u, "name") ?? readString(u, "title");
6993
+ if (isExitPlanModeTool(toolName)) {
6994
+ const plan = readExitPlanMarkdown(u);
6995
+ if (plan !== null) {
6996
+ const status2 = readString(u, "status");
6997
+ const event2 = { kind: "exit-plan-mode", toolCallId, plan };
6998
+ if (status2 !== void 0) {
6999
+ event2.status = status2;
7000
+ }
7001
+ return event2;
7002
+ }
7003
+ }
6974
7004
  const title = sanitizeSingleLine(rawTitle);
6975
7005
  const status = readString(u, "status");
6976
7006
  const rawKind = readString(u, "kind");
@@ -6995,6 +7025,18 @@ function mapToolCallUpdate(u) {
6995
7025
  if (!meaningful) {
6996
7026
  return null;
6997
7027
  }
7028
+ const toolName = readString(u, "name") ?? rawTitle;
7029
+ if (isExitPlanModeTool(toolName)) {
7030
+ const event2 = { kind: "exit-plan-mode", toolCallId };
7031
+ const plan = readExitPlanMarkdown(u);
7032
+ if (plan !== null) {
7033
+ event2.plan = plan;
7034
+ }
7035
+ if (status !== void 0) {
7036
+ event2.status = status;
7037
+ }
7038
+ return event2;
7039
+ }
6998
7040
  const event = { kind: "tool-call-update", toolCallId };
6999
7041
  if (title !== void 0) {
7000
7042
  event.title = title;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "description": "Multi-client ACP session daemon: spawn agents, attach over WSS, multiplex sessions across editors.",
5
5
  "license": "MIT",
6
6
  "type": "module",