@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/README.md +15 -0
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +1 -1
  5. package/dist/web/assets/{audio-preview-Cwk_Z_u_.js → audio-preview-_926SILu.js} +1 -1
  6. package/dist/web/assets/{chat-tab-CQNAY-zq.js → chat-tab-CZ4JB8bF.js} +3 -3
  7. package/dist/web/assets/{code-editor-BgXsb83g.js → code-editor-CgX34_CM.js} +2 -2
  8. package/dist/web/assets/{conflict-editor-A6O-mhn5.js → conflict-editor-4d7ifSFr.js} +1 -1
  9. package/dist/web/assets/{database-viewer-CBLEmxas.js → database-viewer-BRGf672-.js} +1 -1
  10. package/dist/web/assets/{diff-viewer-B371RoWS.js → diff-viewer-C8Dx_mMP.js} +1 -1
  11. package/dist/web/assets/{docx-preview-DafnN_dz.js → docx-preview-CTC4n52W.js} +1 -1
  12. package/dist/web/assets/{extension-webview-W7f_5_mG.js → extension-webview-C2-MlEV1.js} +1 -1
  13. package/dist/web/assets/{git-log-panel-Ce46ptIM.js → git-log-panel-D6XL2Qfe.js} +1 -1
  14. package/dist/web/assets/{glide-data-grid-CDGRCp0-.js → glide-data-grid-W196CMwG.js} +1 -1
  15. package/dist/web/assets/{image-preview-sNwg0Z48.js → image-preview-B4vAybDG.js} +1 -1
  16. package/dist/web/assets/{index-TZXL49LV.js → index-B8jn9Try.js} +4 -4
  17. package/dist/web/assets/keybindings-store-D3ajyN3W.js +1 -0
  18. package/dist/web/assets/{markdown-renderer-BujgAML3.js → markdown-renderer-tbhXgrmJ.js} +1 -1
  19. package/dist/web/assets/notification-store-CuF7CL5K.js +1 -0
  20. package/dist/web/assets/{pdf-preview-DdgV87Km.js → pdf-preview-CEE9y9ai.js} +1 -1
  21. package/dist/web/assets/{port-forwarding-tab-Cr0tNsKR.js → port-forwarding-tab-CL03gwO3.js} +1 -1
  22. package/dist/web/assets/{postgres-viewer-C48Z1EEC.js → postgres-viewer-Cca5RWLN.js} +1 -1
  23. package/dist/web/assets/{settings-tab-B3npVG3R.js → settings-tab-BIhxSzkH.js} +1 -1
  24. package/dist/web/assets/{sql-query-editor-BfZjAp4G.js → sql-query-editor-CMXFZyid.js} +1 -1
  25. package/dist/web/assets/{sqlite-viewer-CsqXOMO-.js → sqlite-viewer-C43nch9A.js} +1 -1
  26. package/dist/web/assets/{system-monitor-tab-CPcrOG1s.js → system-monitor-tab-DR3Ny9fs.js} +1 -1
  27. package/dist/web/assets/{terminal-tab-3NFEpEkX.js → terminal-tab-BKZgoFBm.js} +1 -1
  28. package/dist/web/assets/{video-preview-MnEqu22P.js → video-preview-2xKLGBUs.js} +1 -1
  29. package/dist/web/index.html +1 -1
  30. package/dist/web/sw.js +1 -1
  31. package/docs/media/ppm-demo-desktop.gif +0 -0
  32. package/docs/media/ppm-demo-desktop.mp4 +0 -0
  33. package/docs/media/ppm-demo-mobile.gif +0 -0
  34. package/docs/media/ppm-demo-mobile.mp4 +0 -0
  35. package/package.json +1 -1
  36. package/src/providers/claude-agent-sdk.ts +43 -8
  37. package/src/services/jsonl-transcript-parser.ts +13 -1
  38. package/src/types/chat.ts +1 -1
  39. package/src/types/config.ts +3 -0
  40. package/src/web/components/settings/ai-settings-section.tsx +14 -0
  41. package/src/web/hooks/use-chat.ts +9 -2
  42. package/src/web/lib/api-settings.ts +1 -0
  43. package/test-tool.mjs +5 -0
  44. package/dist/web/assets/keybindings-store-vRxA7Pu1.js +0 -1
  45. 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
- ...((opts?.model ?? providerConfig.model) && { model: opts?.model ?? providerConfig.model }),
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
- // Build a retry message from the LATEST user content (not the stale firstMsg).
879
- // Follow-ups via pushMessage update lastUserContent on the streaming session,
880
- // so retries after token refresh correctly replay the current turn.
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(content, images),
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.*authentication_error/i.test(textContent)) {
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
- yield { type: "error", message: `Rate limited. Auto-retrying in ${backoff / 1000}s... (${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})` };
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
- yield { type: "error", message: `Rate limited. Auto-retrying in ${backoff / 1000}s... (${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})` };
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
- if (isSdkErrorMessage) {
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 }
@@ -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
- streamingEventsRef.current.push(ev as ChatEvent);
341
- const errEvents = [...streamingEventsRef.current];
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,
@@ -179,6 +179,7 @@ export interface AIProviderSettings {
179
179
  permission_mode?: string;
180
180
  system_prompt?: string;
181
181
  agent_teams?: boolean;
182
+ context_1m?: boolean;
182
183
  }
183
184
 
184
185
  export interface AISettings {
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};