@hienlh/ppm 0.8.39 → 0.8.41

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 (50) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/chat-ui-test-1.png +0 -0
  3. package/chat-ui-test-mid.png +0 -0
  4. package/chat-ui-test-mid2.png +0 -0
  5. package/chat-ui-test-top.png +0 -0
  6. package/chat-ui-v2-mid.png +0 -0
  7. package/dist/web/assets/{api-settings-0Hx_9lIU.js → api-settings-CaKDC7_s.js} +1 -1
  8. package/dist/web/assets/chat-tab-CN-ULuHd.js +7 -0
  9. package/dist/web/assets/{code-editor-DZlXHMtA.js → code-editor-CFZAz_WA.js} +1 -1
  10. package/dist/web/assets/{database-viewer-BaxjPtYR.js → database-viewer-DT9IEf-4.js} +1 -1
  11. package/dist/web/assets/{diff-viewer-CDdO3tqP.js → diff-viewer-SZcQ0Arc.js} +1 -1
  12. package/dist/web/assets/git-graph-DsKiCtcj.js +1 -0
  13. package/dist/web/assets/{index-CvbNQ1mi.js → index-BiOW8qrf.js} +8 -8
  14. package/dist/web/assets/index-CGOBw13I.css +2 -0
  15. package/dist/web/assets/{input-4ElbicvY.js → input-CE3bFwLk.js} +1 -1
  16. package/dist/web/assets/keybindings-store-BShKvCCo.js +1 -0
  17. package/dist/web/assets/markdown-renderer-Cr3-VbIg.js +59 -0
  18. package/dist/web/assets/{postgres-viewer-hmqfZRr-.js → postgres-viewer-BhTz35TV.js} +1 -1
  19. package/dist/web/assets/{settings-store-Clv3ZNje.js → settings-store-xG6mKqkD.js} +2 -2
  20. package/dist/web/assets/settings-tab-TlZunMhi.js +1 -0
  21. package/dist/web/assets/{sqlite-viewer-9X_1ZHJE.js → sqlite-viewer-CnZbwR2F.js} +1 -1
  22. package/dist/web/assets/{tab-store-D7tRt0VT.js → tab-store-NOBndc0_.js} +1 -1
  23. package/dist/web/assets/tag-DJUYe5BQ.js +1 -0
  24. package/dist/web/assets/{terminal-tab-V7x81Qpr.js → terminal-tab-BBy02Lde.js} +1 -1
  25. package/dist/web/assets/{use-monaco-theme-czriskTO.js → use-monaco-theme-DlFSiqvG.js} +1 -1
  26. package/dist/web/index.html +9 -9
  27. package/dist/web/sw.js +1 -1
  28. package/docs/lessons-learned.md +5 -12
  29. package/package.json +2 -3
  30. package/sdh-uit-edu-vn-screenshot.png +0 -0
  31. package/src/index.ts +0 -10
  32. package/src/providers/claude-agent-sdk.ts +57 -292
  33. package/src/server/routes/fs-browse.ts +21 -0
  34. package/src/types/config.ts +0 -8
  35. package/src/web/components/chat/message-list.tsx +163 -55
  36. package/src/web/components/settings/ai-settings-section.tsx +0 -24
  37. package/src/web/components/shared/markdown-renderer.tsx +48 -2
  38. package/src/web/lib/api-settings.ts +0 -1
  39. package/dist/web/assets/chat-tab-CGic5t8w.js +0 -7
  40. package/dist/web/assets/git-graph-C-TRbbx7.js +0 -1
  41. package/dist/web/assets/index-DXTts38q.css +0 -2
  42. package/dist/web/assets/keybindings-store-Dqs-i9cV.js +0 -1
  43. package/dist/web/assets/markdown-renderer-DE503g9L.js +0 -59
  44. package/dist/web/assets/settings-tab-BdgsQeES.js +0 -1
  45. package/scripts/patch-sdk.mjs +0 -214
  46. package/scripts/test-drain-bug.mjs +0 -131
  47. package/test-sdk.mjs +0 -106
  48. /package/dist/web/assets/{api-client-B0aMOJxF.js → api-client-TUmacMRS.js} +0 -0
  49. /package/dist/web/assets/{react-Dk7fkoaB.js → react-rgzL83kk.js} +0 -0
  50. /package/dist/web/assets/{utils-DBpa1UZX.js → utils-DC-bdPS3.js} +0 -0
