@hienlh/ppm 0.8.39 → 0.8.40
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/CHANGELOG.md +9 -0
- package/dist/web/assets/{chat-tab-CGic5t8w.js → chat-tab-CWbZGvbK.js} +1 -1
- package/dist/web/assets/{code-editor-DZlXHMtA.js → code-editor-tvmXoTU1.js} +1 -1
- package/dist/web/assets/{database-viewer-BaxjPtYR.js → database-viewer-CTEUcPLq.js} +1 -1
- package/dist/web/assets/{diff-viewer-CDdO3tqP.js → diff-viewer-BXKPZ7WC.js} +1 -1
- package/dist/web/assets/{git-graph-C-TRbbx7.js → git-graph-C2LLHCfn.js} +1 -1
- package/dist/web/assets/{index-CvbNQ1mi.js → index-BPfvFxHR.js} +7 -7
- package/dist/web/assets/keybindings-store-Dy8h77O5.js +1 -0
- package/dist/web/assets/{markdown-renderer-DE503g9L.js → markdown-renderer-NystIEIy.js} +1 -1
- package/dist/web/assets/{postgres-viewer-hmqfZRr-.js → postgres-viewer-D2U0pMTo.js} +1 -1
- package/dist/web/assets/{settings-tab-BdgsQeES.js → settings-tab-CMsA6CMf.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-9X_1ZHJE.js → sqlite-viewer-C2SI9Y3G.js} +1 -1
- package/dist/web/assets/{terminal-tab-V7x81Qpr.js → terminal-tab-BRvfFQV9.js} +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/sw.js +1 -1
- package/docs/lessons-learned.md +5 -12
- package/package.json +2 -3
- package/src/index.ts +0 -10
- package/src/providers/claude-agent-sdk.ts +57 -292
- package/src/types/config.ts +0 -8
- package/src/web/components/settings/ai-settings-section.tsx +0 -24
- package/src/web/lib/api-settings.ts +0 -1
- package/dist/web/assets/keybindings-store-Dqs-i9cV.js +0 -1
- package/scripts/patch-sdk.mjs +0 -214
- package/scripts/test-drain-bug.mjs +0 -131
- package/test-sdk.mjs +0 -106
package/scripts/patch-sdk.mjs
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Postinstall patch for @anthropic-ai/claude-agent-sdk
|
|
4
|
-
*
|
|
5
|
-
* Fixes Windows + Bun subprocess pipe issues:
|
|
6
|
-
* 1. Adding drain() handling to ProcessTransport.write()
|
|
7
|
-
* 2. Awaiting the initial prompt write in query() entry point
|
|
8
|
-
* 3. Replacing readline async iterator with manual line reader in readMessages()
|
|
9
|
-
*
|
|
10
|
-
* Bun on Windows has broken: stdin pipe backpressure, unawaited async writes,
|
|
11
|
-
* and readline.createInterface() async iterator (Symbol.asyncIterator).
|
|
12
|
-
*
|
|
13
|
-
* Tracking issues:
|
|
14
|
-
* - TS SDK #44: https://github.com/anthropics/claude-agent-sdk-typescript/issues/44
|
|
15
|
-
* - TS SDK #64: https://github.com/anthropics/claude-agent-sdk-typescript/issues/64
|
|
16
|
-
*
|
|
17
|
-
* Remove this patch when upstream fixes land.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
21
|
-
import { join } from "path";
|
|
22
|
-
|
|
23
|
-
export function patchSdk(sdkPath) {
|
|
24
|
-
if (!existsSync(sdkPath)) {
|
|
25
|
-
console.log("[patch-sdk] SDK not found, skipping patch");
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let content = readFileSync(sdkPath, "utf8");
|
|
30
|
-
let patches = 0;
|
|
31
|
-
|
|
32
|
-
// ── Patch 1: ProcessTransport.write() — add drain handling ──
|
|
33
|
-
|
|
34
|
-
if (content.includes("waiting for drain")) {
|
|
35
|
-
console.log("[patch-sdk] Patch 1 (drain): already applied");
|
|
36
|
-
} else {
|
|
37
|
-
// Surgical approach: find the backpressure line and patch it
|
|
38
|
-
const drainPattern =
|
|
39
|
-
/if\(!this\.processStdin\.write\(([A-Za-z_$][A-Za-z0-9_$]*)\)\)([A-Za-z_$][A-Za-z0-9_$]*)\("\[ProcessTransport\] Write buffer full, data queued"\)/;
|
|
40
|
-
const drainMatch = content.match(drainPattern);
|
|
41
|
-
|
|
42
|
-
if (!drainMatch) {
|
|
43
|
-
console.warn("[patch-sdk] Patch 1 (drain): pattern not found, skipping");
|
|
44
|
-
} else {
|
|
45
|
-
const oldLine = drainMatch[0];
|
|
46
|
-
const arg = drainMatch[1];
|
|
47
|
-
const logger = drainMatch[2];
|
|
48
|
-
|
|
49
|
-
// Replace backpressure line:
|
|
50
|
-
// - Non-Windows: await drain event (pipe buffers are large, drain fires reliably)
|
|
51
|
-
// - Windows: skip drain — Bun+Windows pipe drain event is unreliable (may never fire
|
|
52
|
-
// in PowerShell). OS still buffers data; subprocess reads when ready.
|
|
53
|
-
const newLine =
|
|
54
|
-
`if(!this.processStdin.write(${arg})){` +
|
|
55
|
-
`${logger}("[ProcessTransport] Write buffer full, "+(process.platform==="win32"?"skipping drain (Windows)":"waiting for drain"));` +
|
|
56
|
-
`if(process.platform!=="win32")await new Promise(_dr=>this.processStdin.once("drain",_dr))}`;
|
|
57
|
-
|
|
58
|
-
content = content.replace(oldLine, newLine);
|
|
59
|
-
|
|
60
|
-
// Make the method async
|
|
61
|
-
const writeIdx = content.indexOf(newLine);
|
|
62
|
-
const oldDecl = `write(${arg}){`;
|
|
63
|
-
const declIdx = content.lastIndexOf(oldDecl, writeIdx);
|
|
64
|
-
if (declIdx >= 0) {
|
|
65
|
-
content =
|
|
66
|
-
content.substring(0, declIdx) +
|
|
67
|
-
`async write(${arg}){` +
|
|
68
|
-
content.substring(declIdx + oldDecl.length);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
patches++;
|
|
72
|
-
console.log("[patch-sdk] Patch 1 (drain): applied");
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ── Patch 2: Await initial prompt write in query() entry point ──
|
|
77
|
-
// The query() function writes the user prompt to transport.write() without
|
|
78
|
-
// awaiting. Since write() is now async (returns Promise on backpressure),
|
|
79
|
-
// the prompt data can be lost on Windows where pipe buffers are small.
|
|
80
|
-
//
|
|
81
|
-
// Pattern (minified):
|
|
82
|
-
// if(typeof Q==="string")TRANSPORT.write(SERIALIZE({type:"user",...})+"\n");
|
|
83
|
-
// else QUERY.streamInput(Q);
|
|
84
|
-
//
|
|
85
|
-
// We need to await the write and make the surrounding context async-compatible.
|
|
86
|
-
// Since write is fire-and-forget here (the Promise is dropped), we wrap it.
|
|
87
|
-
|
|
88
|
-
if (content.includes("__ppm_await_write__")) {
|
|
89
|
-
console.log("[patch-sdk] Patch 2 (await prompt): already applied");
|
|
90
|
-
} else {
|
|
91
|
-
// Match: TRANSPORT.write(SERIALIZE({type:"user",...})+`\n`);
|
|
92
|
-
// Anchor on stable string literals: type:"user",session_id:"",message:{role:"user"
|
|
93
|
-
const promptWritePattern =
|
|
94
|
-
/([A-Za-z_$][A-Za-z0-9_$]*)\.write\(([A-Za-z_$][A-Za-z0-9_$]*)\(\{type:"user",session_id:"",message:\{role:"user",content:\[\{type:"text",text:([A-Za-z_$][A-Za-z0-9_$]*)\}\]\},parent_tool_use_id:null\}\)\+(?:`\n`|"\\n")\)/;
|
|
95
|
-
const promptMatch = content.match(promptWritePattern);
|
|
96
|
-
|
|
97
|
-
if (!promptMatch) {
|
|
98
|
-
console.warn(
|
|
99
|
-
"[patch-sdk] Patch 2 (await prompt): pattern not found, skipping",
|
|
100
|
-
);
|
|
101
|
-
} else {
|
|
102
|
-
const oldPromptWrite = promptMatch[0];
|
|
103
|
-
// Wrap in async IIFE — keeps query() sync so callers don't need `await query()`
|
|
104
|
-
const newPromptWrite =
|
|
105
|
-
`/*__ppm_await_write__*/(async()=>{await ${oldPromptWrite}})()`;
|
|
106
|
-
|
|
107
|
-
content = content.replace(oldPromptWrite, newPromptWrite);
|
|
108
|
-
patches++;
|
|
109
|
-
console.log("[patch-sdk] Patch 2 (await prompt): applied");
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── Patch 3: Replace readline async iterator in readMessages() ──
|
|
114
|
-
// Bun on Windows doesn't implement Symbol.asyncIterator for
|
|
115
|
-
// readline.createInterface(), causing "undefined is not a function"
|
|
116
|
-
// when the SDK does `for await (let X of readlineInterface)`.
|
|
117
|
-
//
|
|
118
|
-
// Replace with a manual line reader using raw stream 'data' events.
|
|
119
|
-
|
|
120
|
-
if (content.includes("__ppm_manual_readline__")) {
|
|
121
|
-
console.log("[patch-sdk] Patch 3 (readline): already applied");
|
|
122
|
-
} else {
|
|
123
|
-
// Match the readMessages method by anchoring on the stable error string
|
|
124
|
-
const readMsgPattern =
|
|
125
|
-
/async\s?\*\s?readMessages\(\)\{if\(!this\.processStdout\)throw Error\("ProcessTransport output stream not available"\);let ([A-Za-z_$][A-Za-z0-9_$]*)=([A-Za-z_$][A-Za-z0-9_$]*)\(\{input:this\.processStdout\}\);try\{for await\(let ([A-Za-z_$][A-Za-z0-9_$]*) of \1\)if\(\3\.trim\(\)\)try\{yield ([A-Za-z_$][A-Za-z0-9_$]*)\(\3\)\}catch\(([A-Za-z_$][A-Za-z0-9_$]*)\)\{throw ([A-Za-z_$][A-Za-z0-9_$]*)\(`Non-JSON stdout: \$\{\3\}`\),Error\(`CLI output was not valid JSON\. This may indicate an error during startup\. Output: \$\{\3\.slice\(0,200\)\}\$\{\3\.length>200\?"\.\.\.":""\}`\)\}await this\.waitForExit\(\)\}catch\(\3\)\{throw \3\}finally\{\1\.close\(\)\}\}/;
|
|
126
|
-
const readMsgMatch = content.match(readMsgPattern);
|
|
127
|
-
|
|
128
|
-
if (!readMsgMatch) {
|
|
129
|
-
console.warn(
|
|
130
|
-
"[patch-sdk] Patch 3 (readline): pattern not found, skipping",
|
|
131
|
-
);
|
|
132
|
-
} else {
|
|
133
|
-
const oldReadMsg = readMsgMatch[0];
|
|
134
|
-
const rlVar = readMsgMatch[1]; // Q (readline interface)
|
|
135
|
-
const createRL = readMsgMatch[2]; // DU (createInterface)
|
|
136
|
-
const lineVar = readMsgMatch[3]; // X (line variable)
|
|
137
|
-
const parseJSON = readMsgMatch[4]; // O1 (JSON parser)
|
|
138
|
-
const errVar = readMsgMatch[5]; // Y (error variable)
|
|
139
|
-
const logger = readMsgMatch[6]; // i0 (logger)
|
|
140
|
-
|
|
141
|
-
// Manual line reader: use stream 'data' events + buffer splitting
|
|
142
|
-
// This avoids readline's broken async iterator on Bun/Windows
|
|
143
|
-
const newReadMsg =
|
|
144
|
-
`/*__ppm_manual_readline__*/async*readMessages(){` +
|
|
145
|
-
`if(!this.processStdout)throw Error("ProcessTransport output stream not available");` +
|
|
146
|
-
// Create a manual async line iterator using stream events
|
|
147
|
-
`let _buf="";` +
|
|
148
|
-
`const _lines=[];` +
|
|
149
|
-
`let _done=false;` +
|
|
150
|
-
`let _err=null;` +
|
|
151
|
-
`let _resolve=null;` +
|
|
152
|
-
`const _notify=()=>{if(_resolve){const r=_resolve;_resolve=null;r()}};` +
|
|
153
|
-
`this.processStdout.setEncoding("utf8");` +
|
|
154
|
-
`this.processStdout.on("data",(chunk)=>{` +
|
|
155
|
-
`_buf+=chunk;` +
|
|
156
|
-
`let nl;` +
|
|
157
|
-
`while((nl=_buf.indexOf("\\n"))!==-1){` +
|
|
158
|
-
`_lines.push(_buf.slice(0,nl));` +
|
|
159
|
-
`_buf=_buf.slice(nl+1)` +
|
|
160
|
-
`}` +
|
|
161
|
-
`_notify()` +
|
|
162
|
-
`});` +
|
|
163
|
-
`this.processStdout.on("end",()=>{` +
|
|
164
|
-
`if(_buf.trim())_lines.push(_buf);` +
|
|
165
|
-
`_buf="";_done=true;_notify()` +
|
|
166
|
-
`});` +
|
|
167
|
-
`this.processStdout.on("error",(e)=>{_err=e;_done=true;_notify()});` +
|
|
168
|
-
// Bun on Windows may not auto-switch to flowing mode when "data" listener is added.
|
|
169
|
-
// Explicit resume() ensures data events fire.
|
|
170
|
-
`this.processStdout.resume();` +
|
|
171
|
-
`try{` +
|
|
172
|
-
`while(true){` +
|
|
173
|
-
`while(_lines.length>0){` +
|
|
174
|
-
`const ${lineVar}=_lines.shift();` +
|
|
175
|
-
`if(${lineVar}.trim())` +
|
|
176
|
-
`try{yield ${parseJSON}(${lineVar})}` +
|
|
177
|
-
`catch(${errVar}){` +
|
|
178
|
-
`throw ${logger}(\`Non-JSON stdout: \${${lineVar}}\`),` +
|
|
179
|
-
`Error(\`CLI output was not valid JSON. This may indicate an error during startup. Output: \${${lineVar}.slice(0,200)}\${${lineVar}.length>200?"...":""}\`)` +
|
|
180
|
-
`}` +
|
|
181
|
-
`}` +
|
|
182
|
-
`if(_err)throw _err;` +
|
|
183
|
-
`if(_done)break;` +
|
|
184
|
-
`await new Promise(r=>{_resolve=r})` +
|
|
185
|
-
`}` +
|
|
186
|
-
`await this.waitForExit()` +
|
|
187
|
-
`}catch(${lineVar}){throw ${lineVar}}}`;
|
|
188
|
-
|
|
189
|
-
content = content.replace(oldReadMsg, newReadMsg);
|
|
190
|
-
patches++;
|
|
191
|
-
console.log("[patch-sdk] Patch 3 (readline): applied");
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (patches > 0) {
|
|
196
|
-
writeFileSync(sdkPath, content, "utf8");
|
|
197
|
-
console.log(`[patch-sdk] Done — ${patches} patch(es) written`);
|
|
198
|
-
} else {
|
|
199
|
-
console.log("[patch-sdk] No patches needed");
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Auto-run when executed directly (postinstall)
|
|
204
|
-
if (process.argv[1]?.endsWith("patch-sdk.mjs")) {
|
|
205
|
-
const sdkPath = join(
|
|
206
|
-
import.meta.dirname,
|
|
207
|
-
"..",
|
|
208
|
-
"node_modules",
|
|
209
|
-
"@anthropic-ai",
|
|
210
|
-
"claude-agent-sdk",
|
|
211
|
-
"sdk.mjs",
|
|
212
|
-
);
|
|
213
|
-
patchSdk(sdkPath);
|
|
214
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Reproduces the stdin backpressure bug that causes SDK to hang on Windows.
|
|
4
|
-
*
|
|
5
|
-
* The issue: ProcessTransport.write() calls stdin.write() but ignores the
|
|
6
|
-
* return value (false = buffer full). Without awaiting 'drain', the subprocess
|
|
7
|
-
* may never receive the data — causing a hang.
|
|
8
|
-
*
|
|
9
|
-
* This script simulates the scenario with a slow-reading subprocess.
|
|
10
|
-
* On macOS/Linux the OS pipe buffer is larger (64KB+), so we write enough
|
|
11
|
-
* to overflow it. On Windows + Bun, even small writes can trigger this.
|
|
12
|
-
*
|
|
13
|
-
* Usage: node scripts/test-drain-bug.mjs
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { spawn } from "node:child_process";
|
|
17
|
-
|
|
18
|
-
// Subprocess that reads stdin slowly (simulates claude CLI processing)
|
|
19
|
-
const slowReader = spawn("node", [
|
|
20
|
-
"-e",
|
|
21
|
-
`
|
|
22
|
-
// Read stdin 1 byte at a time with delays to create backpressure
|
|
23
|
-
process.stdin.setEncoding("utf8");
|
|
24
|
-
let total = 0;
|
|
25
|
-
process.stdin.on("data", (chunk) => {
|
|
26
|
-
total += chunk.length;
|
|
27
|
-
// Pause stdin to simulate slow processing (like claude thinking)
|
|
28
|
-
process.stdin.pause();
|
|
29
|
-
setTimeout(() => process.stdin.resume(), 50);
|
|
30
|
-
});
|
|
31
|
-
process.stdin.on("end", () => {
|
|
32
|
-
process.stdout.write(JSON.stringify({ received: total }));
|
|
33
|
-
});
|
|
34
|
-
`,
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
const CHUNK = "x".repeat(1024); // 1KB chunk
|
|
38
|
-
const TOTAL_WRITES = 256; // 256KB total — enough to overflow pipe buffer
|
|
39
|
-
|
|
40
|
-
// ── Test 1: WITHOUT drain (current SDK behavior) ──
|
|
41
|
-
console.log("=== Test 1: Write WITHOUT drain (current SDK bug) ===");
|
|
42
|
-
let writesFailed = 0;
|
|
43
|
-
let writesOk = 0;
|
|
44
|
-
|
|
45
|
-
for (let i = 0; i < TOTAL_WRITES; i++) {
|
|
46
|
-
const ok = slowReader.stdin.write(CHUNK);
|
|
47
|
-
if (!ok) writesFailed++;
|
|
48
|
-
else writesOk++;
|
|
49
|
-
}
|
|
50
|
-
slowReader.stdin.end();
|
|
51
|
-
|
|
52
|
-
let output = "";
|
|
53
|
-
slowReader.stdout.on("data", (d) => (output += d));
|
|
54
|
-
|
|
55
|
-
await new Promise((resolve) => slowReader.on("close", resolve));
|
|
56
|
-
const result1 = JSON.parse(output || '{"received":0}');
|
|
57
|
-
|
|
58
|
-
console.log(` Writes OK: ${writesOk}`);
|
|
59
|
-
console.log(` Writes FULL: ${writesFailed} (buffer was full, SDK just logs & continues)`);
|
|
60
|
-
console.log(` Data sent: ${TOTAL_WRITES * 1024} bytes`);
|
|
61
|
-
console.log(` Data received: ${result1.received} bytes`);
|
|
62
|
-
console.log(
|
|
63
|
-
` Lost data: ${writesFailed > 0 ? "POSSIBLE — depends on OS buffer behavior" : "none (buffer was big enough)"}`,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// ── Test 2: WITH drain (patched behavior) ──
|
|
67
|
-
console.log("\n=== Test 2: Write WITH drain (patched SDK) ===");
|
|
68
|
-
|
|
69
|
-
const slowReader2 = spawn("node", [
|
|
70
|
-
"-e",
|
|
71
|
-
`
|
|
72
|
-
process.stdin.setEncoding("utf8");
|
|
73
|
-
let total = 0;
|
|
74
|
-
process.stdin.on("data", (chunk) => {
|
|
75
|
-
total += chunk.length;
|
|
76
|
-
process.stdin.pause();
|
|
77
|
-
setTimeout(() => process.stdin.resume(), 50);
|
|
78
|
-
});
|
|
79
|
-
process.stdin.on("end", () => {
|
|
80
|
-
process.stdout.write(JSON.stringify({ received: total }));
|
|
81
|
-
});
|
|
82
|
-
`,
|
|
83
|
-
]);
|
|
84
|
-
|
|
85
|
-
let drainWaits = 0;
|
|
86
|
-
const start = Date.now();
|
|
87
|
-
|
|
88
|
-
for (let i = 0; i < TOTAL_WRITES; i++) {
|
|
89
|
-
const ok = slowReader2.stdin.write(CHUNK);
|
|
90
|
-
if (!ok) {
|
|
91
|
-
drainWaits++;
|
|
92
|
-
await new Promise((r) => slowReader2.stdin.once("drain", r));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
slowReader2.stdin.end();
|
|
96
|
-
|
|
97
|
-
let output2 = "";
|
|
98
|
-
slowReader2.stdout.on("data", (d) => (output2 += d));
|
|
99
|
-
await new Promise((resolve) => slowReader2.on("close", resolve));
|
|
100
|
-
const result2 = JSON.parse(output2 || '{"received":0}');
|
|
101
|
-
const elapsed = Date.now() - start;
|
|
102
|
-
|
|
103
|
-
console.log(` Drain waits: ${drainWaits}`);
|
|
104
|
-
console.log(` Data sent: ${TOTAL_WRITES * 1024} bytes`);
|
|
105
|
-
console.log(` Data received: ${result2.received} bytes`);
|
|
106
|
-
console.log(` Match: ${result2.received === TOTAL_WRITES * 1024 ? "YES — all data delivered" : "NO — data lost!"}`);
|
|
107
|
-
console.log(` Time: ${elapsed}ms (slower due to drain waits, but reliable)`);
|
|
108
|
-
|
|
109
|
-
// ── Summary ──
|
|
110
|
-
console.log("\n=== Summary ===");
|
|
111
|
-
if (writesFailed > 0) {
|
|
112
|
-
console.log(
|
|
113
|
-
`Buffer overflowed ${writesFailed}x in Test 1 (no drain).`,
|
|
114
|
-
);
|
|
115
|
-
console.log(
|
|
116
|
-
"On Windows + Bun, this causes the SDK subprocess to hang indefinitely.",
|
|
117
|
-
);
|
|
118
|
-
console.log(
|
|
119
|
-
"The patch adds 'await drain' to prevent data loss → fixes the hang.",
|
|
120
|
-
);
|
|
121
|
-
} else {
|
|
122
|
-
console.log(
|
|
123
|
-
"Buffer did NOT overflow on this OS (macOS/Linux has large pipe buffers).",
|
|
124
|
-
);
|
|
125
|
-
console.log(
|
|
126
|
-
"On Windows + Bun, pipe buffers are smaller → overflow happens even with small prompts.",
|
|
127
|
-
);
|
|
128
|
-
console.log(
|
|
129
|
-
"The patch is still correct: it's a no-op when write() returns true.",
|
|
130
|
-
);
|
|
131
|
-
}
|
package/test-sdk.mjs
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal SDK test — run on Windows to diagnose Bun + SDK issue.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* bun test-sdk.mjs
|
|
6
|
-
* node --experimental-strip-types test-sdk.mjs
|
|
7
|
-
*/
|
|
8
|
-
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
|
-
import { spawnSync } from "node:child_process";
|
|
11
|
-
|
|
12
|
-
// Remove CLAUDECODE to avoid nested session error
|
|
13
|
-
delete process.env.CLAUDECODE;
|
|
14
|
-
|
|
15
|
-
const cwd = homedir();
|
|
16
|
-
|
|
17
|
-
console.log("=== SDK Test ===");
|
|
18
|
-
console.log(`Platform: ${process.platform}`);
|
|
19
|
-
console.log(`Runtime: ${typeof Bun !== "undefined" ? `Bun ${Bun.version}` : `Node ${process.version}`}`);
|
|
20
|
-
console.log(`CWD: ${cwd}`);
|
|
21
|
-
console.log(`API_KEY: ${process.env.ANTHROPIC_API_KEY ? "SET" : "unset"}`);
|
|
22
|
-
|
|
23
|
-
// Test 1: claude --version
|
|
24
|
-
console.log("\n--- Test 1: claude --version ---");
|
|
25
|
-
try {
|
|
26
|
-
const ver = spawnSync("claude", ["--version"], { encoding: "utf-8", timeout: 10000 });
|
|
27
|
-
console.log(`exit=${ver.status} stdout="${ver.stdout?.trim()}" stderr="${ver.stderr?.trim()}"`);
|
|
28
|
-
} catch (e) {
|
|
29
|
-
console.log(`FAILED: ${e.message}`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Test 2: claude -p (direct CLI)
|
|
33
|
-
console.log("\n--- Test 2: claude -p (direct spawn) ---");
|
|
34
|
-
try {
|
|
35
|
-
const direct = spawnSync("claude", ["-p", "say ok", "--output-format", "text", "--max-turns", "1"], {
|
|
36
|
-
encoding: "utf-8",
|
|
37
|
-
timeout: 30000,
|
|
38
|
-
cwd,
|
|
39
|
-
env: process.env,
|
|
40
|
-
});
|
|
41
|
-
console.log(`exit=${direct.status}`);
|
|
42
|
-
console.log(`stdout="${direct.stdout?.trim().slice(0, 200)}"`);
|
|
43
|
-
if (direct.stderr?.trim()) console.log(`stderr="${direct.stderr.trim().slice(0, 200)}"`);
|
|
44
|
-
} catch (e) {
|
|
45
|
-
console.log(`FAILED: ${e.message}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Test 3: SDK query()
|
|
49
|
-
console.log("\n--- Test 3: SDK query() ---");
|
|
50
|
-
const startTime = Date.now();
|
|
51
|
-
const TIMEOUT = 15000;
|
|
52
|
-
let gotEvent = false;
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const q = query({
|
|
56
|
-
prompt: "say ok",
|
|
57
|
-
options: {
|
|
58
|
-
cwd,
|
|
59
|
-
maxTurns: 1,
|
|
60
|
-
permissionMode: "bypassPermissions",
|
|
61
|
-
allowDangerouslySkipPermissions: true,
|
|
62
|
-
systemPrompt: { type: "custom", custom: "Reply only with: ok" },
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Race first event against timeout
|
|
67
|
-
const iterator = q[Symbol.asyncIterator]();
|
|
68
|
-
const result = await Promise.race([
|
|
69
|
-
iterator.next(),
|
|
70
|
-
new Promise((resolve) => setTimeout(() => resolve("TIMEOUT"), TIMEOUT)),
|
|
71
|
-
]);
|
|
72
|
-
|
|
73
|
-
if (result === "TIMEOUT") {
|
|
74
|
-
console.log(`TIMEOUT: no events after ${TIMEOUT / 1000}s`);
|
|
75
|
-
console.log(">>> This confirms the Bun + Windows SDK issue <<<");
|
|
76
|
-
try { q.close(); } catch {}
|
|
77
|
-
} else {
|
|
78
|
-
gotEvent = true;
|
|
79
|
-
const elapsed = Date.now() - startTime;
|
|
80
|
-
const msg = result.value;
|
|
81
|
-
console.log(`First event in ${elapsed}ms: type=${msg?.type} subtype=${msg?.subtype ?? "none"}`);
|
|
82
|
-
|
|
83
|
-
// Read remaining events
|
|
84
|
-
let count = 1;
|
|
85
|
-
for await (const ev of { [Symbol.asyncIterator]: () => iterator }) {
|
|
86
|
-
count++;
|
|
87
|
-
if (ev.type === "assistant") {
|
|
88
|
-
const text = ev.message?.content?.find((b) => b.type === "text")?.text ?? "";
|
|
89
|
-
console.log(`Event #${count}: assistant text="${text.slice(0, 100)}"`);
|
|
90
|
-
} else if (ev.type === "result") {
|
|
91
|
-
console.log(`Event #${count}: result subtype=${ev.subtype}`);
|
|
92
|
-
break;
|
|
93
|
-
} else {
|
|
94
|
-
console.log(`Event #${count}: ${ev.type}`);
|
|
95
|
-
}
|
|
96
|
-
if (count > 20) { console.log("(stopping after 20 events)"); break; }
|
|
97
|
-
}
|
|
98
|
-
console.log(`\nSUCCESS: SDK works! Total events: ${count}`);
|
|
99
|
-
}
|
|
100
|
-
} catch (e) {
|
|
101
|
-
console.log(`ERROR: ${e.message}`);
|
|
102
|
-
if (e.stack) console.log(e.stack);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
console.log(`\nTotal time: ${((Date.now() - startTime) / 1000).toFixed(1)}s`);
|
|
106
|
-
process.exit(gotEvent ? 0 : 1);
|