@hienlh/ppm 0.8.0 → 0.8.1
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,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.1] - 2026-03-23
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Silent timeout on account decrypt failure**: When all account tokens fail decryption (e.g. different machine key in WSL), now shows actionable error instead of 120s silent timeout
|
|
7
|
+
- **SDK error extraction**: Use `errors: string[]` array per SDK spec instead of non-existent singular `error` field — previously swallowed error details
|
|
8
|
+
- **Assistant message error detection**: Handle `SDKAssistantMessage.error` field for `authentication_failed`, `billing_error`, `rate_limit`, `server_error` per SDK spec
|
|
9
|
+
- **Empty success detection**: Detect SDK returning `success` with 0 turns and no content as silent failure, surface guidance to user
|
|
10
|
+
- **Network error hints**: Add WSL-specific hints for ConnectionRefused, auth failures, and connectivity issues
|
|
11
|
+
|
|
3
12
|
## [0.8.0] - 2026-03-23
|
|
4
13
|
|
|
5
14
|
### Added
|
package/package.json
CHANGED
|
@@ -106,7 +106,9 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
106
106
|
if (!event || typeof event !== "object") return null;
|
|
107
107
|
const e = event as Record<string, unknown>;
|
|
108
108
|
if (e.type === "result" && e.subtype === "error_during_execution") {
|
|
109
|
-
|
|
109
|
+
// SDK uses `errors: string[]` array for error details
|
|
110
|
+
const errorsArr = Array.isArray(e.errors) ? (e.errors as string[]).join(" ") : "";
|
|
111
|
+
const msg = errorsArr || String(e.error ?? "");
|
|
110
112
|
if (msg.includes("429") || msg.toLowerCase().includes("rate limit") || msg.toLowerCase().includes("overloaded")) return 429;
|
|
111
113
|
if (msg.includes("401") || msg.toLowerCase().includes("unauthorized") || msg.toLowerCase().includes("invalid api key")) return 401;
|
|
112
114
|
}
|
|
@@ -533,7 +535,19 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
533
535
|
|
|
534
536
|
// Account-based auth injection (multi-account mode)
|
|
535
537
|
// Fallback to existing env (ANTHROPIC_API_KEY) when no accounts configured.
|
|
536
|
-
const
|
|
538
|
+
const accountsEnabled = accountSelector.isEnabled();
|
|
539
|
+
const account = accountsEnabled ? accountSelector.next() : null;
|
|
540
|
+
if (accountsEnabled && !account) {
|
|
541
|
+
// All accounts in DB but none usable
|
|
542
|
+
const reason = accountSelector.lastFailReason;
|
|
543
|
+
const hint = reason === "all_decrypt_failed"
|
|
544
|
+
? "Account tokens were encrypted with a different machine key. Re-add your accounts in Settings, or copy ~/.ppm/account.key from the original machine."
|
|
545
|
+
: "All accounts are disabled or in cooldown. Check Settings → Accounts.";
|
|
546
|
+
console.error(`[sdk] session=${sessionId} account auth failed (${reason}): ${hint}`);
|
|
547
|
+
yield { type: "error" as const, message: `Authentication failed: ${hint}` };
|
|
548
|
+
yield { type: "done" as const, sessionId, resultSubtype: "error_auth" };
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
537
551
|
if (account) {
|
|
538
552
|
console.log(`[sdk] Using account ${account.id} (${account.email ?? "no-email"})`);
|
|
539
553
|
yield { type: "account_info" as const, accountId: account.id, accountLabel: account.label ?? account.email ?? "Unknown" };
|
|
@@ -774,6 +788,20 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
774
788
|
|
|
775
789
|
// Full assistant message
|
|
776
790
|
if (msg.type === "assistant") {
|
|
791
|
+
// SDK assistant messages can carry an error field for auth/billing/rate-limit failures
|
|
792
|
+
const assistantError = (msg as any).error as string | undefined;
|
|
793
|
+
if (assistantError) {
|
|
794
|
+
const errorHints: Record<string, string> = {
|
|
795
|
+
authentication_failed: "API authentication failed. Check your account credentials in Settings → Accounts.",
|
|
796
|
+
billing_error: "Billing error on this account. Check your subscription status.",
|
|
797
|
+
rate_limit: "Rate limited by the API. Please wait and try again.",
|
|
798
|
+
invalid_request: "Invalid request sent to the API.",
|
|
799
|
+
server_error: "Anthropic API server error. Try again shortly.",
|
|
800
|
+
};
|
|
801
|
+
const hint = errorHints[assistantError] ?? `API error: ${assistantError}`;
|
|
802
|
+
console.error(`[sdk] session=${sessionId} assistant error: ${assistantError}`);
|
|
803
|
+
yield { type: "error", message: hint };
|
|
804
|
+
}
|
|
777
805
|
const content = (msg as any).message?.content;
|
|
778
806
|
if (Array.isArray(content)) {
|
|
779
807
|
for (const block of content) {
|
|
@@ -875,9 +903,11 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
875
903
|
|
|
876
904
|
// Surface non-success subtypes as errors so FE can display them
|
|
877
905
|
if (subtype && subtype !== "success") {
|
|
878
|
-
//
|
|
879
|
-
const
|
|
880
|
-
const sdkDetail =
|
|
906
|
+
// SDK error results use `errors: string[]` array (not singular `error`)
|
|
907
|
+
const errorsArr = Array.isArray(result.errors) ? result.errors : [];
|
|
908
|
+
const sdkDetail = errorsArr.length > 0
|
|
909
|
+
? errorsArr.join("\n")
|
|
910
|
+
: (typeof result.error === "string" ? result.error : "");
|
|
881
911
|
// Log full result for debugging (truncated at 2000 chars)
|
|
882
912
|
console.error(`[sdk] result error: subtype=${subtype} turns=${result.num_turns ?? 0} detail=${sdkDetail || "(none)"}`);
|
|
883
913
|
console.error(`[sdk] result full dump: ${JSON.stringify(result).slice(0, 2000)}`);
|
|
@@ -887,13 +917,35 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
887
917
|
error_during_execution: "Agent encountered an error during execution.",
|
|
888
918
|
};
|
|
889
919
|
const baseMsg = errorMessages[subtype] ?? `Agent stopped: ${subtype}`;
|
|
890
|
-
|
|
920
|
+
// Add specific hints for common network/auth errors
|
|
921
|
+
const detailLower = sdkDetail.toLowerCase();
|
|
922
|
+
let hint = "";
|
|
923
|
+
if (detailLower.includes("connectionrefused") || detailLower.includes("connection refused") || detailLower.includes("econnrefused")) {
|
|
924
|
+
hint = "\n\nHint: Cannot reach Anthropic API. If running in WSL, check DNS/proxy settings (e.g. `curl -s https://api.anthropic.com` from WSL terminal).";
|
|
925
|
+
} else if (detailLower.includes("unable to connect")) {
|
|
926
|
+
hint = "\n\nHint: Network connectivity issue. Check your internet connection and firewall/proxy settings.";
|
|
927
|
+
} else if (detailLower.includes("401") || detailLower.includes("unauthorized") || detailLower.includes("invalid api key")) {
|
|
928
|
+
hint = "\n\nHint: Authentication failed. Try re-adding your account in Settings → Accounts.";
|
|
929
|
+
}
|
|
930
|
+
const fullMsg = sdkDetail ? `${baseMsg}\n${sdkDetail}${hint}` : baseMsg;
|
|
891
931
|
yield {
|
|
892
932
|
type: "error",
|
|
893
933
|
message: fullMsg,
|
|
894
934
|
};
|
|
895
935
|
}
|
|
896
936
|
|
|
937
|
+
// Detect empty/suspicious success — SDK returned "success" but no real assistant content
|
|
938
|
+
if ((!subtype || subtype === "success") && (result.num_turns ?? 0) === 0 && !assistantContent) {
|
|
939
|
+
// SDK success result has `result: string` containing final text
|
|
940
|
+
const resultText = typeof result.result === "string" ? result.result : "";
|
|
941
|
+
console.warn(`[sdk] session=${sessionId} result success but 0 turns, no assistant content, result="${resultText.slice(0, 200)}"`);
|
|
942
|
+
console.warn(`[sdk] result dump: ${JSON.stringify(result).slice(0, 2000)}`);
|
|
943
|
+
const hint = resultText
|
|
944
|
+
? `Claude returned: "${resultText}"\nThis may indicate a session or connection issue. Try creating a new chat session.`
|
|
945
|
+
: "Claude returned no response (0 turns). This usually means the API connection failed silently. Check that `claude` CLI works in your terminal, or try creating a new chat session.";
|
|
946
|
+
yield { type: "error", message: hint };
|
|
947
|
+
}
|
|
948
|
+
|
|
897
949
|
// Store subtype and numTurns for the done event
|
|
898
950
|
resultSubtype = subtype;
|
|
899
951
|
resultNumTurns = result.num_turns as number | undefined;
|
|
@@ -961,7 +1013,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
961
1013
|
if (retryMsg.type === "result") {
|
|
962
1014
|
const r = retryMsg as any;
|
|
963
1015
|
if (r.subtype && r.subtype !== "success") {
|
|
964
|
-
|
|
1016
|
+
const retryErrors = Array.isArray(r.errors) ? r.errors.join("\n") : "";
|
|
1017
|
+
yield { type: "error", message: retryErrors || `Agent stopped: ${r.subtype}` };
|
|
965
1018
|
}
|
|
966
1019
|
resultSubtype = r.subtype;
|
|
967
1020
|
resultNumTurns = r.num_turns;
|
|
@@ -36,11 +36,20 @@ class AccountSelectorService {
|
|
|
36
36
|
setConfigValue(MAX_RETRY_CONFIG_KEY, String(n));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Reason for the last null return from next() */
|
|
40
|
+
private _lastFailReason: "none" | "no_active" | "all_decrypt_failed" = "none";
|
|
41
|
+
|
|
42
|
+
/** Why the last next() call returned null */
|
|
43
|
+
get lastFailReason(): "none" | "no_active" | "all_decrypt_failed" {
|
|
44
|
+
return this._lastFailReason;
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
/**
|
|
40
48
|
* Pick next available account (skips cooldown/disabled).
|
|
41
49
|
* Returns null if no active accounts available.
|
|
42
50
|
*/
|
|
43
51
|
next(): AccountWithTokens | null {
|
|
52
|
+
this._lastFailReason = "none";
|
|
44
53
|
const now = Math.floor(Date.now() / 1000);
|
|
45
54
|
const allAccounts = accountService.list();
|
|
46
55
|
|
|
@@ -53,7 +62,10 @@ class AccountSelectorService {
|
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
const active = accountService.list().filter((a) => a.status === "active");
|
|
56
|
-
if (active.length === 0)
|
|
65
|
+
if (active.length === 0) {
|
|
66
|
+
this._lastFailReason = "no_active";
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
57
69
|
|
|
58
70
|
let pickedId: string;
|
|
59
71
|
if (this.getStrategy() === "fill-first") {
|
|
@@ -66,7 +78,11 @@ class AccountSelectorService {
|
|
|
66
78
|
this.cursor = (this.cursor + 1) % active.length;
|
|
67
79
|
}
|
|
68
80
|
this._lastPickedId = pickedId;
|
|
69
|
-
|
|
81
|
+
const result = accountService.getWithTokens(pickedId);
|
|
82
|
+
if (!result) {
|
|
83
|
+
this._lastFailReason = "all_decrypt_failed";
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
/** Called when account receives 429 — apply exponential backoff */
|
package/src/types/chat.ts
CHANGED
|
@@ -79,7 +79,8 @@ export type ResultSubtype =
|
|
|
79
79
|
| "success"
|
|
80
80
|
| "error_max_turns"
|
|
81
81
|
| "error_max_budget_usd"
|
|
82
|
-
| "error_during_execution"
|
|
82
|
+
| "error_during_execution"
|
|
83
|
+
| "error_auth";
|
|
83
84
|
|
|
84
85
|
export type ChatEvent =
|
|
85
86
|
| { type: "text"; content: string; parentToolUseId?: string }
|