@hienlh/ppm 0.8.19 → 0.8.21
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 +10 -0
- package/package.json +1 -1
- package/scripts/patch-sdk.mjs +9 -3
- package/scripts/release.sh +55 -0
- package/src/index.ts +7 -3
- package/src/providers/claude-agent-sdk.ts +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.21] - 2026-03-24
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **OAuth auto-refresh on auth failure**: Automatically refresh expired OAuth token and retry when SDK returns `authentication_failed`, instead of just showing error to user
|
|
7
|
+
|
|
8
|
+
## [0.8.20] - 2026-03-24
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **SDK patch resolution**: Use `fileURLToPath` instead of `new URL().pathname` for cross-platform SDK path resolution; add debug logging for bunx SDK resolution
|
|
12
|
+
|
|
3
13
|
## [0.8.19] - 2026-03-24
|
|
4
14
|
|
|
5
15
|
### Fixed
|
package/package.json
CHANGED
package/scripts/patch-sdk.mjs
CHANGED
|
@@ -46,11 +46,14 @@ if (content.includes("waiting for drain")) {
|
|
|
46
46
|
const arg = drainMatch[1];
|
|
47
47
|
const logger = drainMatch[2];
|
|
48
48
|
|
|
49
|
-
// Replace backpressure line:
|
|
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.
|
|
50
53
|
const newLine =
|
|
51
54
|
`if(!this.processStdin.write(${arg})){` +
|
|
52
|
-
`${logger}("[ProcessTransport] Write buffer full, waiting for drain");` +
|
|
53
|
-
`await new Promise(_dr=>this.processStdin.once("drain",_dr))}`;
|
|
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))}`;
|
|
54
57
|
|
|
55
58
|
content = content.replace(oldLine, newLine);
|
|
56
59
|
|
|
@@ -162,6 +165,9 @@ if (content.includes("__ppm_manual_readline__")) {
|
|
|
162
165
|
`_buf="";_done=true;_notify()` +
|
|
163
166
|
`});` +
|
|
164
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();` +
|
|
165
171
|
`try{` +
|
|
166
172
|
`while(true){` +
|
|
167
173
|
`while(_lines.length>0){` +
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
VERSION="${1:-}"
|
|
5
|
+
if [ -z "$VERSION" ]; then
|
|
6
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
7
|
+
echo "No version specified, using package.json: v$VERSION"
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
# Strip leading 'v' if provided
|
|
11
|
+
VERSION="${VERSION#v}"
|
|
12
|
+
TAG="v$VERSION"
|
|
13
|
+
|
|
14
|
+
echo "=== Release $TAG ==="
|
|
15
|
+
|
|
16
|
+
# 1. Build frontend
|
|
17
|
+
echo "[1/4] Building frontend..."
|
|
18
|
+
bun run build:web
|
|
19
|
+
|
|
20
|
+
# 2. Build binaries for all platforms
|
|
21
|
+
echo "[2/4] Compiling binaries..."
|
|
22
|
+
mkdir -p dist
|
|
23
|
+
|
|
24
|
+
TARGETS=(
|
|
25
|
+
"bun-darwin-arm64:ppm-darwin-arm64"
|
|
26
|
+
"bun-darwin-x64:ppm-darwin-x64"
|
|
27
|
+
"bun-linux-x64:ppm-linux-x64"
|
|
28
|
+
"bun-windows-x64:ppm-windows-x64.exe"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
for entry in "${TARGETS[@]}"; do
|
|
32
|
+
target="${entry%%:*}"
|
|
33
|
+
artifact="${entry##*:}"
|
|
34
|
+
echo " -> $artifact ($target)"
|
|
35
|
+
bun build src/index.ts --compile --target="$target" --outfile="dist/$artifact"
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
# 3. Create tag if not exists
|
|
39
|
+
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
40
|
+
echo "[3/4] Tag $TAG already exists, skipping"
|
|
41
|
+
else
|
|
42
|
+
echo "[3/4] Creating tag $TAG..."
|
|
43
|
+
git tag "$TAG"
|
|
44
|
+
git push origin "$TAG"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# 4. Create or update release
|
|
48
|
+
echo "[4/4] Uploading to GitHub release..."
|
|
49
|
+
if gh release view "$TAG" >/dev/null 2>&1; then
|
|
50
|
+
gh release upload "$TAG" dist/ppm-* --clobber
|
|
51
|
+
else
|
|
52
|
+
gh release create "$TAG" dist/ppm-* --title "$TAG" --generate-notes
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo "=== Done: https://github.com/$(gh repo view --json nameWithOwner -q .nameWithOwner)/releases/tag/$TAG ==="
|
package/src/index.ts
CHANGED
|
@@ -36,10 +36,14 @@ program
|
|
|
36
36
|
}
|
|
37
37
|
// Ensure SDK patches are applied (bunx skips postinstall hooks)
|
|
38
38
|
try {
|
|
39
|
-
const
|
|
39
|
+
const { fileURLToPath } = await import("node:url");
|
|
40
|
+
const sdkPath = fileURLToPath(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
|
|
41
|
+
console.log("[patch-sdk] Resolved SDK at:", sdkPath);
|
|
40
42
|
const { patchSdk } = await import("../scripts/patch-sdk.mjs");
|
|
41
|
-
patchSdk(
|
|
42
|
-
} catch {
|
|
43
|
+
patchSdk(sdkPath);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.log("[patch-sdk] Could not resolve SDK:", (e as Error).message);
|
|
46
|
+
}
|
|
43
47
|
const { startServer } = await import("./server/index.ts");
|
|
44
48
|
await startServer(options);
|
|
45
49
|
});
|
|
@@ -641,8 +641,10 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
641
641
|
|
|
642
642
|
// Retry logic: if SDK returns error_during_execution with 0 turns on first event,
|
|
643
643
|
// it's a transient subprocess failure — retry once before surfacing the error.
|
|
644
|
+
// Also handles authentication_failed by refreshing OAuth token and retrying.
|
|
644
645
|
const MAX_RETRIES = 1;
|
|
645
646
|
let retryCount = 0;
|
|
647
|
+
let authRetried = false;
|
|
646
648
|
|
|
647
649
|
retryLoop: while (true) {
|
|
648
650
|
let sdkEventCount = 0;
|
|
@@ -816,6 +818,52 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
816
818
|
// Dump full SDK message for debugging
|
|
817
819
|
console.error(`[sdk] session=${sessionId} cwd=${effectiveCwd} assistant error: ${assistantError} (isFirst=${isFirstMessage} retry=${retryCount})`);
|
|
818
820
|
console.error(`[sdk] assistant message dump: ${JSON.stringify(msg).slice(0, 2000)}`);
|
|
821
|
+
|
|
822
|
+
// OAuth token expired — refresh and retry once before showing error
|
|
823
|
+
if (assistantError === "authentication_failed" && account && !authRetried) {
|
|
824
|
+
authRetried = true;
|
|
825
|
+
try {
|
|
826
|
+
await accountService.refreshAccessToken(account.id);
|
|
827
|
+
console.log(`[sdk] session=${sessionId} OAuth token refreshed for ${account.id} — retrying`);
|
|
828
|
+
// Re-build env with refreshed token
|
|
829
|
+
const refreshedAccount = accountService.getWithTokens(account.id);
|
|
830
|
+
if (refreshedAccount) {
|
|
831
|
+
const retryEnv = this.buildQueryEnv(meta.projectPath, refreshedAccount);
|
|
832
|
+
const q = query({
|
|
833
|
+
prompt: message,
|
|
834
|
+
options: {
|
|
835
|
+
sessionId: undefined,
|
|
836
|
+
resume: undefined,
|
|
837
|
+
cwd: effectiveCwd,
|
|
838
|
+
systemPrompt: systemPromptOpt,
|
|
839
|
+
settingSources: ["user", "project"],
|
|
840
|
+
env: retryEnv,
|
|
841
|
+
settings: { permissions: { allow: [], deny: [] } },
|
|
842
|
+
allowedTools,
|
|
843
|
+
permissionMode,
|
|
844
|
+
allowDangerouslySkipPermissions: isBypass,
|
|
845
|
+
...(permissionHooks && { hooks: permissionHooks }),
|
|
846
|
+
...(providerConfig.model && { model: providerConfig.model }),
|
|
847
|
+
...(providerConfig.effort && { effort: providerConfig.effort }),
|
|
848
|
+
maxTurns: providerConfig.max_turns ?? 100,
|
|
849
|
+
...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
|
|
850
|
+
...(providerConfig.thinking_budget_tokens != null && {
|
|
851
|
+
thinkingBudgetTokens: providerConfig.thinking_budget_tokens,
|
|
852
|
+
}),
|
|
853
|
+
canUseTool,
|
|
854
|
+
includePartialMessages: true,
|
|
855
|
+
} as any,
|
|
856
|
+
});
|
|
857
|
+
this.activeQueries.set(sessionId, q);
|
|
858
|
+
eventSource = q;
|
|
859
|
+
continue retryLoop;
|
|
860
|
+
}
|
|
861
|
+
} catch (refreshErr) {
|
|
862
|
+
console.error(`[sdk] session=${sessionId} OAuth refresh failed:`, refreshErr);
|
|
863
|
+
accountSelector.onAuthError(account.id);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
819
867
|
const errorHints: Record<string, string> = {
|
|
820
868
|
authentication_failed: "API authentication failed. Check your account credentials in Settings → Accounts.",
|
|
821
869
|
billing_error: "Billing error on this account. Check your subscription status.",
|