@hydra-acp/cli 0.1.43 → 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;
@@ -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;
@@ -10783,6 +10913,8 @@ function formatEvent(event) {
10783
10913
  case "tool-call":
10784
10914
  case "tool-call-update":
10785
10915
  return [];
10916
+ case "exit-plan-mode":
10917
+ return [];
10786
10918
  case "plan":
10787
10919
  return formatPlan(event);
10788
10920
  case "mode-changed":
@@ -11105,6 +11237,62 @@ function toolIconStyle(status) {
11105
11237
  return "tool-status-running";
11106
11238
  }
11107
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
+ }
11108
11296
  function formatPlan(event) {
11109
11297
  const stopped = event.stopped === true;
11110
11298
  const amended = event.amended === true;
@@ -12664,6 +12852,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
12664
12852
  return true;
12665
12853
  case "/clear":
12666
12854
  toolStates.clear();
12855
+ exitPlanStates.clear();
12667
12856
  toolCallOrder.length = 0;
12668
12857
  toolsBlockStartedAt = null;
12669
12858
  toolsBlockEndedAt = null;
@@ -12878,6 +13067,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
12878
13067
  }
12879
13068
  };
12880
13069
  const toolStates = /* @__PURE__ */ new Map();
13070
+ const exitPlanStates = /* @__PURE__ */ new Map();
12881
13071
  const toolCallOrder = [];
12882
13072
  let toolsExpanded = false;
12883
13073
  let toolsBlockStartedAt = null;
@@ -13067,6 +13257,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13067
13257
  screen.clearKey("plan");
13068
13258
  lastPlanEvent = null;
13069
13259
  toolStates.clear();
13260
+ exitPlanStates.clear();
13070
13261
  toolCallOrder.length = 0;
13071
13262
  toolsExpanded = false;
13072
13263
  toolsBlockEndedAt = null;
@@ -13083,6 +13274,23 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13083
13274
  screen.appendStreaming(event.text, "\xB7 ", "thought", "thought");
13084
13275
  return;
13085
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
+ }
13086
13294
  if (event.kind === "tool-call") {
13087
13295
  closeAgentText();
13088
13296
  recordToolCall(event.toolCallId, event.title, event.status, void 0);
@@ -13154,6 +13362,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13154
13362
  ]);
13155
13363
  }
13156
13364
  toolStates.clear();
13365
+ exitPlanStates.clear();
13157
13366
  toolCallOrder.length = 0;
13158
13367
  toolsBlockStartedAt = null;
13159
13368
  toolsBlockEndedAt = null;
@@ -13210,6 +13419,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
13210
13419
  renderToolsBlock();
13211
13420
  screen.clearKey("tools");
13212
13421
  toolStates.clear();
13422
+ exitPlanStates.clear();
13213
13423
  toolCallOrder.length = 0;
13214
13424
  toolsBlockStartedAt = null;
13215
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.43",
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",