@wrongstack/acp 1.0.33 → 1.0.36

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/index.js CHANGED
@@ -1135,7 +1135,11 @@ var StdioTransport = class {
1135
1135
  return;
1136
1136
  }
1137
1137
  try {
1138
- this.dispatch(JSON.parse(raw), raw.length);
1138
+ const parsed = JSON.parse(raw);
1139
+ if (parsed === null || typeof parsed !== "object") {
1140
+ throw new Error("frame is not a JSON-RPC object");
1141
+ }
1142
+ this.dispatch(parsed, raw.length);
1139
1143
  } catch (err) {
1140
1144
  this.stderr.write(`[wstack-acp parse error] ${err}
1141
1145
  `, "utf8");
@@ -2238,39 +2242,6 @@ async function ensureAuthenticated(host) {
2238
2242
  );
2239
2243
  }
2240
2244
 
2241
- // src/client/acp-session-content.ts
2242
- function textContent(text) {
2243
- return { type: "text", text };
2244
- }
2245
- function imageContent(mimeType, data) {
2246
- return { type: "image", mimeType, data };
2247
- }
2248
- function audioContent(mimeType, data) {
2249
- return { type: "audio", mimeType, data };
2250
- }
2251
- function extractText(block) {
2252
- if (typeof block !== "object" || block === null) return "";
2253
- const b = block;
2254
- if (b.type === "text" && typeof b.text === "string") return b.text;
2255
- if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
2256
- return b.resource.text;
2257
- }
2258
- return "";
2259
- }
2260
- function isRecord(v) {
2261
- return typeof v === "object" && v !== null && !Array.isArray(v);
2262
- }
2263
- function emptyRunResult(stopReason) {
2264
- return {
2265
- text: "",
2266
- stopReason,
2267
- hasText: false,
2268
- toolCalls: [],
2269
- diffs: [],
2270
- thoughts: ""
2271
- };
2272
- }
2273
-
2274
2245
  // src/client/acp-message-routing.ts
2275
2246
  function isBestEffortAckMethod(method) {
2276
2247
  return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
@@ -2738,10 +2709,45 @@ function isAbortLikeError(err) {
2738
2709
  }
2739
2710
  function resolvePermissionDeadline(value) {
2740
2711
  if (value === Number.POSITIVE_INFINITY) return null;
2741
- if (value !== void 0 && Number.isFinite(value) && value > 0) return Math.trunc(value);
2712
+ if (value !== void 0 && Number.isFinite(value) && value > 0) {
2713
+ return Math.min(Math.trunc(value), 2147483647);
2714
+ }
2742
2715
  return DEFAULT_PERMISSION_TIMEOUT_MS;
2743
2716
  }
2744
2717
 
2718
+ // src/client/acp-session-content.ts
2719
+ function textContent(text) {
2720
+ return { type: "text", text };
2721
+ }
2722
+ function imageContent(mimeType, data) {
2723
+ return { type: "image", mimeType, data };
2724
+ }
2725
+ function audioContent(mimeType, data) {
2726
+ return { type: "audio", mimeType, data };
2727
+ }
2728
+ function extractText(block) {
2729
+ if (typeof block !== "object" || block === null) return "";
2730
+ const b = block;
2731
+ if (b.type === "text" && typeof b.text === "string") return b.text;
2732
+ if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
2733
+ return b.resource.text;
2734
+ }
2735
+ return "";
2736
+ }
2737
+ function isRecord(v) {
2738
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2739
+ }
2740
+ function emptyRunResult(stopReason) {
2741
+ return {
2742
+ text: "",
2743
+ stopReason,
2744
+ hasText: false,
2745
+ toolCalls: [],
2746
+ diffs: [],
2747
+ thoughts: ""
2748
+ };
2749
+ }
2750
+
2745
2751
  // src/client/acp-session-updates.ts