@@ -118,173 +118,6 @@ export class ClaudeAgentSdkProvider implements AIProvider {
118
118
  return null;
119
119
  }
120
120
 
121
- /**
122
- * Direct CLI fallback for Windows — spawns `claude -p` with stream-json output.
123
- * Workaround for Bun + Windows SDK subprocess pipe buffering issue.
124
- * Returns an async generator yielding the same event types as SDK query().
125
- *
126
- * TODO: Remove this fallback when TypeScript SDK fixes Windows stdin pipe buffering.
127
- * Tracking issues:
128
- * - Python SDK #208 (FIXED): https://github.com/anthropics/claude-agent-sdk-python/issues/208
129
- * Fix: stdin drain() after writes — TypeScript SDK lacks equivalent fix.
130
- * - TS SDK #44 (OPEN): https://github.com/anthropics/claude-agent-sdk-typescript/issues/44
131
- * query() yields zero events for 3+ minutes on Windows.
132
- * - TS SDK #64 (OPEN): https://github.com/anthropics/claude-agent-sdk-typescript/issues/64
133
- * Bash tool hangs on empty output — related pipe/EOF handling issue.
134
- * When these are resolved, switch back to SDK query() by removing the
135
- * `useDirectCli` branch in sendMessage() and deleting this method.
136
- */
137
- private async *queryDirectCli(opts: {
138
- prompt: string;
139
- cwd: string;
140
- sessionId: string;
141
- resumeSessionId?: string;
142
- env: Record<string, string | undefined>;
143
- providerConfig: Partial<import("../types/config.ts").AIProviderConfig>;
144
- }): AsyncGenerator<any> {
145
- const args = ["-p", opts.prompt, "--verbose", "--output-format", "stream-json"];
146
-
147
- // Session management — resume if caller confirmed a valid session ID
148
- if (opts.resumeSessionId) {
149
- args.push("--resume", opts.resumeSessionId);
150
- }
151
-
152
- // Config-driven options
153
- if (opts.providerConfig.model) args.push("--model", opts.providerConfig.model);
154
- const maxTurns = opts.providerConfig.max_turns ?? 100;
155
- args.push("--max-turns", String(maxTurns));
156
- if (opts.providerConfig.effort) args.push("--effort", opts.providerConfig.effort);
157
-
158
- // Permission mode
159
- args.push("--permission-mode", opts.providerConfig.permission_mode ?? "bypassPermissions");
160
- if ((opts.providerConfig.permission_mode ?? "bypassPermissions") === "bypassPermissions") {
161
- args.push("--dangerously-skip-permissions");
162
- }
163
-
164
- // System prompt — CLI uses --append-system-prompt flag
165
- if (opts.providerConfig.system_prompt) {
166
- args.push("--append-system-prompt", opts.providerConfig.system_prompt);
167
- }
168
-
169
- // On Windows, `claude` is a .cmd wrapper (npm global) — Bun.spawn can't resolve .cmd
170
- // files directly. Use `cmd /c` to let the Windows shell find it via PATH.
171
- const cmd = process.platform === "win32"
172
- ? ["cmd", "/c", "claude", ...args]
173
- : ["claude", ...args];
174
- console.log(`[sdk-cli] spawning: ${cmd.slice(0, 7).join(" ")}... cwd=${opts.cwd}`);
175
-
176
- const proc = Bun.spawn({
177
- cmd,
178
- cwd: opts.cwd,
179
- stdout: "pipe",
180
- stderr: "pipe",
181
- env: opts.env as Record<string, string>,
182
- });
183
-
184
- // Store proc for abort support
185
- const abortHandle = { close: () => { try { proc.kill(); } catch {} } };
186
- this.activeQueries.set(opts.sessionId, abortHandle as any);
187
-
188
- try {
189
- const reader = proc.stdout.getReader();
190
- const decoder = new TextDecoder();
191
- let buffer = "";
192
-
193
- while (true) {
194
- const { done, value } = await reader.read();
195
- if (done) break;
196
-
197
- buffer += decoder.decode(value, { stream: true });
198
- const lines = buffer.split("\n");
199
- buffer = lines.pop() ?? ""; // Keep incomplete last line in buffer
200
-
201
- for (const line of lines) {
202
- const trimmed = line.trim();
203
- if (!trimmed) continue;
204
- try {
205
- const event = JSON.parse(trimmed);
206
- // CLI stream-json doesn't emit per-token stream_event deltas — it sends
207
- // complete assistant messages. Synthesize stream_event deltas so the FE
208
- // gets a smooth streaming experience (same as SDK with includePartialMessages).
209
- if (event.type === "assistant" && event.message?.content) {
210
- for (const block of event.message.content) {
211
- if (block.type === "text" && block.text) {
212
- // Emit text in ~30-char chunks as synthetic stream_event deltas
213
- const text = block.text as string;
214
- const CHUNK = 30;
215
- for (let i = 0; i < text.length; i += CHUNK) {
216
- yield {
217
- type: "stream_event",
218
- event: {
219
- type: "content_block_delta",
220
- delta: { type: "text_delta", text: text.slice(i, i + CHUNK) },
221
- },
222
- };
223
- }
224
- } else if (block.type === "thinking" && block.thinking) {
225
- yield {
226
- type: "stream_event",
227
- event: {
228
- type: "content_block_delta",
229
- delta: { type: "thinking_delta", thinking: block.thinking },
230
- },
231
- };
232
- }
233
- }
234
- }
235
- // Log error/result events for diagnostics
236
- if (event.type === "error") {
237
- console.error(`[sdk-cli] error event: ${JSON.stringify(event).slice(0, 1000)}`);
238
- } else if (event.type === "result" && event.is_error) {
239
- console.error(`[sdk-cli] result error: ${JSON.stringify(event).slice(0, 1000)}`);
240
- }
241
- // Always yield the original event too (for init, result, rate_limit, etc.)
242
- yield event;
243
- } catch {
244
- // Skip non-JSON lines (e.g. progress indicators)
245
- }
246
- }
247
- }
248
-
249
- // Process remaining buffer
250
- if (buffer.trim()) {
251
- try { yield JSON.parse(buffer.trim()); } catch {}
252
- }
253
-
254
- // Wait for process to exit
255
- const exitCode = await proc.exited;
256
- console.log(`[sdk-cli] process exited: code=${exitCode}`);
257
-
258
- // Always read stderr for diagnostics (errors can occur even with exit code 0)
259
- try {
260
- const errReader = proc.stderr.getReader();
261
- const stderrDecoder = new TextDecoder();
262
- const errParts: string[] = [];
263
- while (true) {
264
- const { done, value } = await errReader.read();
265
- if (done) break;
266
- if (value) errParts.push(stderrDecoder.decode(value, { stream: true }));
267
- }
268
- const fullStderr = errParts.join("").trim();
269
- if (fullStderr) {
270
- console.error(`[sdk-cli] stderr (last 1500): ${fullStderr.slice(-1500)}`);
271
- if (exitCode !== 0) {
272
- const errMatch = fullStderr.match(/\b(?:Error|TypeError|SyntaxError|ReferenceError|RangeError):\s*.+/);
273
- const errorMsg = errMatch ? errMatch[0].slice(0, 500) : fullStderr.slice(-300);
274
- yield {
275
- type: "result",
276
- subtype: "error_during_execution",
277
- error: `CLI exited with code ${exitCode}: ${errorMsg}`,
278
- };
279
- }
280
- }
281
- } catch {}
282
- } finally {
283
- this.activeQueries.delete(opts.sessionId);
284
- try { proc.kill(); } catch {}
285
- }
286
- }
287
-
288
121
  /** Read current provider config from yaml (fresh each call) */
