@wrongstack/acp 0.295.1 → 0.296.2

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/client.js CHANGED
@@ -117,6 +117,9 @@ var ClientTransport = class {
117
117
  };
118
118
  const waitForMarker = (chunk) => {
119
119
  this.buffer += chunk;
120
+ if (this.buffer.length > this.maxFrameChars) {
121
+ this.buffer = this.buffer.slice(-this.maxFrameChars);
122
+ }
120
123
  const idx = this.buffer.indexOf("[wstack-acp]\n");
121
124
  if (idx !== -1) {
122
125
  this.buffer = this.buffer.slice(idx + "[wstack-acp]\n".length);
@@ -233,6 +236,39 @@ function verbatimOptions(invocation) {
233
236
  // src/types/acp-v1.ts
234
237
  var ACP_PROTOCOL_VERSION = 1;
235
238
 
239
+ // src/client/acp-session-content.ts
240
+ function textContent(text) {
241
+ return { type: "text", text };
242
+ }
243
+ function imageContent(mimeType, data) {
244
+ return { type: "image", mimeType, data };
245
+ }
246
+ function audioContent(mimeType, data) {
247
+ return { type: "audio", mimeType, data };
248
+ }
249
+ function extractText(block) {
250
+ if (typeof block !== "object" || block === null) return "";
251
+ const b = block;
252
+ if (b.type === "text" && typeof b.text === "string") return b.text;
253
+ if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
254
+ return b.resource.text;
255
+ }
256
+ return "";
257
+ }
258
+ function isRecord(v) {
259
+ return typeof v === "object" && v !== null && !Array.isArray(v);
260
+ }
261
+ function emptyRunResult(stopReason) {
262
+ return {
263
+ text: "",
264
+ stopReason,
265
+ hasText: false,
266
+ toolCalls: [],
267
+ diffs: [],
268
+ thoughts: ""
269
+ };
270
+ }
271
+
236
272
  // src/client/file-server.ts
237
273
  import { randomBytes } from "node:crypto";
238
274
  import { realpathSync } from "node:fs";
@@ -908,7 +944,7 @@ function finitePositiveLimit(value, fallback) {
908
944
  return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
909
945
  }
910
946
 
911
- // src/client/acp-session.ts
947
+ // src/client/acp-session-errors.ts
912
948
  var ACPSessionError = class extends Error {
913
949
  kind;
914
950
  cause;
@@ -922,6 +958,264 @@ var ACPSessionError = class extends Error {
922
958
  function isJsonRpcError(v) {
923
959
  return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
924
960
  }
961
+
962
+ // src/client/acp-session-updates.ts
963
+ function createSessionScratch() {
964
+ return { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
965
+ }
966
+ function handleAcpSessionUpdate(msg, scratch, emitProgress) {
967
+ const update = msg.params?.update;
968
+ if (typeof update !== "object" || update === null) return;
969
+ const u = update;
970
+ emitProgress({ type: "raw", update: u });
971
+ switch (u.sessionUpdate) {
972
+ case "agent_message_chunk": {
973
+ const text = extractText(u.content);
974
+ if (text) {
975
+ scratch.text += text;
976
+ emitProgress({ type: "message", text });
977
+ }
978
+ return;
979
+ }
980
+ case "thought_chunk": {
981
+ const text = extractText(u.content);
982
+ if (text) {
983
+ scratch.thoughts += text;
984
+ emitProgress({ type: "thought", text });
985
+ }
986
+ return;
987
+ }
988
+ case "tool_call":
989
+ case "tool_call_update":
990
+ captureToolCall(u, u.sessionUpdate === "tool_call", scratch, emitProgress);
991
+ return;
992
+ case "plan":
993
+ if (Array.isArray(u.entries)) {
994
+ scratch.plan = u.entries;
995
+ emitProgress({ type: "plan", entries: u.entries });
996
+ }
997
+ return;
998
+ case "usage_update":
999
+ if (typeof u.used === "number" && typeof u.size === "number") {
1000
+ const usage = {
1001
+ used: u.used,
1002
+ size: u.size,
1003
+ ...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
1004
+ };
1005
+ scratch.usage = usage;
1006
+ emitProgress({ type: "usage", usage });
1007
+ }
1008
+ return;
1009
+ case "available_commands_update":
1010
+ case "current_mode_update":
1011
+ case "config_option_update":
1012
+ case "session_info_update":
1013
+ case "user_message_chunk":
1014
+ case "next_edit_suggestions":
1015
+ case "elicitation":
1016
+ return;
1017
+ default:
1018
+ return;
1019
+ }
1020
+ }
1021
+ function captureToolCall(u, isNew, scratch, emitProgress) {
1022
+ const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
1023
+ if (!toolCallId) return;
1024
+ const prev = scratch.toolCalls.get(toolCallId);
1025
+ const record = {
1026
+ toolCallId,
1027
+ title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
1028
+ kind: typeof u.kind === "string" ? u.kind : prev?.kind,
1029
+ status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
1030
+ rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
1031
+ rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
1032
+ };
1033
+ scratch.toolCalls.set(toolCallId, record);
1034
+ if (Array.isArray(u.content)) {
1035
+ for (const c of u.content) {
1036
+ if (c && typeof c === "object" && c.type === "diff") {
1037
+ const diff = {
1038
+ path: c.path,
1039
+ oldText: c.oldText,
1040
+ newText: c.newText
1041
+ };
1042
+ scratch.diffs.push(diff);
1043
+ emitProgress({ type: "diff", diff });
1044
+ }
1045
+ }
1046
+ }
1047
+ emitProgress({
1048
+ type: isNew ? "tool_call" : "tool_call_update",
1049
+ toolCall: record
1050
+ });
1051
+ }
1052
+
1053
+ // src/client/acp-session-callbacks.ts
1054
+ async function handleAcpPermissionRequest(msg, permissionPolicy, sender) {
1055
+ const id = msg.id;
1056
+ if (id === void 0) return;
1057
+ const params = msg.params;
1058
+ const toolCall = params?.toolCall;
1059
+ const options = Array.isArray(params?.options) ? params.options : [];
1060
+ if (!toolCall) {
1061
+ await sender.sendErrorResponse(id, -32602, "toolCall is required");
1062
+ return;
1063
+ }
1064
+ const policyAbort = new AbortController();
1065
+ try {
1066
+ const outcome = await permissionPolicy({
1067
+ toolCall,
1068
+ options,
1069
+ signal: policyAbort.signal
1070
+ });
1071
+ await sender.sendResult(id, { outcome });
1072
+ } catch (err) {
1073
+ const message = err instanceof Error ? err.message : String(err);
1074
+ await sender.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
1075
+ }
1076
+ }
1077
+ async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender) {
1078
+ const id = msg.id;
1079
+ if (id === void 0) return;
1080
+ const params = msg.params;
1081
+ if (!params?.path) {
1082
+ await sender.sendErrorResponse(id, -32602, "path is required");
1083
+ return;
1084
+ }
1085
+ if (msg.method === "fs/write_text_file") {
1086
+ const allowed = await authorizeAcpCallback(permissionPolicy, {
1087
+ toolCallId: `acp-fs-write-${id}`,
1088
+ title: `Write file: ${params.path}`,
1089
+ kind: "edit",
1090
+ rawInput: { path: params.path, sessionId: params.sessionId }
1091
+ });
1092
+ if (!allowed) {
1093
+ await sender.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
1094
+ return;
1095
+ }
1096
+ }
1097
+ try {
1098
+ if (msg.method === "fs/read_text_file") {
1099
+ const result = await fileServer.readTextFile({
1100
+ sessionId: params.sessionId ?? "",
1101
+ path: params.path
1102
+ });
1103
+ await sender.sendResult(id, result);
1104
+ } else {
1105
+ await fileServer.writeTextFile({
1106
+ sessionId: params.sessionId ?? "",
1107
+ path: params.path,
1108
+ content: params.content ?? ""
1109
+ });
1110
+ await sender.sendResult(id, {});
1111
+ }
1112
+ } catch (err) {
1113
+ const code = err instanceof FsError ? -32602 : -32603;
1114
+ const message = err instanceof Error ? err.message : String(err);
1115
+ await sender.sendErrorResponse(id, code, message);
1116
+ }
1117
+ }
1118
+ async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, sender) {
1119
+ const id = msg.id;
1120
+ if (id === void 0) return;
1121
+ const params = msg.params ?? {};
1122
+ try {
1123
+ switch (msg.method) {
1124
+ case "terminal/create": {
1125
+ const allowed = await authorizeAcpCallback(permissionPolicy, {
1126
+ toolCallId: `acp-terminal-create-${id}`,
1127
+ title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
1128
+ kind: "execute",
1129
+ rawInput: {
1130
+ command: params.command,
1131
+ args: params.args,
1132
+ cwd: params.cwd,
1133
+ sessionId: params.sessionId
1134
+ }
1135
+ });
1136
+ if (!allowed) {
1137
+ await sender.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
1138
+ return;
1139
+ }
1140
+ const createOpts = {
1141
+ sessionId: String(params.sessionId ?? ""),
1142
+ command: String(params.command ?? ""),
1143
+ args: Array.isArray(params.args) ? params.args : []
1144
+ };
1145
+ if (Array.isArray(params.env)) {
1146
+ createOpts.env = params.env;
1147
+ }
1148
+ if (typeof params.cwd === "string") {
1149
+ createOpts.cwd = params.cwd;
1150
+ }
1151
+ if (typeof params.outputByteLimit === "number") {
1152
+ createOpts.outputByteLimit = params.outputByteLimit;
1153
+ }
1154
+ const result = terminalServer.create(createOpts);
1155
+ await sender.sendResult(id, result);
1156
+ return;
1157
+ }
1158
+ case "terminal/output": {
1159
+ const terminalId = String(params.terminalId ?? "");
1160
+ const out = terminalServer.output(terminalId);
1161
+ await sender.sendResult(id, out);
1162
+ return;
1163
+ }
1164
+ case "terminal/wait_for_exit": {
1165
+ const terminalId = String(params.terminalId ?? "");
1166
+ const exit = await terminalServer.waitForExit(terminalId);
1167
+ await sender.sendResult(id, exit);
1168
+ return;
1169
+ }
1170
+ case "terminal/kill": {
1171
+ const terminalId = String(params.terminalId ?? "");
1172
+ terminalServer.kill(terminalId);
1173
+ await sender.sendResult(id, {});
1174
+ return;
1175
+ }
1176
+ case "terminal/release": {
1177
+ const terminalId = String(params.terminalId ?? "");
1178
+ terminalServer.release(terminalId);
1179
+ await sender.sendResult(id, {});
1180
+ return;
1181
+ }
1182
+ default:
1183
+ await sender.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
1184
+ }
1185
+ } catch (err) {
1186
+ const message = err instanceof Error ? err.message : String(err);
1187
+ await sender.sendErrorResponse(id, -32603, message);
1188
+ }
1189
+ }
1190
+ async function authorizeAcpCallback(permissionPolicy, partial) {
1191
+ try {
1192
+ const outcome = await permissionPolicy({
1193
+ toolCall: {
1194
+ sessionUpdate: "tool_call_update",
1195
+ toolCallId: partial.toolCallId,
1196
+ title: partial.title,
1197
+ kind: partial.kind,
1198
+ status: "pending",
1199
+ ...partial.rawInput ? { rawInput: partial.rawInput } : {}
1200
+ },
1201
+ options: [
1202
+ { optionId: "allow", name: "Allow", kind: "allow_once" },
1203
+ { optionId: "reject", name: "Reject", kind: "reject_once" }
1204
+ ],
1205
+ signal: new AbortController().signal
1206
+ });
1207
+ return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
1208
+ } catch {
1209
+ return false;
1210
+ }
1211
+ }
1212
+
1213
+ // src/client/acp-message-routing.ts
1214
+ function isBestEffortAckMethod(method) {
1215
+ return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
1216
+ }
1217
+
1218
+ // src/client/acp-session.ts
925
1219
  var ACPSession = class _ACPSession {
926
1220
  transport;
927
1221
  fileServer;
@@ -1615,6 +1909,12 @@ var ACPSession = class _ACPSession {
1615
1909
  error: { code, message }
1616
1910
  });
1617
1911
  }
1912
+ responseSender() {
1913
+ return {
1914
+ sendResult: (id, result) => this.sendResult(id, result),
1915
+ sendErrorResponse: (id, code, message) => this.sendErrorResponse(id, code, message)
1916
+ };
1917
+ }
1618
1918
  handleMessage(msg) {
1619
1919
  if (msg.id !== void 0 && (msg.result !== void 0 || msg.error !== void 0)) {
1620
1920
  const pending = this.pending.get(msg.id);
@@ -1629,29 +1929,32 @@ var ACPSession = class _ACPSession {
1629
1929
  return;
1630
1930
  }
1631
1931
  if (msg.method === "session/update") {
1632
- this.handleUpdate(msg);
1932
+ handleAcpSessionUpdate(msg, this.scratch, (event) => this.emitProgress(event));
1633
1933
  return;
1634
1934
  }
1635
1935
  if (msg.method === "session/request_permission") {
1636
- void this.handlePermissionRequest(msg);
1936
+ void handleAcpPermissionRequest(msg, this.permissionPolicy, this.responseSender());
1637
1937
  return;
1638
1938
  }
1639
1939
  if (msg.method === "fs/read_text_file" || msg.method === "fs/write_text_file") {
1640
- void this.handleFsRequest(msg);
1940
+ void handleAcpFsRequest(
1941
+ msg,
1942
+ this.fileServer,
1943
+ this.permissionPolicy,
1944
+ this.responseSender()
1945
+ );
1641
1946
  return;
1642
1947
  }
1643
1948
  if (msg.method?.startsWith("terminal/")) {
1644
- void this.handleTerminalRequest(msg);
1645
- return;
1646
- }
1647
- if (msg.method === "mcp/connect" || msg.method === "mcp/message" || msg.method === "mcp/disconnect") {
1648
- if (msg.id !== void 0) {
1649
- this.sendResult(msg.id, {}).catch(() => {
1650
- });
1651
- }
1949
+ void handleAcpTerminalRequest(
1950
+ msg,
1951
+ this.terminalServer,
1952
+ this.permissionPolicy,
1953
+ this.responseSender()
1954
+ );
1652
1955
  return;
1653
1956
  }
1654
- if (msg.method === "elicitation/create" || msg.method === "elicitation/complete") {
1957
+ if (isBestEffortAckMethod(msg.method)) {
1655
1958
  if (msg.id !== void 0) {
1656
1959
  this.sendResult(msg.id, {}).catch(() => {
1657
1960
  });
@@ -1672,98 +1975,6 @@ var ACPSession = class _ACPSession {
1672
1975
  );
1673
1976
  }
1674
1977
  }
1675
- handleUpdate(msg) {
1676
- const update = msg.params?.update;
1677
- if (typeof update !== "object" || update === null) return;
1678
- const u = update;
1679
- this.emitProgress({ type: "raw", update: u });
1680
- switch (u.sessionUpdate) {
1681
- case "agent_message_chunk": {
1682
- const text = extractText(u.content);
1683
- if (text) {
1684
- this.scratch.text += text;
1685
- this.emitProgress({ type: "message", text });
1686
- }
1687
- return;
1688
- }
1689
- case "thought_chunk": {
1690
- const text = extractText(u.content);
1691
- if (text) {
1692
- this.scratch.thoughts += text;
1693
- this.emitProgress({ type: "thought", text });
1694
- }
1695
- return;
1696
- }
1697
- case "tool_call":
1698
- case "tool_call_update": {
1699
- this.captureToolCall(u, u.sessionUpdate === "tool_call");
1700
- return;
1701
- }
1702
- case "plan":
1703
- if (Array.isArray(u.entries)) {
1704
- this.scratch.plan = u.entries;
1705
- this.emitProgress({ type: "plan", entries: u.entries });
1706
- }
1707
- return;
1708
- case "usage_update":
1709
- if (typeof u.used === "number" && typeof u.size === "number") {
1710
- const usage = {
1711
- used: u.used,
1712
- size: u.size,
1713
- ...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
1714
- };
1715
- this.scratch.usage = usage;
1716
- this.emitProgress({ type: "usage", usage });
1717
- }
1718
- return;
1719
- case "available_commands_update":
1720
- case "current_mode_update":
1721
- case "config_option_update":
1722
- case "session_info_update":
1723
- case "user_message_chunk":
1724
- case "next_edit_suggestions":
1725
- case "elicitation":
1726
- return;
1727
- default:
1728
- return;
1729
- }
1730
- }
1731
- /**
1732
- * Fold a `tool_call` / `tool_call_update` notification into the scratch
1733
- * tool-call map (deduped by toolCallId), extract any `diff` content into
1734
- * the diffs list, and emit live progress.
1735
- */
1736
- captureToolCall(u, isNew) {
1737
- const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
1738
- if (!toolCallId) return;
1739
- const prev = this.scratch.toolCalls.get(toolCallId);
1740
- const record = {
1741
- toolCallId,
1742
- title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
1743
- kind: typeof u.kind === "string" ? u.kind : prev?.kind,
1744
- status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
1745
- rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
1746
- rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
1747
- };
1748
- this.scratch.toolCalls.set(toolCallId, record);
1749
- if (Array.isArray(u.content)) {
1750
- for (const c of u.content) {
1751
- if (c && typeof c === "object" && c.type === "diff") {
1752
- const diff = {
1753
- path: c.path,
1754
- oldText: c.oldText,
1755
- newText: c.newText
1756
- };
1757
- this.scratch.diffs.push(diff);
1758
- this.emitProgress({ type: "diff", diff });
1759
- }
1760
- }
1761
- }
1762
- this.emitProgress({
1763
- type: isNew ? "tool_call" : "tool_call_update",
1764
- toolCall: record
1765
- });
1766
- }
1767
1978
  emitProgress(event) {
1768
1979
  if (!this.progressHandler) return;
1769
1980
  try {
@@ -1774,217 +1985,11 @@ var ACPSession = class _ACPSession {
1774
1985
  /** Live progress handler installed for the duration of a `prompt()` turn. */
1775
1986
  progressHandler = null;
1776
1987
  // Per-prompt scratch state
1777
- scratch = { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
1988
+ scratch = createSessionScratch();
1778
1989
  resetScratch() {
1779
- this.scratch = { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
1780
- }
1781
- async handlePermissionRequest(msg) {
1782
- const id = msg.id;
1783
- if (id === void 0) return;
1784
- const params = msg.params;
1785
- const toolCall = params?.toolCall;
1786
- const options = Array.isArray(params?.options) ? params.options : [];
1787
- if (!toolCall) {
1788
- await this.sendErrorResponse(id, -32602, "toolCall is required");
1789
- return;
1790
- }
1791
- const policyAbort = new AbortController();
1792
- try {
1793
- const outcome = await this.permissionPolicy({
1794
- toolCall,
1795
- options,
1796
- signal: policyAbort.signal
1797
- });
1798
- await this.sendResult(id, { outcome });
1799
- } catch (err) {
1800
- const message = err instanceof Error ? err.message : String(err);
1801
- await this.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
1802
- }
1803
- }
1804
- /**
1805
- * Enforce authorization at privileged callback sinks (fs/write,
1806
- * terminal/create). Unlike `handlePermissionRequest` which responds to
1807
- * agent-initiated `session/request_permission` messages, this method is
1808
- * called by the handler BEFORE dispatching to FileServer/TerminalServer,
1809
- * closing the gap where the agent simply skips the voluntary permission
1810
- * request and sends the privileged callback directly.
1811
- *
1812
- * Uses the session's permission policy. The default
1813
- * (`readOnlyPermissionPolicy`) auto-approves only side-effect-free tool
1814
- * calls (read/search/fetch/think) and rejects everything else — this is
1815
- * the safe-by-default posture. For trusted local agents (CLI `acp spawn`,
1816
- * Director fan-out), inject `defaultPermissionPolicy` to grant
1817
- * write/execute access.
1818
- *
1819
- * Returns true if the callback is authorized, false if denied.
1820
- */
1821
- async authorizeCallback(partial) {
1822
- try {
1823
- const outcome = await this.permissionPolicy({
1824
- toolCall: {
1825
- sessionUpdate: "tool_call_update",
1826
- toolCallId: partial.toolCallId,
1827
- title: partial.title,
1828
- kind: partial.kind,
1829
- status: "pending",
1830
- ...partial.rawInput ? { rawInput: partial.rawInput } : {}
1831
- },
1832
- options: [
1833
- { optionId: "allow", name: "Allow", kind: "allow_once" },
1834
- { optionId: "reject", name: "Reject", kind: "reject_once" }
1835
- ],
1836
- signal: new AbortController().signal
1837
- });
1838
- return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
1839
- } catch {
1840
- return false;
1841
- }
1842
- }
1843
- async handleFsRequest(msg) {
1844
- const id = msg.id;
1845
- if (id === void 0) return;
1846
- const params = msg.params;
1847
- if (!params?.path) {
1848
- await this.sendErrorResponse(id, -32602, "path is required");
1849
- return;
1850
- }
1851
- if (msg.method === "fs/write_text_file") {
1852
- const allowed = await this.authorizeCallback({
1853
- toolCallId: `acp-fs-write-${id}`,
1854
- title: `Write file: ${params.path}`,
1855
- kind: "edit",
1856
- rawInput: { path: params.path, sessionId: params.sessionId }
1857
- });
1858
- if (!allowed) {
1859
- await this.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
1860
- return;
1861
- }
1862
- }
1863
- try {
1864
- if (msg.method === "fs/read_text_file") {
1865
- const result = await this.fileServer.readTextFile({
1866
- sessionId: params.sessionId ?? "",
1867
- path: params.path
1868
- });
1869
- await this.sendResult(id, result);
1870
- } else {
1871
- await this.fileServer.writeTextFile({
1872
- sessionId: params.sessionId ?? "",
1873
- path: params.path,
1874
- content: params.content ?? ""
1875
- });
1876
- await this.sendResult(id, {});
1877
- }
1878
- } catch (err) {
1879
- const code = err instanceof FsError ? -32602 : -32603;
1880
- const message = err instanceof Error ? err.message : String(err);
1881
- await this.sendErrorResponse(id, code, message);
1882
- }
1883
- }
1884
- async handleTerminalRequest(msg) {
1885
- const id = msg.id;
1886
- if (id === void 0) return;
1887
- const params = msg.params ?? {};
1888
- try {
1889
- switch (msg.method) {
1890
- case "terminal/create": {
1891
- const allowed = await this.authorizeCallback({
1892
- toolCallId: `acp-terminal-create-${id}`,
1893
- title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
1894
- kind: "execute",
1895
- rawInput: {
1896
- command: params.command,
1897
- args: params.args,
1898
- cwd: params.cwd,
1899
- sessionId: params.sessionId
1900
- }
1901
- });
1902
- if (!allowed) {
1903
- await this.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
1904
- return;
1905
- }
1906
- const createOpts = {
1907
- sessionId: String(params.sessionId ?? ""),
1908
- command: String(params.command ?? ""),
1909
- args: Array.isArray(params.args) ? params.args : []
1910
- };
1911
- if (Array.isArray(params.env)) {
1912
- createOpts.env = params.env;
1913
- }
1914
- if (typeof params.cwd === "string") {
1915
- createOpts.cwd = params.cwd;
1916
- }
1917
- if (typeof params.outputByteLimit === "number") {
1918
- createOpts.outputByteLimit = params.outputByteLimit;
1919
- }
1920
- const result = this.terminalServer.create(createOpts);
1921
- await this.sendResult(id, result);
1922
- return;
1923
- }
1924
- case "terminal/output": {
1925
- const terminalId = String(params.terminalId ?? "");
1926
- const out = this.terminalServer.output(terminalId);
1927
- await this.sendResult(id, out);
1928
- return;
1929
- }
1930
- case "terminal/wait_for_exit": {
1931
- const terminalId = String(params.terminalId ?? "");
1932
- const exit = await this.terminalServer.waitForExit(terminalId);
1933
- await this.sendResult(id, exit);
1934
- return;
1935
- }
1936
- case "terminal/kill": {
1937
- const terminalId = String(params.terminalId ?? "");
1938
- this.terminalServer.kill(terminalId);
1939
- await this.sendResult(id, {});
1940
- return;
1941
- }
1942
- case "terminal/release": {
1943
- const terminalId = String(params.terminalId ?? "");
1944
- this.terminalServer.release(terminalId);
1945
- await this.sendResult(id, {});
1946
- return;
1947
- }
1948
- default:
1949
- await this.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
1950
- }
1951
- } catch (err) {
1952
- const message = err instanceof Error ? err.message : String(err);
1953
- await this.sendErrorResponse(id, -32603, message);
1954
- }
1990
+ this.scratch = createSessionScratch();
1955
1991
  }
1956
1992
  };
1957
- function textContent(text) {
1958
- return { type: "text", text };
1959
- }
1960
- function imageContent(mimeType, data) {
1961
- return { type: "image", mimeType, data };
1962
- }
1963
- function audioContent(mimeType, data) {
1964
- return { type: "audio", mimeType, data };
1965
- }
1966
- function extractText(block) {
1967
- if (typeof block !== "object" || block === null) return "";
1968
- const b = block;
1969
- if (b.type === "text" && typeof b.text === "string") return b.text;
1970
- if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
1971
- return b.resource.text;
1972
- }
1973
- return "";
1974
- }
1975
- function isRecord(v) {
1976
- return typeof v === "object" && v !== null && !Array.isArray(v);
1977
- }
1978
- function emptyRunResult(stopReason) {
1979
- return {
1980
- text: "",
1981
- stopReason,
1982
- hasText: false,
1983
- toolCalls: [],
1984
- diffs: [],
1985
- thoughts: ""
1986
- };
1987
- }
1988
1993
 
1989
1994
  // src/integration/acp-subagent-runner.ts
1990
1995
  async function makeACPSubagentRunner(options) {