2746
2752
  function createSessionScratch() {
2747
2753
  return { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
@@ -2937,6 +2943,146 @@ function emitProgress(event) {
2937
2943
  }
2938
2944
  }
2939
2945
 
2946
+ // src/client/acp-session-prompt.ts
2947
+ async function prompt(host, blocks, signal, onProgress) {
2948
+ if (host.closed) {
2949
+ throw new ACPSessionError("closed", "session is closed");
2950
+ }
2951
+ if (host.state !== "ready" && host.state !== "authenticated" && host.state !== "done") {
2952
+ throw new ACPSessionError("protocol_error", `prompt called in state=${host.state}`);
2953
+ }
2954
+ if (host.promptCallbackAbort) {
2955
+ throw new ACPSessionError("protocol_error", "prompt called while another prompt is running");
2956
+ }
2957
+ if (signal.aborted) {
2958
+ return emptyRunResult("cancelled");
2959
+ }
2960
+ const caps = host.agentCapabilities.promptCapabilities;
2961
+ for (const block of blocks) {
2962
+ const supported = block.type === "image" ? caps?.image : block.type === "audio" ? caps?.audio : block.type === "resource" ? caps?.embeddedContext : true;
2963
+ if (supported !== true) {
2964
+ throw new ACPSessionError(
2965
+ "unsupported_capability",
2966
+ `agent does not support ${block.type} prompt content`
2967
+ );
2968
+ }
2969
+ }
2970
+ let cancelled = false;
2971
+ host.promptCallbackAbort = new AbortController();
2972
+ const onAbort = () => {
2973
+ cancelled = true;
2974
+ host.promptCallbackAbort?.abort();
2975
+ if (host.sessionId) {
2976
+ host.transport.send({
2977
+ jsonrpc: "2.0",
2978
+ method: "session/cancel",
2979
+ params: { sessionId: host.sessionId }
2980
+ }).catch(() => {
2981
+ });
2982
+ }
2983
+ };
2984
+ signal.addEventListener("abort", onAbort, { once: true });
2985
+ let rejectCreate;
2986
+ const onCreateAbort = () => {
2987
+ signal.removeEventListener("abort", onAbort);
2988
+ rejectCreate?.(new ACPSessionError("aborted", "prompt was aborted by the parent"));
2989
+ };
2990
+ if (!host.sessionId) {
2991
+ let sessionId;
2992
+ const createPromise = host.createSessionWithAuth();
2993
+ try {
2994
+ sessionId = await Promise.race([
2995
+ createPromise,
2996
+ new Promise((_, reject) => {
2997
+ rejectCreate = reject;
2998
+ signal.addEventListener("abort", onCreateAbort, { once: true });
2999
+ })
3000
+ ]);
3001
+ } catch (err) {
3002
+ signal.removeEventListener("abort", onAbort);
3003
+ signal.removeEventListener("abort", onCreateAbort);
3004
+ rejectCreate = void 0;
3005
+ host.promptCallbackAbort?.abort();
3006
+ host.promptCallbackAbort = null;
3007
+ if (err instanceof ACPSessionError && err.kind === "aborted") {
3008
+ host.cancelLateSession(createPromise);
3009
+ return emptyRunResult("cancelled");
3010
+ }
3011
+ throw err;
3012
+ }
3013
+ signal.removeEventListener("abort", onCreateAbort);
3014
+ rejectCreate = void 0;
3015
+ host.sessionId = sessionId;
3016
+ }
3017
+ if (signal.aborted) {
3018
+ host.promptCallbackAbort = null;
3019
+ return emptyRunResult("cancelled");
3020
+ }
3021
+ host.resetScratch();
3022
+ host.progressHandler = onProgress ?? null;
3023
+ const promptId = host.allocId();
3024
+ let rejectTurn;
3025
+ const onTurnAbort = () => {
3026
+ rejectTurn?.(new ACPSessionError("aborted", "prompt was aborted by the parent"));
3027
+ };
3028
+ signal.addEventListener("abort", onTurnAbort, { once: true });
3029
+ const turnPromise = Promise.race([
3030
+ host.sendRequest(
3031
+ promptId,
3032
+ "session/prompt",
3033
+ {
3034
+ sessionId: host.sessionId,
3035
+ prompt: blocks
3036
+ },
3037
+ host.timeoutMs
3038
+ ),
3039
+ new Promise((_, reject) => {
3040
+ rejectTurn = reject;
3041
+ })
3042
+ ]);
3043
+ host.state = "prompting";
3044
+ let response;
3045
+ try {
3046
+ response = await turnPromise;
3047
+ } catch (err) {
3048
+ host.state = "done";
3049
+ const abortedKind = err instanceof ACPSessionError && err.kind === "aborted";
3050
+ if (cancelled || abortedKind) {
3051
+ return emptyRunResult("cancelled");
3052
+ }
3053
+ const msg = err instanceof Error ? err.message : String(err);
3054
+ if (signal.aborted) {
3055
+ throw new ACPSessionError("aborted", "prompt was aborted by the parent", err);
3056
+ }
3057
+ throw new ACPSessionError("prompt_failed", `session/prompt failed: ${msg}`, err);
3058
+ } finally {
3059
+ signal.removeEventListener("abort", onAbort);
3060
+ signal.removeEventListener("abort", onTurnAbort);
3061
+ signal.removeEventListener("abort", onCreateAbort);
3062
+ rejectTurn = void 0;
3063
+ rejectCreate = void 0;
3064
+ host.promptCallbackAbort?.abort();
3065
+ host.promptCallbackAbort = null;
3066
+ host.progressHandler = null;
3067
+ }
3068
+ host.state = "done";
3069
+ if (isJsonRpcError(response)) {
3070
+ throw new ACPSessionError("prompt_failed", `agent error: ${response.message}`, response);
3071
+ }
3072
+ const stopReason = response.stopReason ?? "end_turn";
3073
+ const finalText = host.scratch.text;
3074
+ return {
3075
+ text: finalText,
3076
+ stopReason,
3077
+ hasText: finalText.length > 0,
3078
+ usage: host.scratch.usage,
3079
+ plan: host.scratch.plan,
3080
+ toolCalls: [...host.scratch.toolCalls.values()],
3081
+ diffs: host.scratch.diffs,
3082
+ thoughts: host.scratch.thoughts
3083
+ };
3084
+ }
3085
+
2940
3086
  // src/client/permission.ts
2941
3087
  function pickAllow(options) {
2942
3088
  const ranked = [...options].sort((a, b) => {
@@ -2992,7 +3138,8 @@ function win32BatchTarget(command) {
2992
3138
  if (/\.(?:cmd|bat)$/i.test(command)) return command;
2993
3139
  if (/[\\/]/.test(command) || path3.extname(command)) return void 0;
2994
3140
  const exts = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD").toLowerCase().split(";");
2995
- for (const dir of (process.env["PATH"] ?? "").split(path3.delimiter)) {
3141
+ for (const rawDir of (process.env["PATH"] ?? "").split(path3.delimiter)) {
3142
+ const dir = rawDir.trim().replace(/^"|"$/g, "");
2996
3143
  if (!dir) continue;
2997
3144
  for (const ext of exts) {
2998
3145
  if (!ext) continue;
@@ -3256,15 +3403,13 @@ var TerminalServer = class {
3256
3403
  resolveCwd(cwd) {
3257
3404
  if (!cwd) return this.projectRoot;
3258
3405
  const resolved = path3.resolve(cwd);
3259
- const rootWithSep = this.projectRoot.endsWith(path3.sep) ? this.projectRoot : this.projectRoot + path3.sep;
3260
- if (resolved !== this.projectRoot && !resolved.startsWith(rootWithSep)) {
3406
+ if (!isInsideRoot(resolved, this.projectRoot)) {
3261
3407
  return this.projectRoot;
3262
3408
  }
3263
3409
  try {
3264
3410
  const realRoot = realpathSync2(this.projectRoot);
3265
3411
  const realCwd = realpathSync2(resolved);
3266
- const realRootWithSep = realRoot.endsWith(path3.sep) ? realRoot : realRoot + path3.sep;
3267
- if (realCwd !== realRoot && !realCwd.startsWith(realRootWithSep)) {
3412
+ if (!isInsideRoot(realCwd, realRoot)) {
3268
3413
  return realRoot;
3269
3414
  }
3270
3415
  return realCwd;
@@ -3296,6 +3441,12 @@ var TerminalServer = class {
3296
3441
  return Math.trunc(value);
3297
3442
  }
3298
3443
  };
3444
+ function isInsideRoot(child, root) {
3445
+ const fold = (p) => process.platform === "win32" ? p.toLowerCase() : p;
3446
+ const c = fold(child);
3447
+ const r = fold(root);
3448
+ return c === r || c.startsWith(r.endsWith(path3.sep) ? r : r + path3.sep);
3449
+ }
3299
3450
  var DENIED_AGENT_ENV_KEYS = /* @__PURE__ */ new Set([
3300
3451
  "NODE_OPTIONS",
3301
3452
  "LD_PRELOAD",
@@ -3559,6 +3710,7 @@ var WebSocketClientTransport = class {
3559
3710
  }
3560
3711
  dispatch(msg) {
3561
3712
  for (const handler of [...this.handlers]) {
3713
+ if (this.closed) break;
3562
3714
  try {
3563
3715
  handler(msg);
3564
3716
  } catch {
@@ -3835,142 +3987,7 @@ var ACPSession = class _ACPSession {
3835
3987
  // Prompt
3836
3988
  // ──────────────────────────────────────────────────────────────────────
3837
3989
  async prompt(blocks, signal, onProgress) {
3838
- if (this.closed) {
3839
- throw new ACPSessionError("closed", "session is closed");
3840
- }
3841
- if (this.state !== "ready" && this.state !== "authenticated" && this.state !== "done") {
3842
- throw new ACPSessionError("protocol_error", `prompt called in state=${this.state}`);
3843
- }
3844
- if (this.promptCallbackAbort) {
3845
- throw new ACPSessionError("protocol_error", "prompt called while another prompt is running");
3846
- }
3847
- if (signal.aborted) {
3848
- return emptyRunResult("cancelled");
3849
- }
3850
- const caps = this.agentCapabilities.promptCapabilities;
3851
- for (const block of blocks) {
3852
- const supported = block.type === "image" ? caps?.image : block.type === "audio" ? caps?.audio : block.type === "resource" ? caps?.embeddedContext : true;
3853
- if (supported !== true) {
3854
- throw new ACPSessionError(
3855
- "unsupported_capability",
3856
- `agent does not support ${block.type} prompt content`
3857
- );
3858
- }
3859
- }
3860
- let cancelled = false;
3861
- this.promptCallbackAbort = new AbortController();
3862
- const onAbort = () => {
3863
- cancelled = true;
3864
- this.promptCallbackAbort?.abort();
3865
- if (this.sessionId) {
3866
- this.transport.send({
3867
- jsonrpc: "2.0",
3868
- method: "session/cancel",
3869
- params: { sessionId: this.sessionId }
3870
- }).catch(() => {
3871
- });
3872
- }
3873
- };
3874
- signal.addEventListener("abort", onAbort, { once: true });
3875
- let rejectCreate;
3876
- const onCreateAbort = () => {
3877
- signal.removeEventListener("abort", onAbort);
3878
- rejectCreate?.(new ACPSessionError("aborted", "prompt was aborted by the parent"));
3879
- };
3880
- if (!this.sessionId) {
3881
- let sessionId;
3882
- const createPromise = this.createSessionWithAuth();
3883
- try {
3884
- sessionId = await Promise.race([
3885
- createPromise,
3886
- new Promise((_, reject) => {
3887
- rejectCreate = reject;
3888
- signal.addEventListener("abort", onCreateAbort, { once: true });
3889
- })
3890
- ]);
3891
- } catch (err) {
3892
- signal.removeEventListener("abort", onAbort);
3893
- signal.removeEventListener("abort", onCreateAbort);
3894
- rejectCreate = void 0;
3895
- this.promptCallbackAbort?.abort();
3896
- this.promptCallbackAbort = null;
3897
- if (err instanceof ACPSessionError && err.kind === "aborted") {
3898
- this.cancelLateSession(createPromise);
3899
- return emptyRunResult("cancelled");
3900
- }
3901
- throw err;
3902
- }
3903
- signal.removeEventListener("abort", onCreateAbort);
3904
- rejectCreate = void 0;
3905
- this.sessionId = sessionId;
3906
- }
3907
- if (signal.aborted) {
3908
- this.promptCallbackAbort = null;
3909
- return emptyRunResult("cancelled");
3910
- }
3911
- this.resetScratch();
3912
- this.progressHandler = onProgress ?? null;
3913
- const promptId = this.allocId();
3914
- let rejectTurn;
3915
- const onTurnAbort = () => {
3916
- rejectTurn?.(new ACPSessionError("aborted", "prompt was aborted by the parent"));
3917
- };
3918
- signal.addEventListener("abort", onTurnAbort, { once: true });
3919
- const turnPromise = Promise.race([
3920
- this.sendRequest(
3921
- promptId,
3922
- "session/prompt",
3923
- {
3924
- sessionId: this.sessionId,
3925
- prompt: blocks
3926
- },
3927
- this.timeoutMs
3928
- ),
3929
- new Promise((_, reject) => {
3930
- rejectTurn = reject;
3931
- })
3932
- ]);
3933
- this.state = "prompting";
3934
- let response;
3935
- try {
3936
- response = await turnPromise;
3937
- } catch (err) {
3938
- this.state = "done";
3939
- const abortedKind = err instanceof ACPSessionError && err.kind === "aborted";
3940
- if (cancelled || abortedKind) {
3941
- return emptyRunResult("cancelled");
3942
- }
3943
- const msg = err instanceof Error ? err.message : String(err);
3944
- if (signal.aborted) {
3945
- throw new ACPSessionError("aborted", "prompt was aborted by the parent", err);
3946
- }
3947
- throw new ACPSessionError("prompt_failed", `session/prompt failed: ${msg}`, err);
3948
- } finally {
3949
- signal.removeEventListener("abort", onAbort);
3950
- signal.removeEventListener("abort", onTurnAbort);
3951
- signal.removeEventListener("abort", onCreateAbort);
3952
- rejectTurn = void 0;
3953
- rejectCreate = void 0;
3954
- this.promptCallbackAbort?.abort();
3955
- this.promptCallbackAbort = null;
3956
- this.progressHandler = null;
3957
- }
3958
- this.state = "done";
3959
- if (isJsonRpcError(response)) {
3960
- throw new ACPSessionError("prompt_failed", `agent error: ${response.message}`, response);
3961
- }
3962
- const stopReason = response.stopReason ?? "end_turn";
3963
- const finalText = this.scratch.text;
3964
- return {
3965
- text: finalText,
3966
- stopReason,
3967
- hasText: finalText.length > 0,
3968
- usage: this.scratch.usage,
3969
- plan: this.scratch.plan,
3970
- toolCalls: [...this.scratch.toolCalls.values()],
3971
- diffs: this.scratch.diffs,
3972
- thoughts: this.scratch.thoughts
3973
- };
3990
+ return prompt(this.acpSessionPromptHost(), blocks, signal, onProgress);
3974
3991
  }
3975
3992
  /**
3976
3993
  * Best-effort cancel for a session the server confirmed after we already
@@ -4140,6 +4157,25 @@ var ACPSession = class _ACPSession {
4140
4157
  void this.progressHandler;
4141
4158
  return this;
4142
4159
  }
4160
+ acpSessionPromptHost() {
4161
+ void {
4162
+ closed: this.closed,
4163
+ state: this.state,
4164
+ promptCallbackAbort: this.promptCallbackAbort,
4165
+ agentCapabilities: this.agentCapabilities,
4166
+ sessionId: this.sessionId,
4167
+ transport: this.transport,
4168
+ createSessionWithAuth: this.createSessionWithAuth,
4169
+ cancelLateSession: this.cancelLateSession,
4170
+ resetScratch: this.resetScratch,
4171
+ progressHandler: this.progressHandler,
4172
+ allocId: this.allocId,
4173
+ sendRequest: this.sendRequest,
4174
+ timeoutMs: this.timeoutMs,
4175
+ scratch: this.scratch
4176
+ };
4177
+ return this;
4178
+ }
4143
4179
  };
4144
4180
 
4145
4181
  // src/client/tool-translator.ts
@@ -1132,7 +1132,11 @@ var StdioTransport = class {
1132
1132
  return;
1133
1133
  }
1134
1134
  try {
1135
- this.dispatch(JSON.parse(raw), raw.length);
1135
+ const parsed = JSON.parse(raw);
1136
+ if (parsed === null || typeof parsed !== "object") {
1137
+ throw new Error("frame is not a JSON-RPC object");
1138
+ }
1139
+ this.dispatch(parsed, raw.length);
1136
1140
  } catch (err) {
1137
1141
  this.stderr.write(`[wstack-acp parse error] ${err}
1138
1142
  `, "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/acp",
3
- "version": "1.0.33",
3
+ "version": "1.0.36",
4
4
  "license": "MIT",
5
5
  "description": "ACP (Agent Client Protocol) integration for WrongStack — client + agent support",
6
6
  "keywords": [
@@ -52,8 +52,8 @@
52
52
  ],
53
53
  "dependencies": {
54
54
  "@agentclientprotocol/sdk": "^1.4.0",
55
- "@wrongstack/core": "1.0.33",
56
- "@wrongstack/primitives": "1.0.33"
55
+ "@wrongstack/core": "1.0.36",
56
+ "@wrongstack/primitives": "1.0.36"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/node": "^26.5.1",
@@ -65,8 +65,8 @@
65
65
  },
66
66
  "scripts": {
67
67
  "build": "node ../../scripts/build-package.mjs",
68
- "typecheck": "tsc --noEmit",
69
- "typecheck:tests": "tsc --noEmit -p tsconfig.test.json",
68
+ "typecheck": "node ../../scripts/bun-typecheck.mjs --noEmit",
69
+ "typecheck:tests": "node ../../scripts/bun-typecheck.mjs --noEmit -p tsconfig.test.json",
70
70
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
71
71
  "test": "vitest run",
72
72
  "smoke": "pnpm build && node --experimental-strip-types ../../scripts/acp-smoke-test.mts"