289
122
  private getProviderConfig(): Partial<import("../types/config.ts").AIProviderConfig> {
290
123
  const ai = configService.get("ai");
@@ -579,68 +412,40 @@ export class ClaudeAgentSdkProvider implements AIProvider {
579
412
  }
580
413
  console.log(`[sdk] query: session=${sessionId} sdkId=${sdkId} isFirst=${isFirstMessage} fork=${shouldFork} cwd=${effectiveCwd} platform=${process.platform} accountMode=${!!account} permissionMode=${permissionMode} isBypass=${isBypass}`);
581
414
 
582
- // Use execution_mode from config (default: "sdk"). CLI mode spawns `claude` binary directly.
583
- const executionMode = providerConfig.execution_mode ?? "sdk";
584
- const useDirectCli = executionMode === "cli";
585
- let eventSource: AsyncIterable<any>;
586
-
587
- if (useDirectCli) {
588
- // Determine resumeSessionId for CLI --resume flag:
589
- // 1. Explicit mapping exists (sdkId !== sessionId) → use mapped SDK ID
590
- // 2. No mapping but session has messages on disk → session was created with PPM UUID, resume it
591
- // 3. No mapping, no messages → truly new session, don't resume
592
- let resumeSessionId: string | undefined;
593
- if (sdkId !== sessionId) {
594
- resumeSessionId = sdkId;
595
- } else if (!isFirstMessage) {
596
- try {
597
- const existingMsgs = await getSessionMessages(sessionId);
598
- if (existingMsgs.length > 0) {
599
- resumeSessionId = sessionId;
600
- setSessionMapping(sessionId, sessionId);
601
- console.log(`[sdk] session ${sessionId} exists on disk (${existingMsgs.length} msgs) — will resume`);
602
- }
603
- } catch {}
604
- }
605
- console.log(`[sdk] Windows detected — using direct CLI fallback (resume=${resumeSessionId ?? "none"})`);
606
- eventSource = this.queryDirectCli({
607
- prompt: message,
608
- cwd: effectiveCwd,
609
- sessionId,
610
- resumeSessionId,
611
- env: queryEnv,
612
- providerConfig,
613
- });
614
- } else {
615
- const q = query({
616
- prompt: message,
617
- options: {
618
- sessionId: isFirstMessage && !shouldFork ? sessionId : undefined,
619
- resume: (isFirstMessage && !shouldFork) ? undefined : sdkId,
620
- ...(shouldFork && { forkSession: true }),
621
- cwd: effectiveCwd,
622
- systemPrompt: systemPromptOpt,
623
- settingSources: ["user", "project"],
624
- env: queryEnv,
625
- settings: { permissions: { allow: [], deny: [] } },
626
- allowedTools,
627
- permissionMode,
628
- allowDangerouslySkipPermissions: isBypass,
629
- ...(permissionHooks && { hooks: permissionHooks }),
630
- ...(providerConfig.model && { model: providerConfig.model }),
631
- ...(providerConfig.effort && { effort: providerConfig.effort }),
632
- maxTurns: providerConfig.max_turns ?? 100,
633
- ...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
634
- ...(providerConfig.thinking_budget_tokens != null && {
635
- thinkingBudgetTokens: providerConfig.thinking_budget_tokens,
636
- }),
637
- canUseTool,
638
- includePartialMessages: true,
639
- } as any,
640
- });
641
- this.activeQueries.set(sessionId, q);
642
- eventSource = q;
643
- }
415
+ const queryOptions: Record<string, any> = {
416
+ // On Windows, child_process.spawn("bun") fails with ENOENT — force node
417
+ ...(process.platform === "win32" && { executable: "node" }),
418
+ sessionId: isFirstMessage && !shouldFork ? sessionId : undefined,
419
+ resume: (isFirstMessage && !shouldFork) ? undefined : sdkId,
420
+ ...(shouldFork && { forkSession: true }),
421
+ cwd: effectiveCwd,
422
+ systemPrompt: systemPromptOpt,
423
+ settingSources: ["user", "project"],
424
+ env: queryEnv,
425
+ settings: { permissions: { allow: [], deny: [] } },
426
+ allowedTools,
427
+ permissionMode,
428
+ allowDangerouslySkipPermissions: isBypass,
429
+ ...(providerConfig.model && { model: providerConfig.model }),
430
+ ...(providerConfig.effort && { effort: providerConfig.effort }),
431
+ maxTurns: providerConfig.max_turns ?? 100,
432
+ ...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
433
+ ...(providerConfig.thinking_budget_tokens != null && {
434
+ thinkingBudgetTokens: providerConfig.thinking_budget_tokens,
435
+ }),
436
+ includePartialMessages: true,
437
+ };
438
+
439
+ const q = query({
440
+ prompt: message,
441
+ options: {
442
+ ...queryOptions,
443
+ ...(permissionHooks && { hooks: permissionHooks }),
444
+ canUseTool,
445
+ } as any,
446
+ });
447
+ this.activeQueries.set(sessionId, q);
448
+ let eventSource: AsyncIterable<any> = q;
644
449
 
645
450
  let lastPartialText = "";
646
451
  /** Number of tool_use blocks pending results (top-level tools only, not subagent children) */
@@ -664,37 +469,13 @@ export class ClaudeAgentSdkProvider implements AIProvider {
664
469
  retryCount++;
665
470
  console.warn(`[sdk] transient error on first event — retrying (attempt ${retryCount}/${MAX_RETRIES})`);
666
471
  // Re-create query for retry — don't reuse sessionId in case SDK partially created it
667
- if (!useDirectCli) {
668
- const q = query({
669
- prompt: message,
670
- options: {
671
- // On retry, let SDK generate a fresh session to avoid conflicts
672
- sessionId: undefined,
673
- resume: undefined,
674
- ...(shouldFork && { forkSession: true }),
675
- cwd: effectiveCwd,
676
- systemPrompt: systemPromptOpt,
677
- settingSources: ["user", "project"],
678
- env: queryEnv,
679
- settings: { permissions: { allow: [], deny: [] } },
680
- allowedTools,
681
- permissionMode,
682
- allowDangerouslySkipPermissions: isBypass,
683
- ...(permissionHooks && { hooks: permissionHooks }),
684
- ...(providerConfig.model && { model: providerConfig.model }),
685
- ...(providerConfig.effort && { effort: providerConfig.effort }),
686
- maxTurns: providerConfig.max_turns ?? 100,
687
- ...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
688
- ...(providerConfig.thinking_budget_tokens != null && {
689
- thinkingBudgetTokens: providerConfig.thinking_budget_tokens,
690
- }),
691
- canUseTool,
692
- includePartialMessages: true,
693
- } as any,
694
- });
695
- this.activeQueries.set(sessionId, q);
696
- eventSource = q;
697
- }
472
+ const retryOpts = { ...queryOptions, sessionId: undefined, resume: undefined };
473
+ const rq = query({
474
+ prompt: message,
475
+ options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
476
+ });
477
+ this.activeQueries.set(sessionId, rq);
478
+ eventSource = rq;
698
479
  continue retryLoop;
699
480
  }
700
481
  }
