@hienlh/ppm 0.9.20 → 0.9.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 +5 -0
- package/package.json +1 -1
- package/src/providers/claude-agent-sdk.ts +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.21] - 2026-04-05
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **SDK internal 401 retry loop stuck**: SDK retries 401 errors 10 times with exponential backoff using the same expired token, leaving user stuck at "Thinking..." for 2+ minutes. Now intercepts `api_retry` system events with 401 on first occurrence, refreshes OAuth token immediately, and restarts the query — cutting recovery from ~2 min to ~1 sec.
|
|
7
|
+
|
|
3
8
|
## [0.9.20] - 2026-04-05
|
|
4
9
|
|
|
5
10
|
### Fixed
|
package/package.json
CHANGED
|
@@ -784,6 +784,41 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
784
784
|
continue;
|
|
785
785
|
}
|
|
786
786
|
|
|
787
|
+
// Intercept SDK's internal api_retry with 401 — the SDK will retry up to 10 times
|
|
788
|
+
// with exponential backoff using the same expired token, wasting 2+ minutes.
|
|
789
|
+
// Instead, refresh the OAuth token immediately and restart the query.
|
|
790
|
+
if (subtype === "api_retry" && (msg as any).error_status === 401 && account && !authRetried) {
|
|
791
|
+
authRetried = true;
|
|
792
|
+
try {
|
|
793
|
+
await accountService.refreshAccessToken(account.id, false);
|
|
794
|
+
const refreshedAccount = accountService.getWithTokens(account.id);
|
|
795
|
+
if (refreshedAccount) {
|
|
796
|
+
const label = refreshedAccount.label ?? refreshedAccount.email ?? "Unknown";
|
|
797
|
+
console.log(`[sdk] session=${sessionId} intercepted api_retry 401 — refreshing token for ${account.id} (${label})`);
|
|
798
|
+
yield { type: "account_retry" as const, reason: "Token refreshed", accountId: refreshedAccount.id, accountLabel: label };
|
|
799
|
+
const retryEnv = this.buildQueryEnv(meta.projectPath, refreshedAccount);
|
|
800
|
+
streamCtrl.done();
|
|
801
|
+
q.close();
|
|
802
|
+
const { generator: earlyAuthGen, controller: earlyAuthCtrl } = createMessageChannel();
|
|
803
|
+
const currentSdkId = getSessionMapping(sessionId);
|
|
804
|
+
const canResume = currentSdkId && currentSdkId !== sessionId;
|
|
805
|
+
if (!canResume) earlyAuthCtrl.push(firstMsg);
|
|
806
|
+
const retryOpts = { ...queryOptions, sessionId: undefined, resume: canResume ? currentSdkId : undefined, env: retryEnv };
|
|
807
|
+
const rq = query({
|
|
808
|
+
prompt: earlyAuthGen,
|
|
809
|
+
options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
|
|
810
|
+
});
|
|
811
|
+
this.streamingSessions.set(sessionId, { meta, query: rq, controller: earlyAuthCtrl });
|
|
812
|
+
this.activeQueries.set(sessionId, rq);
|
|
813
|
+
eventSource = rq;
|
|
814
|
+
continue retryLoop;
|
|
815
|
+
}
|
|
816
|
+
} catch (refreshErr) {
|
|
817
|
+
console.error(`[sdk] session=${sessionId} early OAuth refresh failed:`, refreshErr);
|
|
818
|
+
accountSelector.onAuthError(account.id);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
787
822
|
// Yield system events so streaming loop can transition phases
|
|
788
823
|
// (e.g. connecting → thinking when hooks/init arrive)
|
|
789
824
|
yield { type: "system" as any, subtype } as any;
|