@hienlh/ppm 0.13.93 → 0.13.95
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 +15 -1
- package/README.md +15 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{audio-preview-Cwk_Z_u_.js → audio-preview-_926SILu.js} +1 -1
- package/dist/web/assets/{chat-tab-CQNAY-zq.js → chat-tab-CZ4JB8bF.js} +3 -3
- package/dist/web/assets/{code-editor-BgXsb83g.js → code-editor-CgX34_CM.js} +2 -2
- package/dist/web/assets/{conflict-editor-A6O-mhn5.js → conflict-editor-4d7ifSFr.js} +1 -1
- package/dist/web/assets/{database-viewer-CBLEmxas.js → database-viewer-BRGf672-.js} +1 -1
- package/dist/web/assets/{diff-viewer-B371RoWS.js → diff-viewer-C8Dx_mMP.js} +1 -1
- package/dist/web/assets/{docx-preview-DafnN_dz.js → docx-preview-CTC4n52W.js} +1 -1
- package/dist/web/assets/{extension-webview-W7f_5_mG.js → extension-webview-C2-MlEV1.js} +1 -1
- package/dist/web/assets/{git-log-panel-Ce46ptIM.js → git-log-panel-D6XL2Qfe.js} +1 -1
- package/dist/web/assets/{glide-data-grid-CDGRCp0-.js → glide-data-grid-W196CMwG.js} +1 -1
- package/dist/web/assets/{image-preview-sNwg0Z48.js → image-preview-B4vAybDG.js} +1 -1
- package/dist/web/assets/{index-TZXL49LV.js → index-B8jn9Try.js} +4 -4
- package/dist/web/assets/keybindings-store-D3ajyN3W.js +1 -0
- package/dist/web/assets/{markdown-renderer-BujgAML3.js → markdown-renderer-tbhXgrmJ.js} +1 -1
- package/dist/web/assets/notification-store-CuF7CL5K.js +1 -0
- package/dist/web/assets/{pdf-preview-DdgV87Km.js → pdf-preview-CEE9y9ai.js} +1 -1
- package/dist/web/assets/{port-forwarding-tab-Cr0tNsKR.js → port-forwarding-tab-CL03gwO3.js} +1 -1
- package/dist/web/assets/{postgres-viewer-C48Z1EEC.js → postgres-viewer-Cca5RWLN.js} +1 -1
- package/dist/web/assets/{settings-tab-B3npVG3R.js → settings-tab-BIhxSzkH.js} +1 -1
- package/dist/web/assets/{sql-query-editor-BfZjAp4G.js → sql-query-editor-CMXFZyid.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-CsqXOMO-.js → sqlite-viewer-C43nch9A.js} +1 -1
- package/dist/web/assets/{system-monitor-tab-CPcrOG1s.js → system-monitor-tab-DR3Ny9fs.js} +1 -1
- package/dist/web/assets/{terminal-tab-3NFEpEkX.js → terminal-tab-BKZgoFBm.js} +1 -1
- package/dist/web/assets/{video-preview-MnEqu22P.js → video-preview-2xKLGBUs.js} +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/sw.js +1 -1
- package/docs/media/ppm-demo-desktop.gif +0 -0
- package/docs/media/ppm-demo-desktop.mp4 +0 -0
- package/docs/media/ppm-demo-mobile.gif +0 -0
- package/docs/media/ppm-demo-mobile.mp4 +0 -0
- package/package.json +1 -1
- package/src/providers/claude-agent-sdk.ts +43 -8
- package/src/services/jsonl-transcript-parser.ts +13 -1
- package/src/types/chat.ts +1 -1
- package/src/types/config.ts +3 -0
- package/src/web/components/settings/ai-settings-section.tsx +14 -0
- package/src/web/hooks/use-chat.ts +9 -2
- package/src/web/lib/api-settings.ts +1 -0
- package/test-tool.mjs +5 -0
- package/dist/web/assets/keybindings-store-vRxA7Pu1.js +0 -1
- package/dist/web/assets/notification-store-C5AT0vIG.js +0 -1
|
@@ -833,6 +833,15 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
833
833
|
console.log(`[sdk] session=${sessionId} mcpServers: ${Object.keys(mcpServers).join(", ")}`);
|
|
834
834
|
}
|
|
835
835
|
|
|
836
|
+
// 1M context (GA): the CLI enables a 1M window when the model name carries a
|
|
837
|
+
// [1m] suffix. The suffix is stripped before the API call. Requires an entitled
|
|
838
|
+
// account (Max/Team/Enterprise) and a supported model; otherwise the API errors.
|
|
839
|
+
const baseModel = opts?.model ?? providerConfig.model;
|
|
840
|
+
const resolvedModel =
|
|
841
|
+
baseModel && providerConfig.context_1m && !/\[1m\]$/i.test(baseModel)
|
|
842
|
+
? `${baseModel}[1m]`
|
|
843
|
+
: baseModel;
|
|
844
|
+
|
|
836
845
|
const queryOptions: Record<string, any> = {
|
|
837
846
|
// On Windows, child_process.spawn("bun") fails with ENOENT — force node
|
|
838
847
|
...(process.platform === "win32" && { executable: "node" }),
|
|
@@ -849,13 +858,14 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
849
858
|
...(hasMcp && { mcpServers }),
|
|
850
859
|
permissionMode,
|
|
851
860
|
allowDangerouslySkipPermissions: isBypass,
|
|
852
|
-
...(
|
|
861
|
+
...(resolvedModel && { model: resolvedModel }),
|
|
853
862
|
...(providerConfig.effort && { effort: providerConfig.effort }),
|
|
854
863
|
maxTurns: providerConfig.max_turns ?? 1000,
|
|
855
864
|
...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
|
|
856
865
|
...(providerConfig.thinking_budget_tokens != null && {
|
|
857
866
|
maxThinkingTokens: providerConfig.thinking_budget_tokens,
|
|
858
867
|
}),
|
|
868
|
+
...(providerConfig.context_1m && { betas: ["context-1m-2025-08-07"] }),
|
|
859
869
|
includePartialMessages: true,
|
|
860
870
|
stderr: stderrCallback,
|
|
861
871
|
};
|
|
@@ -875,18 +885,28 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
875
885
|
session_id: sessionId,
|
|
876
886
|
};
|
|
877
887
|
|
|
878
|
-
//
|
|
879
|
-
//
|
|
880
|
-
//
|
|
888
|
+
// Once the turn has produced output (top-level assistant text/tool_use), the user
|
|
889
|
+
// message + any tool_results are persisted to JSONL. Mid-turn retries must resume and
|
|
890
|
+
// continue — NOT re-push the original message, which restarts the turn and re-displays
|
|
891
|
+
// an already-answered AskUserQuestion. Persists across retryLoop iterations.
|
|
892
|
+
let turnProgressed = false;
|
|
893
|
+
|
|
894
|
+
// Build a retry message for token-refresh / transient-error retries.
|
|
895
|
+
// Pre-turn (nothing persisted yet): re-push the LATEST user content so the SDK has a
|
|
896
|
+
// prompt (follow-ups via pushMessage keep lastUserContent current).
|
|
897
|
+
// Mid-turn (turnProgressed): the SDK's resume loads the pending tool_result and marks
|
|
898
|
+
// the turn `interrupted_turn`; we push a continuation nudge to drive the generator so
|
|
899
|
+
// it picks up where it stopped instead of duplicating the turn.
|
|
881
900
|
// Also returns the raw content/images for re-populating the new streaming session.
|
|
882
901
|
const buildRetryMsg = () => {
|
|
883
902
|
const ss = this.streamingSessions.get(sessionId);
|
|
884
903
|
const content = ss?.lastUserContent ?? message;
|
|
885
904
|
const images = ss?.lastUserImages;
|
|
905
|
+
const retryContent = turnProgressed ? "Continue from where you left off." : content;
|
|
886
906
|
return {
|
|
887
907
|
msg: {
|
|
888
908
|
type: 'user' as const,
|
|
889
|
-
message: buildMessageParam(
|
|
909
|
+
message: buildMessageParam(retryContent, turnProgressed ? undefined : images),
|
|
890
910
|
parent_tool_use_id: null,
|
|
891
911
|
session_id: sessionId,
|
|
892
912
|
},
|
|
@@ -1165,12 +1185,20 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1165
1185
|
// Detect 401 pattern in text: "Failed to authenticate. API Error: 401 ..."
|
|
1166
1186
|
if (!assistantError) {
|
|
1167
1187
|
const textContent = this.extractAssistantText(msg);
|
|
1168
|
-
if (textContent && /API Error:\s*401\b
|
|
1188
|
+
if (textContent && /API Error:\s*401\b/i.test(textContent)) {
|
|
1169
1189
|
assistantError = "authentication_failed";
|
|
1170
1190
|
console.warn(`[sdk] session=${sessionId} detected 401 in assistant text content — treating as auth error`);
|
|
1171
1191
|
} else if (textContent && /hit your (?:[\w-]+\s+)*limit/i.test(textContent)) {
|
|
1172
1192
|
assistantError = "rate_limit";
|
|
1173
1193
|
console.warn(`[sdk] session=${sessionId} detected quota limit in assistant text content — treating as rate_limit`);
|
|
1194
|
+
} else if (textContent && /API Error:\s*5\d{2}\b/i.test(textContent)) {
|
|
1195
|
+
// 5xx (e.g. 529 Overloaded) — match the explicit "API Error: 5xx" text only.
|
|
1196
|
+
// Treat as server_error so it enters the retry branch and the raw error text is
|
|
1197
|
+
// not streamed as duplicated assistant content. Deliberately NOT keyed off
|
|
1198
|
+
// isApiErrorMessage alone — that flag is also set for non-retryable 4xx errors
|
|
1199
|
+
// (e.g. billing/invalid_request), which must surface immediately, not retry.
|
|
1200
|
+
assistantError = "server_error";
|
|
1201
|
+
console.warn(`[sdk] session=${sessionId} detected API 5xx server error — treating as server_error`);
|
|
1174
1202
|
}
|
|
1175
1203
|
}
|
|
1176
1204
|
|
|
@@ -1228,7 +1256,9 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1228
1256
|
} else {
|
|
1229
1257
|
console.warn(`[sdk] session=${sessionId} rate limited — retrying in ${backoff / 1000}s (attempt ${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})`);
|
|
1230
1258
|
}
|
|
1231
|
-
|
|
1259
|
+
// Replaceable status line (not an `error` event) so repeated retries don't
|
|
1260
|
+
// accumulate duplicate error blocks — only the final error (if any) is shown.
|
|
1261
|
+
yield { type: "status_update", phase: "retrying", message: `Đang thử lại (${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})...` };
|
|
1232
1262
|
await new Promise((r) => setTimeout(r, backoff));
|
|
1233
1263
|
// Close current streaming session and recreate with (potentially new) account env.
|
|
1234
1264
|
const retry4 = buildRetryMsg();
|
|
@@ -1260,6 +1290,9 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1260
1290
|
// Skip emitting the raw 401 error as text content — already shown as error event
|
|
1261
1291
|
continue;
|
|
1262
1292
|
}
|
|
1293
|
+
// Successful top-level assistant output → turn is now persisted to JSONL.
|
|
1294
|
+
// Subsequent retries this turn must continue, not re-push the original message.
|
|
1295
|
+
if (!parentId) turnProgressed = true;
|
|
1263
1296
|
const content = (msg as any).message?.content;
|
|
1264
1297
|
if (Array.isArray(content)) {
|
|
1265
1298
|
for (const block of content) {
|
|
@@ -1325,7 +1358,9 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1325
1358
|
} else {
|
|
1326
1359
|
console.warn(`[sdk] session=${sessionId} result 429 — retrying in ${backoff / 1000}s (attempt ${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})`);
|
|
1327
1360
|
}
|
|
1328
|
-
|
|
1361
|
+
// Replaceable status line (not an `error` event) — avoids accumulating
|
|
1362
|
+
// duplicate error blocks across retries; only a final error is shown.
|
|
1363
|
+
yield { type: "status_update", phase: "retrying", message: `Đang thử lại (${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})...` };
|
|
1329
1364
|
await new Promise((r) => setTimeout(r, backoff));
|
|
1330
1365
|
const retry5 = buildRetryMsg();
|
|
1331
1366
|
closeCurrentStream();
|
|
@@ -49,7 +49,19 @@ export function parseSessionMessage(
|
|
|
49
49
|
(b) => b.type === "text" && typeof b.text === "string" &&
|
|
50
50
|
/Failed to authenticate|API Error: 40[13]|hit your limit|rate.?limit/i.test(b.text as string),
|
|
51
51
|
));
|
|
52
|
-
|
|
52
|
+
// SDK emits a placeholder "No response requested." assistant turn after some
|
|
53
|
+
// interrupted/empty turns (e.g. following a 5xx/Overloaded failure). It carries no
|
|
54
|
+
// value — drop so it doesn't render as a stray bubble when reloading the session.
|
|
55
|
+
const isNoOpAssistant =
|
|
56
|
+
role === "assistant" &&
|
|
57
|
+
Array.isArray(message?.content) &&
|
|
58
|
+
(message!.content as Array<Record<string, unknown>>).length > 0 &&
|
|
59
|
+
(message!.content as Array<Record<string, unknown>>).every(
|
|
60
|
+
(b) => b.type === "text" && typeof b.text === "string" &&
|
|
61
|
+
(b.text as string).trim() === "No response requested.",
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (isSdkErrorMessage || isNoOpAssistant) {
|
|
53
65
|
return {
|
|
54
66
|
id: msg.uuid,
|
|
55
67
|
role,
|
package/src/types/chat.ts
CHANGED
|
@@ -134,7 +134,7 @@ export type ChatEvent =
|
|
|
134
134
|
| { type: "done"; sessionId: string; resultSubtype?: ResultSubtype; numTurns?: number; contextWindowPct?: number; lastMessageUuid?: string }
|
|
135
135
|
| { type: "account_info"; accountId: string; accountLabel: string }
|
|
136
136
|
| { type: "account_retry"; reason: string; accountId?: string; accountLabel?: string }
|
|
137
|
-
| { type: "status_update"; phase: "routing" | "refreshing" | "switching"; message: string; accountLabel?: string }
|
|
137
|
+
| { type: "status_update"; phase: "routing" | "refreshing" | "switching" | "retrying"; message: string; accountLabel?: string }
|
|
138
138
|
| { type: "system"; subtype: string }
|
|
139
139
|
| { type: "team_detected"; teamName: string }
|
|
140
140
|
| { type: "team_updated"; teamName: string; team: unknown }
|
package/src/types/config.ts
CHANGED
|
@@ -63,6 +63,9 @@ export interface AIProviderConfig {
|
|
|
63
63
|
max_budget_usd?: number;
|
|
64
64
|
thinking_budget_tokens?: number;
|
|
65
65
|
agent_teams?: boolean;
|
|
66
|
+
// Enable 1M context window via beta header. Requires an entitled account
|
|
67
|
+
// (Max/Team/Enterprise) and an opus-4/sonnet-4 model; otherwise the API errors.
|
|
68
|
+
context_1m?: boolean;
|
|
66
69
|
|
|
67
70
|
// CLI-specific (Cursor, Codex, Gemini)
|
|
68
71
|
cli_command?: string;
|
|
@@ -285,6 +285,20 @@ export function AISettingsSection({ compact }: { compact?: boolean } = {}) {
|
|
|
285
285
|
{config?.agent_teams && (
|
|
286
286
|
<TeamListSection compact={compact} />
|
|
287
287
|
)}
|
|
288
|
+
|
|
289
|
+
<div className="flex items-center justify-between gap-2">
|
|
290
|
+
<div>
|
|
291
|
+
<Label htmlFor="ai-context-1m" className={compact ? labelSize : undefined}>1M Context Window</Label>
|
|
292
|
+
<p className={`${compact ? "text-[9px]" : "text-[11px]"} text-muted-foreground`}>
|
|
293
|
+
Requires an entitled account (Max/Team/Enterprise) and an Opus 4 / Sonnet 4 model. Other accounts will error.
|
|
294
|
+
</p>
|
|
295
|
+
</div>
|
|
296
|
+
<Switch
|
|
297
|
+
id="ai-context-1m"
|
|
298
|
+
checked={config?.context_1m ?? false}
|
|
299
|
+
onCheckedChange={(v) => handleSave("context_1m", v)}
|
|
300
|
+
/>
|
|
301
|
+
</div>
|
|
288
302
|
</>
|
|
289
303
|
)}
|
|
290
304
|
|
|
@@ -337,13 +337,20 @@ export function useChat(sessionId: string | null, providerId = "claude", project
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
case "error": {
|
|
340
|
-
|
|
341
|
-
|
|
340
|
+
// Safety net: dedupe identical consecutive errors (e.g. repeated 5xx) so a turn
|
|
341
|
+
// never accumulates duplicate error blocks — keep only one.
|
|
342
|
+
const evs = streamingEventsRef.current;
|
|
343
|
+
const lastErr = evs[evs.length - 1];
|
|
344
|
+
const isDupErr = lastErr?.type === "error" && (lastErr as { message?: string }).message === ev.message;
|
|
345
|
+
if (!isDupErr) evs.push(ev as ChatEvent);
|
|
346
|
+
const errEvents = [...evs];
|
|
342
347
|
setMessages((prev) => {
|
|
343
348
|
const last = prev[prev.length - 1];
|
|
344
349
|
if (last?.role === "assistant") {
|
|
345
350
|
return [...prev.slice(0, -1), { ...last, events: errEvents }];
|
|
346
351
|
}
|
|
352
|
+
// System-message fallback: skip if the trailing system message is the same error.
|
|
353
|
+
if (last?.role === "system" && last.content === ev.message) return prev;
|
|
347
354
|
return [...prev, {
|
|
348
355
|
id: `error-${Date.now()}`,
|
|
349
356
|
role: "system" as const,
|
package/test-tool.mjs
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { tmpdir } from "node:os";
|
|
2
2
|
import { ClaudeAgentSdkProvider } from "./src/providers/claude-agent-sdk.ts";
|
|
3
|
+
import { configService } from "./src/services/config.service.ts";
|
|
4
|
+
|
|
5
|
+
// Load real config from SQLite (provider reads model/context_1m from here).
|
|
6
|
+
// Without this, configService falls back to DEFAULT_CONFIG (sonnet-4-6, no 1M).
|
|
7
|
+
configService.load();
|
|
3
8
|
|
|
4
9
|
// Remove CLAUDECODE to avoid nested session error
|
|
5
10
|
delete process.env.CLAUDECODE;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./vendor-markdown-0Mxgxy0L.js";import"./api-client-bbJLzRVE.js";import{L as e}from"./index-TZXL49LV.js";export{e as useKeybindingsStore};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./vendor-markdown-0Mxgxy0L.js";import"./api-client-bbJLzRVE.js";import{W as e}from"./index-TZXL49LV.js";export{e as useNotificationStore};
|