@@ -836,33 +617,13 @@ export class ClaudeAgentSdkProvider implements AIProvider {
836
617
  const refreshedAccount = accountService.getWithTokens(account.id);
837
618
  if (refreshedAccount) {
838
619
  const retryEnv = this.buildQueryEnv(meta.projectPath, refreshedAccount);
839
- const q = query({
620
+ const retryOpts = { ...queryOptions, sessionId: undefined, resume: undefined, env: retryEnv };
621
+ const rq = query({
840
622
  prompt: message,
841
- options: {
842
- sessionId: undefined,
843
- resume: undefined,
844
- cwd: effectiveCwd,
845
- systemPrompt: systemPromptOpt,
846
- settingSources: ["user", "project"],
847
- env: retryEnv,
848
- settings: { permissions: { allow: [], deny: [] } },
849
- allowedTools,
850
- permissionMode,
851
- allowDangerouslySkipPermissions: isBypass,
852
- ...(permissionHooks && { hooks: permissionHooks }),
853
- ...(providerConfig.model && { model: providerConfig.model }),
854
- ...(providerConfig.effort && { effort: providerConfig.effort }),
855
- maxTurns: providerConfig.max_turns ?? 100,
856
- ...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
857
- ...(providerConfig.thinking_budget_tokens != null && {
858
- thinkingBudgetTokens: providerConfig.thinking_budget_tokens,
859
- }),
860
- canUseTool,
861
- includePartialMessages: true,
862
- } as any,
623
+ options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
863
624
  });
864
- this.activeQueries.set(sessionId, q);
865
- eventSource = q;
625
+ this.activeQueries.set(sessionId, rq);
626
+ eventSource = rq;
866
627
  continue retryLoop;
867
628
  }
868
629
  } catch (refreshErr) {
@@ -1069,9 +830,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1069
830
  const effectiveCwd = meta.projectPath || homedir();
1070
831
  const retryAccount = accountSelector.isEnabled() ? accountSelector.next() : null;
1071
832
  const queryEnv = this.buildQueryEnv(meta.projectPath, retryAccount);
1072
- const retryQuery = query({
1073
- prompt: message,
1074
- options: {
833
+ const retryOptions = {
834
+ ...(process.platform === "win32" && { executable: "node" }),
1075
835
  cwd: effectiveCwd,
1076
836
  systemPrompt: systemPromptOpt,
1077
837
  settingSources: ["user", "project"],
@@ -1080,13 +840,18 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1080
840
  allowedTools,
1081
841
  permissionMode,
1082
842
  allowDangerouslySkipPermissions: isBypass,
1083
- ...(permissionHooks && { hooks: permissionHooks }),
1084
843
  ...(providerConfig.model && { model: providerConfig.model }),
1085
844
  maxTurns: providerConfig.max_turns ?? 100,
1086
- canUseTool,
1087
845
  includePartialMessages: true,
1088
- } as any,
1089
- });
846
+ };
847
+ const retryQuery = query({
848
+ prompt: message,
849
+ options: {
850
+ ...retryOptions,
851
+ ...(permissionHooks && { hooks: permissionHooks }),
852
+ canUseTool,
853
+ } as any,
854
+ });
1090
855
  this.activeQueries.set(sessionId, retryQuery);
1091
856
  for await (const retryMsg of retryQuery) {
1092
857
  if (retryMsg.type === "system") continue;
@@ -1,4 +1,5 @@
1
1
  import { Hono } from "hono";
2
+ import { existsSync } from "fs";
2
3
  import {
3
4
  browse,
4
5
  list,
@@ -52,6 +53,26 @@ fsBrowseRoutes.get("/read", (c) => {
52
53
  }
53
54
  });
54
55
 
56
+ /** GET /api/fs/raw?path=/some/file — serve file as binary (for images in markdown, etc.) */
57
+ fsBrowseRoutes.get("/raw", (c) => {
58
+ try {
59
+ const filePath = c.req.query("path");
60
+ if (!filePath) return c.json(err("path is required"), 400);
61
+ if (!existsSync(filePath)) return c.json(err("File not found"), 404);
62
+
63
+ const file = Bun.file(filePath);
64
+ return new Response(file.stream(), {
65
+ headers: {
66
+ "Content-Type": file.type || "application/octet-stream",
67
+ "Content-Disposition": "inline",
68
+ "Cache-Control": "private, max-age=3600",
69
+ },
70
+ });
71
+ } catch (e) {
72
+ return c.json(err((e as Error).message), errorStatus(e));
73
+ }
74
+ });
75
+
55
76
  /** PUT /api/fs/write — write file outside project { path, content } */
56
77
  fsBrowseRoutes.put("/write", async (c) => {
57
78
  try {
@@ -43,12 +43,8 @@ export interface AIConfig {
43
43
  const VALID_PERMISSION_MODES = ["default", "acceptEdits", "plan", "bypassPermissions"] as const;
44
44
  export type PermissionMode = typeof VALID_PERMISSION_MODES[number];
45
45
 
46
- const VALID_EXECUTION_MODES = ["sdk", "cli"] as const;
47
- export type ExecutionMode = typeof VALID_EXECUTION_MODES[number];
48
-
49
46
  export interface AIProviderConfig {
50
47
  type: "agent-sdk" | "mock";
51
- execution_mode?: ExecutionMode;
52
48
  api_key_env?: string;
53
49
  base_url?: string;
54
50
  // Agent SDK-specific settings (ignored by mock provider)
@@ -73,7 +69,6 @@ export const DEFAULT_CONFIG: PpmConfig = {
73
69
  providers: {
74
70
  claude: {
75
71
  type: "agent-sdk",
76
- execution_mode: "sdk",
77
72
  api_key_env: "ANTHROPIC_API_KEY",
78
73
  model: "claude-sonnet-4-6",
79
74
  effort: "high",
@@ -97,9 +92,6 @@ export function validateAIProviderConfig(config: Partial<AIProviderConfig>): str
97
92
  if (config.type != null && !VALID_TYPES.includes(config.type as any)) {
98
93
  errors.push(`type must be one of: ${VALID_TYPES.join(", ")}`);
99
94
  }
100
- if (config.execution_mode != null && !VALID_EXECUTION_MODES.includes(config.execution_mode as any)) {
101
- errors.push(`execution_mode must be one of: ${VALID_EXECUTION_MODES.join(", ")}`);
102
- }
103
95
  if (config.model != null && !VALID_MODELS.includes(config.model as any)) {
104
96
  errors.push(`model must be one of: ${VALID_MODELS.join(", ")}`);
105
97
  }