@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.8.19",
3
+ "version": "0.8.21",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -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: await drain instead of just logging
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 sdkUrl = import.meta.resolve("@anthropic-ai/claude-agent-sdk");
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(new URL(sdkUrl).pathname);
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.",