@testchimp/cli 0.1.59 → 0.1.61

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.
@@ -1081,6 +1081,34 @@ function runOpencode(prompt, model, childEnv, opencodeSessionId, callbacks, atta
1081
1081
  const textByPartId = new Map();
1082
1082
  const partTypeById = new Map();
1083
1083
  let sawStdout = false;
1084
+ let cancelEscalationTimer = null;
1085
+ let cancelWatcher = null;
1086
+ const clearCancelWatcher = () => {
1087
+ if (cancelWatcher) {
1088
+ clearInterval(cancelWatcher);
1089
+ cancelWatcher = null;
1090
+ }
1091
+ if (cancelEscalationTimer) {
1092
+ clearTimeout(cancelEscalationTimer);
1093
+ cancelEscalationTimer = null;
1094
+ }
1095
+ };
1096
+ const killActiveChild = (signal) => {
1097
+ try {
1098
+ if (!child.killed)
1099
+ child.kill(signal);
1100
+ }
1101
+ catch {
1102
+ /* ignore */
1103
+ }
1104
+ };
1105
+ cancelWatcher = setInterval(() => {
1106
+ if (!callbacks.getCancelRequested?.())
1107
+ return;
1108
+ clearCancelWatcher();
1109
+ killActiveChild("SIGTERM");
1110
+ cancelEscalationTimer = setTimeout(() => killActiveChild("SIGKILL"), 2000);
1111
+ }, 500);
1084
1112
  let progressTicker = setInterval(() => {
1085
1113
  if (sawStdout) {
1086
1114
  if (progressTicker) {
@@ -1236,6 +1264,7 @@ function runOpencode(prompt, model, childEnv, opencodeSessionId, callbacks, atta
1236
1264
  });
1237
1265
  child.on("close", (code) => {
1238
1266
  callbacks.onActiveChild?.(null);
1267
+ clearCancelWatcher();
1239
1268
  if (progressTicker) {
1240
1269
  clearInterval(progressTicker);
1241
1270
  progressTicker = null;
@@ -1546,9 +1575,7 @@ export async function runChimphands(opts) {
1546
1575
  syncLiveSse(attached);
1547
1576
  })
1548
1577
  : () => { };
1549
- const stopTunnel = runtimeId && attachUrl
1550
- ? startTunnelWorker(backend, apiKey, runtimeId, attachUrl)
1551
- : () => { };
1578
+ let stopTunnel = () => { };
1552
1579
  const userId = bootStr(boot, "chimphands_service_account_user_id", "chimphandsServiceAccountUserId");
1553
1580
  if (userId) {
1554
1581
  process.env.TESTCHIMP_USER_ID = userId;
@@ -1634,6 +1661,13 @@ export async function runChimphands(opts) {
1634
1661
  // Polling is best-effort when inbound SSE misses an event.
1635
1662
  }
1636
1663
  };
1664
+ const seenWorktreeWriteRequestIds = new Set();
1665
+ const handleUserFileEdit = (edit) => {
1666
+ if (seenWorktreeWriteRequestIds.has(edit.requestId))
1667
+ return;
1668
+ seenWorktreeWriteRequestIds.add(edit.requestId);
1669
+ void applyUserFileEdit(backend, apiKey, sessionId, edit, () => agentTurnInProgress);
1670
+ };
1637
1671
  const postEvent = (role, content, opts) => {
1638
1672
  const bodyOpts = { ...opts };
1639
1673
  if (opencodeSessionId && !bodyOpts.opencodeSessionId) {
@@ -1669,28 +1703,42 @@ export async function runChimphands(opts) {
1669
1703
  };
1670
1704
  if (userId)
1671
1705
  childEnv.TESTCHIMP_USER_ID = userId;
1706
+ const handleCancelTurn = () => {
1707
+ cancelTurnRequested = true;
1708
+ const ch = activeOpencodeChild;
1709
+ if (ch && !ch.killed) {
1710
+ try {
1711
+ ch.kill("SIGTERM");
1712
+ setTimeout(() => {
1713
+ try {
1714
+ if (!ch.killed)
1715
+ ch.kill("SIGKILL");
1716
+ }
1717
+ catch {
1718
+ /* ignore */
1719
+ }
1720
+ }, 2000);
1721
+ }
1722
+ catch {
1723
+ /* ignore */
1724
+ }
1725
+ }
1726
+ };
1672
1727
  const stopInbound = connectInboundStream(backend, apiKey, sessionId, {
1673
1728
  onUserMessage: enqueueUserMessage,
1674
1729
  onIdle: () => {
1675
1730
  idle = true;
1676
1731
  },
1677
- onCancelTurn: () => {
1678
- cancelTurnRequested = true;
1679
- const ch = activeOpencodeChild;
1680
- if (ch && !ch.killed) {
1681
- try {
1682
- ch.kill("SIGTERM");
1683
- }
1684
- catch {
1685
- /* ignore */
1686
- }
1687
- }
1688
- },
1689
- onUserFileEdit: (edit) => {
1690
- void applyUserFileEdit(backend, apiKey, sessionId, edit, () => agentTurnInProgress);
1691
- },
1732
+ onCancelTurn: handleCancelTurn,
1733
+ onUserFileEdit: handleUserFileEdit,
1692
1734
  shouldRun: () => sessionActive,
1693
1735
  });
1736
+ if (runtimeId && attachUrl) {
1737
+ stopTunnel = startTunnelWorker(backend, apiKey, runtimeId, attachUrl, sessionId, {
1738
+ onUserFileEdit: handleUserFileEdit,
1739
+ onCancelTurn: handleCancelTurn,
1740
+ });
1741
+ }
1694
1742
  const shutdownRuntime = async () => {
1695
1743
  sessionActive = false;
1696
1744
  stopInbound();
@@ -1970,7 +2018,7 @@ async function commitAndPushDirtyWorktree(message) {
1970
2018
  console.error(`ChimpHands commit-before-idle failed: ${err instanceof Error ? err.message : String(err)}`);
1971
2019
  }
1972
2020
  }
1973
- function startTunnelWorker(backend, apiKey, runtimeId, attachUrl) {
2021
+ function startTunnelWorker(backend, apiKey, runtimeId, attachUrl, sessionId, handlers) {
1974
2022
  let stopped = false;
1975
2023
  const base = attachUrl.replace(/\/$/, "");
1976
2024
  let ws = null;
@@ -2101,6 +2149,29 @@ function startTunnelWorker(backend, apiKey, runtimeId, attachUrl) {
2101
2149
  socket.send(JSON.stringify({ type: "pong" }));
2102
2150
  return;
2103
2151
  }
2152
+ if (frame.type === "inbound_event" && frame.event === "cancel_turn") {
2153
+ const payload = frame.data;
2154
+ if (payload?.sessionId && payload.sessionId !== sessionId) {
2155
+ return;
2156
+ }
2157
+ handlers?.onCancelTurn?.();
2158
+ return;
2159
+ }
2160
+ if (frame.type === "inbound_event" && frame.event === "user_file_edit" && frame.data) {
2161
+ const edit = frame.data;
2162
+ if (edit.sessionId && edit.sessionId !== sessionId) {
2163
+ return;
2164
+ }
2165
+ const requestId = edit.requestId || edit.request_id;
2166
+ if (requestId && edit.path) {
2167
+ handlers?.onUserFileEdit?.({
2168
+ requestId,
2169
+ path: edit.path,
2170
+ content: edit.content ?? "",
2171
+ });
2172
+ }
2173
+ return;
2174
+ }
2104
2175
  if (frame.type === "http_request" || frame.requestId) {
2105
2176
  void handleHttpRequest(socket, frame);
2106
2177
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testchimp/cli",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
4
4
  "description": "TestChimp CLI and MCP server — coverage, plans, EaaS, TrueCoverage, API operations (calls /api/mcp/*)",
5
5
  "type": "module",
6
6
  "main": "dist/bin/testchimp.js",